[
  {
    "path": ".dockerignore",
    "content": "bin/\ntmp/\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\nindent_size = 4\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.go]\nindent_style = tab\n\n[*.proto]\nindent_size = 2\n\n[{Makefile,*.mk}]\nindent_style = tab\n\n[{config.yaml.dist,config.dev.yaml}]\nindent_size = 2\n\n[.golangci.yaml]\nindent_size = 2\n"
  },
  {
    "path": ".envrc",
    "content": "if ! has nix_direnv_version || ! nix_direnv_version 3.0.6; then\n  source_url \"https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.6/direnvrc\" \"sha256-RYcUJaRMf8oF5LznDrlCXbkOQrywm0HDv1VjYGaJGdM=\"\nfi\nuse flake . --impure\n\ndotenv_if_exists\n"
  },
  {
    "path": ".github/.editorconfig",
    "content": "[{*.yml,*.yaml}]\nindent_size = 2\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "## Community Code of Conduct\n\nThis project follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).\n"
  },
  {
    "path": ".github/DCO",
    "content": "Developer Certificate of Origin\nVersion 1.1\n\nCopyright (C) 2004, 2006 The Linux Foundation and its contributors.\n660 York Street, Suite 102,\nSan Francisco, CA 94110 USA\n\nEveryone is permitted to copy and distribute verbatim copies of this\nlicense document, but changing it is not allowed.\n\n\nDeveloper's Certificate of Origin 1.1\n\nBy making a contribution to this project, I certify that:\n\n(a) The contribution was created in whole or in part by me and I\n    have the right to submit it under the open source license\n    indicated in the file; or\n\n(b) The contribution is based upon previous work that, to the best\n    of my knowledge, is covered under an appropriate open source\n    license and I have the right under that license to submit that\n    work with modifications, whether created in whole or in part\n    by me, under the same open source license (unless I am\n    permitted to submit under a different license), as indicated\n    in the file; or\n\n(c) The contribution was provided directly to me by some other\n    person who certified (a), (b) or (c) and I have not modified\n    it.\n\n(d) I understand and agree that this project and the contribution\n    are public and that a record of the contribution (including all\n    personal information I submit with it, including my sign-off) is\n    maintained indefinitely and may be redistributed consistent with\n    this project or the open source license(s) involved.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yaml",
    "content": "name: 🐛 Bug report\ndescription: Report a bug to help us improve Dex\nbody:\n- type: markdown\n  attributes:\n    value: |\n      Thank you for submitting a bug report!\n\n      Please fill out the template below to make it easier to debug your problem.\n\n      If you are not sure if it is a bug or not, you can contact us via the available [support channels](https://github.com/dexidp/dex/issues/new/choose).\n- type: checkboxes\n  attributes:\n    label: Preflight Checklist\n    description: Please ensure you've completed all of the following.\n    options:\n      - label: I agree to follow the [Code of Conduct](https://github.com/dexidp/dex/blob/master/.github/CODE_OF_CONDUCT.md) that this project adheres to.\n        required: true\n      - label: I have searched the [issue tracker](https://www.github.com/dexidp/dex/issues) for an issue that matches the one I want to file, without success.\n        required: true\n      - label: I am not looking for support or already pursued the available [support channels](https://github.com/dexidp/dex/issues/new/choose) without success.\n        required: true\n- type: input\n  attributes:\n    label: Version\n    description: What version of Dex are you running?\n    placeholder: 2.29.0\n  validations:\n    required: true\n- type: dropdown\n  attributes:\n    label: Storage Type\n    description: Which persistent storage type are you using?\n    options:\n      - etcd\n      - Kubernetes\n      - In-memory\n      - Postgres\n      - MySQL\n      - SQLite\n  validations:\n    required: true\n- type: dropdown\n  attributes:\n    label: Installation Type\n    description: How did you install Dex?\n    options:\n      - Binary\n      - Official container image\n      - Official Helm chart\n      - Custom container image\n      - Custom Helm chart\n      - Other (specify below)\n    multiple: true\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: Expected Behavior\n    description: A clear and concise description of what you expected to happen.\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: Actual Behavior\n    description: A clear description of what actually happens.\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: Steps To Reproduce\n    description: Steps to reproduce the behavior if it is not self-explanatory.\n    placeholder: |\n      1. In this environment...\n      2. With this config...\n      3. Run '...'\n      4. See error...\n- type: textarea\n  attributes:\n    label: Additional Information\n    description: Links? References? Anything that will give us more context about the issue that you are encountering!\n- type: textarea\n  attributes:\n    label: Configuration\n    description: Contents of your configuration file (if relevant).\n    render: yaml\n    placeholder: |\n      issuer: http://127.0.0.1:5556/dex\n\n      storage:\n        # ...\n\n      connectors:\n        # ...\n\n      staticClients:\n        # ...\n- type: textarea\n  attributes:\n    label: Logs\n    description: Dex application logs (if relevant).\n    render: shell\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: 📖 Documentation enhancement\n    url: https://github.com/dexidp/website/issues\n    about: Suggest an improvement to the documentation\n\n  - name: ❓ Ask a question\n    url: https://github.com/dexidp/dex/discussions/new?category=q-a\n    about: Ask and discuss questions with other Dex community members\n\n  - name: 📚 Documentation\n    url: https://dexidp.io/docs/\n    about: Check the documentation for help\n\n  - name: 💬 Slack channel\n    url: https://cloud-native.slack.com/messages/dexidp\n    about: Please ask and answer questions here\n\n  - name: 💡 Dex Enhancement Proposal\n    url: https://github.com/dexidp/dex/tree/master/docs/enhancements/README.md\n    about: Open a proposal for significant architectural change\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yaml",
    "content": "name: 🎉 Feature request\ndescription: Suggest an idea for Dex\nbody:\n- type: markdown\n  attributes:\n    value: |\n      Thank you for submitting a feature request!\n\n      Please describe what you would like to change/add and why in detail by filling out the template below.\n\n      If you are not sure if your request fits into Dex, you can contact us via the available [support channels](https://github.com/dexidp/dex/issues/new/choose).\n- type: checkboxes\n  attributes:\n    label: Preflight Checklist\n    description: Please ensure you've completed all of the following.\n    options:\n      - label: I agree to follow the [Code of Conduct](https://github.com/dexidp/dex/blob/master/.github/CODE_OF_CONDUCT.md) that this project adheres to.\n        required: true\n      - label: I have searched the [issue tracker](https://www.github.com/dexidp/dex/issues) for an issue that matches the one I want to file, without success.\n        required: true\n- type: textarea\n  attributes:\n    label: Problem Description\n    description: A clear and concise description of the problem you are seeking to solve with this feature request.\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: Proposed Solution\n    description: A clear and concise description of what would you like to happen.\n  validations:\n    required: true\n- type: textarea\n  attributes:\n    label: Alternatives Considered\n    description: A clear and concise description of any alternative solutions or features you've considered.\n- type: textarea\n  attributes:\n    label: Additional Information\n    description: Add any other context about the problem here.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nThank you for sending a pull request! Here are some tips for contributors:\n\n1. Fill the description template below.\n2. Sign a DCO (if you haven't already signed it).\n3. Include appropriate tests (if necessary). Make sure that all CI checks passed.\n4. If the Pull Request is a work in progress, make use of GitHub's \"Draft PR\" feature and mark it as such.\n-->\n\n#### Overview\n\n<!-- Describe your changes briefly here. -->\n\n#### What this PR does / why we need it\n\n<!--\n- Please state in detail why we need this PR and what it solves.\n- If your PR closes some of the existing issues, please add links to them here.\n  Mentioned issues will be automatically closed.\n  Usage: \"Closes #<issue number>\", or \"Closes (paste link of issue)\"\n-->\n\n#### Special notes for your reviewer\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a vulnerability\n\nTo report a vulnerability, send an email to [cncf-dex-maintainers@lists.cncf.io](mailto:cncf-dex-maintainers@lists.cncf.io)\ndetailing the issue and steps to reproduce. The reporter(s) can expect a\nresponse within 48 hours acknowledging the issue was received. If a response is\nnot received within 48 hours, please reach out to any maintainer directly\nto confirm receipt of the issue.\n\n## Review Process\n\nOnce a maintainer has confirmed the relevance of the report, a draft security\nadvisory will be created on GitHub. The draft advisory will be used to discuss\nthe issue with maintainers, the reporter(s).\nIf the reporter(s) wishes to participate in this discussion, then provide\nreporter GitHub username(s) to be invited to the discussion. If the reporter(s)\ndoes not wish to participate directly in the discussion, then the reporter(s)\ncan request to be updated regularly via email.\n\nIf the vulnerability is accepted, a timeline for developing a patch, public\ndisclosure, and patch release will be determined. The reporter(s) are expected\nto participate in the discussion of the timeline and abide by agreed upon dates\nfor public disclosure.\n"
  },
  {
    "path": ".github/dependabot.yaml",
    "content": "version: 2\n\nupdates:\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    labels:\n      - \"area/dependencies\"\n    schedule:\n      interval: \"daily\"\n    groups:\n      etcd:\n        patterns:\n          - \"go.etcd.io/*\"\n\n  - package-ecosystem: \"gomod\"\n    directory: \"/api/v2\"\n    labels:\n      - \"area/dependencies\"\n    schedule:\n      interval: \"daily\"\n\n  - package-ecosystem: \"gomod\"\n    directory: \"/examples\"\n    labels:\n      - \"area/dependencies\"\n    schedule:\n      interval: \"daily\"\n\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    labels:\n      - \"area/dependencies\"\n    schedule:\n      interval: \"daily\"\n\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    labels:\n      - \"area/dependencies\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    labels:\n      - release-note/ignore\n  categories:\n    - title: Exciting New Features 🎉\n      labels:\n        - kind/feature\n        - release-note/new-feature\n    - title: Enhancements 🚀\n      labels:\n        - kind/enhancement\n        - release-note/enhancement\n    - title: Bug Fixes 🐛\n      labels:\n        - kind/bug\n        - release-note/bug-fix\n    - title: Breaking Changes 🛠\n      labels:\n        - release-note/breaking-change\n    - title: Deprecations ❌\n      labels:\n        - release-note/deprecation\n    - title: Dependency Updates ⬆️\n      labels:\n        - area/dependencies\n        - release-note/dependency-update\n    - title: Other Changes\n      labels:\n        - \"*\"\n"
  },
  {
    "path": ".github/workflows/analysis-scorecard.yaml",
    "content": "name: OpenSSF Scorecard\n\non:\n  branch_protection_rule:\n  push:\n    branches: [ main ]\n  schedule:\n    - cron: '30 0 * * 5'\n\npermissions:\n  contents: read\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    permissions:\n      actions: read\n      contents: read\n      id-token: write\n      security-events: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          persist-credentials: false\n\n      - name: Run analysis\n        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          publish_results: true\n\n      - name: Upload results as artifact\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: OpenSSF Scorecard results\n          path: results.sarif\n          retention-days: 5\n\n      - name: Upload results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v3.29.5\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/artifacts.yaml",
    "content": "name: Artifacts\n\non:\n  workflow_call:\n    inputs:\n      publish:\n        description: Publish artifacts to the artifact store\n        default: false\n        required: false\n        type: boolean\n    secrets:\n      DOCKER_USERNAME:\n        required: true\n      DOCKER_PASSWORD:\n        required: true\n    outputs:\n      container-image-name:\n        description: Container image name\n        value: ${{ jobs.container-images.outputs.name }}\n      container-image-digest:\n        description: Container image digest\n        value: ${{ jobs.container-images.outputs.digest }}\n      container-image-ref:\n        description: Container image ref\n        value: ${{ jobs.container-images.outputs.ref }}\n\npermissions:\n  contents: read\n\njobs:\n  container-images:\n    name: Container images\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        variant:\n          - alpine\n          - distroless\n\n    permissions:\n      attestations: write\n      contents: read\n      packages: write\n      id-token: write\n      security-events: write\n\n    outputs:\n      name: ${{ steps.image-name.outputs.value }}\n      digest: ${{ steps.build.outputs.digest }}\n      ref: ${{ steps.image-ref.outputs.value }}\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-tags: true\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0\n\n      - name: Set up Syft\n        uses: anchore/sbom-action/download-syft@57aae528053a48a3f6235f2d9461b05fbcb7366d # v0.23.1\n\n      - name: Install cosign\n        uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0\n\n      - name: Set image name\n        id: image-name\n        run: echo \"value=ghcr.io/${{ github.repository }}\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Gather build metadata\n        id: meta\n        uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0\n        with:\n          images: |\n            ${{ steps.image-name.outputs.value }}\n            ${{ github.repository == 'dexidp/dex' && 'dexidp/dex' || '' }}\n          flavor: |\n            latest = false\n          tags: |\n            type=ref,event=branch,enable=${{ matrix.variant == 'alpine' }}\n            type=ref,event=pr,prefix=pr-,enable=${{ matrix.variant == 'alpine' }}\n            type=semver,pattern={{raw}},enable=${{ matrix.variant == 'alpine' }}\n            type=raw,value=latest,enable=${{ github.ref_name == github.event.repository.default_branch && matrix.variant == 'alpine' }}\n            type=ref,event=branch,suffix=-${{ matrix.variant }}\n            type=ref,event=pr,prefix=pr-,suffix=-${{ matrix.variant }}\n            type=semver,pattern={{raw}},suffix=-${{ matrix.variant }}\n            type=raw,value=latest,enable={{is_default_branch}},suffix=-${{ matrix.variant }}\n          labels: |\n            org.opencontainers.image.documentation=https://dexidp.io/docs/\n\n      # Multiple exporters are not supported yet\n      # See https://github.com/moby/buildkit/pull/2760\n      - name: Get version from git-version script\n        id: version\n        run: echo \"value=$(bash ./scripts/git-version)\" >> \"$GITHUB_OUTPUT\"\n\n      # Multiple exporters are not supported yet\n      # See https://github.com/moby/buildkit/pull/2760\n      - name: Determine build output\n        uses: haya14busa/action-cond@94f77f7a80cd666cb3155084e428254fea4281fd # v1.2.1\n        id: build-output\n        with:\n          cond: ${{ inputs.publish }}\n          if_true: type=image,push=true\n          if_false: type=oci,dest=image.tar\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ github.token }}\n        if: inputs.publish\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n        if: inputs.publish\n\n      - name: Build and push image\n        id: build\n        uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0\n        with:\n          context: .\n          platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x\n          tags: ${{ steps.meta.outputs.tags }}\n          build-args: |\n            BASE_IMAGE=${{ matrix.variant }}\n            VERSION=${{ steps.version.outputs.value }}\n            COMMIT_HASH=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }}\n            BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }}\n          labels: |\n            ${{ steps.meta.outputs.labels }}\n          # cache-from: type=gha\n          # cache-to: type=gha,mode=max\n          outputs: ${{ steps.build-output.outputs.value }}\n          # push: ${{ inputs.publish }}\n\n      - name: Sign the images with GitHub OIDC Token\n        run: |\n          cosign sign --yes ${{ steps.image-name.outputs.value }}@${{ steps.build.outputs.digest }}\n        if: inputs.publish\n\n      - name: Set image ref\n        id: image-ref\n        run: echo \"value=${{ steps.image-name.outputs.value }}@${{ steps.build.outputs.digest }}\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Fetch image\n        run: skopeo --insecure-policy copy docker://${{ steps.image-ref.outputs.value }} oci-archive:image.tar\n        if: inputs.publish\n\n      # Uncomment the following lines for debugging:\n      # - name: Upload image as artifact\n      #   uses: actions/upload-artifact@v3\n      #   with:\n      #     name: \"[${{ github.job }}] OCI tarball\"\n      #     path: image.tar\n\n      - name: Extract OCI tarball\n        id: extract-oci\n        run: |\n          mkdir -p image\n          tar -xf image.tar -C image\n\n          image_name=$(jq -r '.manifests[0].annotations[\"io.containerd.image.name\"]' image/index.json)\n          image_tag=$(jq -r '.manifests[0].annotations[\"org.opencontainers.image.ref.name\"]' image/index.json)\n\n          echo \"Copying $image_tag -> $image_name\"\n          skopeo copy \"oci:image:$image_tag\" \"docker-daemon:$image_name\"\n\n          echo \"value=$image_name\" >> \"$GITHUB_OUTPUT\"\n        if: ${{ !inputs.publish }}\n\n\n      # - name: List tags\n      #   run: skopeo --insecure-policy list-tags oci:image\n      #\n      # # See https://github.com/anchore/syft/issues/1545\n      # - name: Extract image from multi-arch image\n      #   run: skopeo --override-os linux --override-arch amd64 --insecure-policy copy oci:image:${{ steps.image-name.outputs.value }}:${{ steps.meta.outputs.version }} docker-archive:docker.tar\n      #\n      # - name: Generate SBOM\n      #   run: syft -o spdx-json=sbom-spdx.json docker-archive:docker.tar\n      #\n      # - name: Upload SBOM as artifact\n      #   uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3\n      #   with:\n      #     name: \"[${{ github.job }}] SBOM\"\n      #     path: sbom-spdx.json\n      #     retention-days: 5\n\n      # TODO: uncomment when the action is working for non ghcr.io pushes. GH Issue: https://github.com/actions/attest-build-provenance/issues/80\n      # - name: Generate build provenance attestation\n      #   uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0\n      #   with:\n      #     subject-name: dexidp/dex\n      #     subject-digest: ${{ steps.build.outputs.digest }}\n      #     push-to-registry: true\n\n      - name: Generate build provenance attestation\n        uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0\n        with:\n          subject-name: ghcr.io/${{ github.repository }}\n          subject-digest: ${{ steps.build.outputs.digest }}\n          push-to-registry: true\n        if: inputs.publish\n\n      - name: Prepare image fs for scanning\n        run: |\n          image_ref=${{ steps.extract-oci.outputs.value != '' && steps.extract-oci.outputs.value || steps.image-ref.outputs.value }}\n          docker export $(docker create --rm $image_ref) -o docker-image.tar\n\n          mkdir -p docker-image\n          tar -xf docker-image.tar -C docker-image\n\n      ## Use cache for the trivy-db to avoid the TOOMANYREQUESTS error https://github.com/aquasecurity/trivy-action/pull/397\n      ## To avoid the trivy-db becoming outdated, we save the cache for one day\n      - name: Get data\n        id: date\n        run: echo \"date=$(date +%Y-%m-%d)\" >> $GITHUB_OUTPUT\n\n      - name: Restore trivy cache\n        uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4\n        with:\n          path: cache/db\n          key: trivy-cache-${{ steps.date.outputs.date }}\n          restore-keys: trivy-cache-\n\n      - name: Run Trivy vulnerability scanner\n        uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # 0.35.0\n        with:\n          input: docker-image\n          format: sarif\n          output: trivy-results.sarif\n          scan-type: \"rootfs\"\n          scan-ref: \".\"\n          cache-dir: \"./cache\"\n        # Disable skipping trivy cache for now\n        env:\n          TRIVY_SKIP_DB_UPDATE: true\n          TRIVY_SKIP_JAVA_DB_UPDATE: true\n\n      ## Trivy-db uses `0600` permissions.\n      ## But `action/cache` use `runner` user by default\n      ## So we need to change the permissions before caching the database.\n      - name: change permissions for trivy.db\n        run: sudo chmod 0644 ./cache/db/trivy.db\n\n      - name: Check Trivy sarif\n        run: cat trivy-results.sarif\n\n      - name: Upload Trivy scan results as artifact\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: \"[${{ github.job }}] Trivy scan results\"\n          path: trivy-results.sarif\n          retention-days: 5\n          overwrite: true\n\n      - name: Upload Trivy scan results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v3.29.5\n        with:\n          sarif_file: trivy-results.sarif\n"
  },
  {
    "path": ".github/workflows/checks.yaml",
    "content": "name: PR Checks\n\non:\n  pull_request:\n    types: [opened, labeled, unlabeled, synchronize]\n\npermissions:\n  contents: read\n\njobs:\n  release-label:\n    name: Release note label\n    runs-on: ubuntu-latest\n\n    if: github.repository == 'dexidp/dex'\n\n    steps:\n      - name: Check minimum labels\n        uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5\n        with:\n          mode: minimum\n          count: 1\n          labels: \"release-note/ignore, kind/feature, release-note/new-feature, kind/enhancement, release-note/enhancement, kind/bug, release-note/bug-fix, release-note/breaking-change, release-note/deprecation, area/dependencies, release-note/dependency-update\"\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\n\non:\n  push:\n    branches: [master]\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n\n    services:\n      postgres:\n        image: postgres:10.8\n        env:\n          TZ: UTC\n        ports:\n          - 5432\n        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5\n\n      postgres-ent:\n        image: postgres:10.8\n        env:\n          TZ: UTC\n        ports:\n          - 5432\n        options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5\n\n      mysql:\n        image: mysql:5.7\n        env:\n          MYSQL_ROOT_PASSWORD: root\n          MYSQL_DATABASE: dex\n        ports:\n          - 3306\n        options: --health-cmd \"mysql -proot -e \\\"show databases;\\\"\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n      mysql-ent:\n        image: mysql:5.7\n        env:\n          MYSQL_ROOT_PASSWORD: root\n          MYSQL_DATABASE: dex\n        ports:\n          - 3306\n        options: --health-cmd \"mysql -proot -e \\\"show databases;\\\"\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n      mysql8:\n        image: mysql:8.0\n        env:\n          MYSQL_ROOT_PASSWORD: root\n          MYSQL_DATABASE: dex\n        ports:\n          - 3306\n        options: --health-cmd \"mysql -proot -e \\\"show databases;\\\"\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n      mysql8-ent:\n        image: mysql:8.0\n        env:\n          MYSQL_ROOT_PASSWORD: root\n          MYSQL_DATABASE: dex\n        ports:\n          - 3306\n        options: --health-cmd \"mysql -proot -e \\\"show databases;\\\"\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n      etcd:\n        image: gcr.io/etcd-development/etcd:v3.5.0\n        ports:\n          - 2379\n        env:\n          ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379\n          ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379\n        options: --health-cmd \"ETCDCTL_API=3 etcdctl --endpoints http://localhost:2379 endpoint health\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n      keystone:\n        image: openio/openstack-keystone:rocky\n        ports:\n          - 5000\n          - 35357\n        options: --health-cmd \"curl --fail http://localhost:5000/v3\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n      vault:\n        image: hashicorp/vault:1.21\n        ports:\n          - 8200\n        env:\n          VAULT_DEV_ROOT_TOKEN_ID: root-token\n          VAULT_DEV_LISTEN_ADDRESS: \"0.0.0.0:8200\"\n        options: --health-cmd \"vault status -address=http://localhost:8200 || exit 1\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n      openbao:\n        image: quay.io/openbao/openbao:2.5\n        ports:\n          - 8210\n        env:\n          BAO_DEV_ROOT_TOKEN_ID: root-token\n          BAO_DEV_LISTEN_ADDRESS: \"0.0.0.0:8210\"\n        options: --health-cmd \"bao status -address=http://localhost:8210 || exit 1\" --health-interval 10s --health-timeout 5s --health-retries 5\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Set up Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: \"1.25\"\n\n      - name: Download tool dependencies\n        run: make deps\n\n      # Ensure that generated files were committed.\n      # It can help us determine, that the code is in the intermediate state, which should not be tested.\n      # Thus, heavy jobs like creating a kind cluster and testing / linting will be skipped.\n      - name: Verify\n        run: make verify\n\n      - name: Start services\n        run: docker compose -f docker-compose.test.yaml up -d\n\n      - name: Create kind cluster\n        uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0\n        with:\n          version: \"v0.17.0\"\n          node_image: \"kindest/node:v1.25.3@sha256:cd248d1438192f7814fbca8fede13cfe5b9918746dfa12583976158a834fd5c5\"\n\n      - name: Test\n        run: make testall\n        env:\n          DEX_MYSQL_DATABASE: dex\n          DEX_MYSQL_USER: root\n          DEX_MYSQL_PASSWORD: root\n          DEX_MYSQL_HOST: 127.0.0.1\n          DEX_MYSQL_PORT: ${{ job.services.mysql.ports[3306] }}\n\n          DEX_MYSQL_ENT_DATABASE: dex\n          DEX_MYSQL_ENT_USER: root\n          DEX_MYSQL_ENT_PASSWORD: root\n          DEX_MYSQL_ENT_HOST: 127.0.0.1\n          DEX_MYSQL_ENT_PORT: ${{ job.services.mysql-ent.ports[3306] }}\n\n          DEX_MYSQL8_DATABASE: dex\n          DEX_MYSQL8_USER: root\n          DEX_MYSQL8_PASSWORD: root\n          DEX_MYSQL8_HOST: 127.0.0.1\n          DEX_MYSQL8_PORT: ${{ job.services.mysql8.ports[3306] }}\n\n          DEX_MYSQL8_ENT_DATABASE: dex\n          DEX_MYSQL8_ENT_USER: root\n          DEX_MYSQL8_ENT_PASSWORD: root\n          DEX_MYSQL8_ENT_HOST: 127.0.0.1\n          DEX_MYSQL8_ENT_PORT: ${{ job.services.mysql8-ent.ports[3306] }}\n\n          DEX_POSTGRES_DATABASE: postgres\n          DEX_POSTGRES_USER: postgres\n          DEX_POSTGRES_PASSWORD: postgres\n          DEX_POSTGRES_HOST: localhost\n          DEX_POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }}\n\n          DEX_POSTGRES_ENT_DATABASE: postgres\n          DEX_POSTGRES_ENT_USER: postgres\n          DEX_POSTGRES_ENT_PASSWORD: postgres\n          DEX_POSTGRES_ENT_HOST: localhost\n          DEX_POSTGRES_ENT_PORT: ${{ job.services.postgres-ent.ports[5432] }}\n\n          DEX_ETCD_ENDPOINTS: http://localhost:${{ job.services.etcd.ports[2379] }}\n\n          DEX_LDAP_HOST: localhost\n          DEX_LDAP_PORT: 3890\n          DEX_LDAP_TLS_PORT: 6360\n\n          DEX_KEYSTONE_URL: http://localhost:${{ job.services.keystone.ports[5000] }}\n          DEX_KEYSTONE_ADMIN_URL: http://localhost:${{ job.services.keystone.ports[35357] }}\n          DEX_KEYSTONE_ADMIN_USER: demo\n          DEX_KEYSTONE_ADMIN_PASS: DEMO_PASS\n\n          DEX_VAULT_ADDR: http://localhost:${{ job.services.vault.ports[8200] }}\n          DEX_VAULT_TOKEN: root-token\n          DEX_OPENBAO_ADDR: http://localhost:${{ job.services.openbao.ports[8210] }}\n          DEX_OPENBAO_TOKEN: root-token\n\n          DEX_KUBERNETES_CONFIG_PATH: ~/.kube/config\n\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Set up Go\n        uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0\n        with:\n          go-version: \"1.25\"\n\n      - name: Download golangci-lint\n        run: make bin/golangci-lint\n\n      - name: Lint\n        run: make lint\n\n  artifacts:\n    name: Artifacts\n    uses: ./.github/workflows/artifacts.yaml\n    with:\n      publish: ${{ github.event_name == 'push' }}\n    secrets:\n      DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}\n      DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}\n    permissions:\n      attestations: write\n      contents: read\n      packages: write\n      id-token: write\n      security-events: write\n\n  dependency-review:\n    name: Dependency review\n    runs-on: ubuntu-latest\n    if: github.event_name == 'pull_request'\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Dependency Review\n        uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\n\non:\n  push:\n    tags: [ \"v[0-9]+.[0-9]+.[0-9]+\" ]\n\npermissions:\n  contents: read\n\njobs:\n  artifacts:\n    name: Artifacts\n    uses: ./.github/workflows/artifacts.yaml\n    with:\n      publish: true\n    secrets:\n      DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}\n      DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}\n    permissions:\n      attestations: write\n      contents: read\n      packages: write\n      id-token: write\n      security-events: write\n"
  },
  {
    "path": ".github/workflows/trivydb-cache.yaml",
    "content": "# Note: This workflow only updates the cache. You should create a separate workflow for your actual Trivy scans.\n# In your scan workflow, set TRIVY_SKIP_DB_UPDATE=true and TRIVY_SKIP_JAVA_DB_UPDATE=true.\nname: Update Trivy Cache\n\non:\n  schedule:\n    - cron: '0 0 * * *'  # Run daily at midnight UTC\n  workflow_dispatch:  # Allow manual triggering\n\npermissions:\n  contents: read\n\njobs:\n  update-trivy-db:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Setup oras\n        uses: oras-project/setup-oras@22ce207df3b08e061f537244349aac6ae1d214f6 # v1.2.4\n\n      - name: Get current date\n        id: date\n        run: echo \"date=$(date +'%Y-%m-%d')\" >> $GITHUB_OUTPUT\n\n      - name: Download and extract the vulnerability DB\n        run: |\n          mkdir -p $GITHUB_WORKSPACE/.cache/trivy/db\n          oras pull ghcr.io/aquasecurity/trivy-db:2\n          tar -xzf db.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/db\n          rm db.tar.gz\n\n      - name: Download and extract the Java DB\n        run: |\n          mkdir -p $GITHUB_WORKSPACE/.cache/trivy/java-db\n          oras pull ghcr.io/aquasecurity/trivy-java-db:1\n          tar -xzf javadb.tar.gz -C $GITHUB_WORKSPACE/.cache/trivy/java-db\n          rm javadb.tar.gz\n\n      - name: Cache DBs\n        uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4\n        with:\n          path: ${{ github.workspace }}/.cache/trivy\n          key: cache-trivy-${{ steps.date.outputs.date }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/.devenv/\n/.direnv/\n/.idea/\n/bin/\n/config.yaml\n/docker-compose.override.yaml\n/var/\n/vendor/\n*.db\n"
  },
  {
    "path": ".gitpod.yml",
    "content": "tasks:\n  - init: go get && go build ./... && go test ./... && make\n    command: go run\n"
  },
  {
    "path": ".golangci.yaml",
    "content": "version: \"2\"\n\nrun:\n  timeout: 5m\n\nlinters:\n  disable:\n    - staticcheck\n    - errcheck\n  enable:\n    - depguard\n    - dogsled\n    - exhaustive\n    - gochecknoinits\n    # - gocritic\n    - goprintffuncname\n    - govet\n    - ineffassign\n    - misspell\n    - nakedret\n    - nolintlint\n    - prealloc\n    # - revive\n    # - sqlclosecheck\n    # - staticcheck\n    - unconvert\n    - unused\n    - whitespace\n\n    # Disable temporarily until everything works with Go 1.20\n    # - bodyclose\n    # - rowserrcheck\n    # - tparallel\n    # - unparam\n\n    # Disable temporarily until the following issue is resolved: https://github.com/golangci/golangci-lint/issues/3086\n    # - sqlclosecheck\n\n    # TODO: fix linter errors before enabling\n    # - exhaustivestruct\n    # - gochecknoglobals\n    # - errorlint\n    # - gocognit\n    # - godot\n    # - nlreturn\n    # - noctx\n    # - revive\n    # - wrapcheck\n\n    # TODO: fix linter errors before enabling (from original config)\n    # - dupl\n    # - errcheck\n    # - goconst\n    # - gocyclo\n    # - gosec\n    # - lll\n    # - scopelint\n\n    # unused\n    # - goheader\n    # - gomodguard\n\n    # don't enable:\n    # - asciicheck\n    # - funlen\n    # - godox\n    # - goerr113\n    # - gomnd\n    # - interfacer\n    # - maligned\n    # - nestif\n    # - testpackage\n    # - wsl\n\n  exclusions:\n    rules:\n      - linters:\n          - errcheck\n          - noctx\n        path: _test.go\n    presets:\n      - comments\n      - std-error-handling\n\n  settings:\n    misspell:\n      locale: US\n    nolintlint:\n      allow-unused: false # report any unused nolint directives\n      require-specific: false # don't require nolint directives to be specific about which linter is being skipped\n    gocritic:\n      # Enable multiple checks by tags. See \"Tags\" section in https://github.com/go-critic/go-critic#usage.\n      enabled-tags:\n        - diagnostic\n        - experimental\n        - opinionated\n        - style\n      disabled-checks:\n        - importShadow\n        - unnamedResult\n    depguard:\n      rules:\n        deprecated:\n          deny:\n            - pkg: \"io/ioutil\"\n              desc: \"The 'io/ioutil' package is deprecated. Use corresponding 'os' or 'io' functions instead.\"\n\nformatters:\n  enable:\n    - gci\n    - gofmt\n    - gofumpt\n    - goimports\n    # - golines\n\n  settings:\n    gci:\n      sections:\n        - standard\n        - default\n        - localmodule\n# issues:\n#     exclude-dirs:\n#         - storage/ent/db # generated ent code\n"
  },
  {
    "path": "ADOPTERS.md",
    "content": "# Adopters\n\nThis is a list of production adopters of Dex (in alphabetical order).\n\n# Companies\n\n- [Aspect](https://www.aspect.com/) uses Dex for authenticating users across their Kubernetes infrastructure (using Kubernetes OIDC support).\n- [Banzai Cloud](https://banzaicloud.com) is using Dex for authenticating to its Pipeline control plane and also to authenticate users against provisioned Kubernetes clusters (via Kubernetes OIDC support).\n- [Ericsson](https://www.ericsson.com) is using Dex to authenticate access to Kubernetes API server in [Cloud Container Distribution](https://www.ericsson.com/en/portfolio/cloud-software-and-services/cloud-core/cloud-infrastructure/nfvi/cloud-container-distribution).\n- [Flant](https://flant.com) uses Dex for providing access to core components of [Managed Kubernetes as a Service](https://flant.com/services/managed-kubernetes-as-a-service), integration with various authentication providers, plugging custom applications.\n- [JuliaBox](https://juliabox.com/) is leveraging federated OIDC provided by Dex for authenticating users to their compute infrastructure based on Kubernetes.\n- [Pusher](https://pusher.com) uses Dex for authenticating users across their Kubernetes infrastructure (using Kubernetes OIDC support) in conjunction with the [OAuth2 Proxy](https://github.com/pusher/oauth2_proxy) for protecting web UIs.\n\n# Projects\n\n- [Argo CD](https://argoproj.github.io/cd) integrates Dex to provide convenient Single Sign On capabilities to its web UI and CLI\n- [Chef](https://chef.io) uses Dex for authenticating users in [Chef Automate](https://automate.chef.io/). The code is Open Source, available at [`github.com/chef/automate`](https://github.com/chef/automate).\n- [Elastisys](https://elastisys.com) uses Dex for authentication in [Welkin, The Application Platform for Software Critical to Society](https://elastisys.io/welkin/), including SSO to Grafana, OpenSearch, and Harbor.\n- [Kasten](https://www.kasten.io) is using Dex for authenticating access to the dashboard of [K10](https://www.kasten.io/product/), a Kubernetes-native platform for backup, disaster recovery and mobility of Kubernetes applications. K10 is widely used by a variety of customers including large enterprises, financial services, design firms, and IT companies.\n- [Kubeflow](https://www.kubeflow.org/) [uses](https://github.com/kubeflow/manifests#dex) Dex as one of its components in the Kubeflow Platform for external OIDC authentication.\n- [Kyma](https://kyma-project.io) is using Dex to authenticate access to Kubernetes API server (even for managed Kubernetes like Google Kubernetes Engine or Azure Kubernetes Service) and for protecting web UI of [Kyma Console](https://github.com/kyma-project/console) and other UIs integrated in Kyma ([Grafana](https://github.com/grafana/grafana), [Loki](https://github.com/grafana/loki), and [Jaeger](https://github.com/jaegertracing/jaeger)). Kyma is an open-source project ([`github.com/kyma-project`](https://github.com/kyma-project/kyma)) designed natively on Kubernetes, that allows you to extend and customize your applications in a quick and modern way, using serverless computing or microservice architecture. \n- [LitmusChaos](https://litmuschaos.io/) uses Dex to [implement](https://docs.litmuschaos.io/docs/user-guides/chaoscenter-oauth-dex-installation#deploy-dex-oidc-provider) OAuth2 login support in ChaosCenter, its centralized chaos management tool.\n- [LLMariner](https://llmariner.ai/) uses Dex for [user management](https://llmariner.ai/docs/features/user_management/).\n- [Pydio](https://pydio.com/) Pydio Cells is an open source sync & share platform written in Go. Cells is using Dex as an OIDC service for authentication and authorizations. Check out [Pydio Cells repository](https://github.com/pydio/cells) for more information and/or to contribute.\n- [sigstore](https://sigstore.dev) uses Dex for authentication in their public Fulcio instance, which is a certificate authority for code signing certificates bound to OIDC-based identities.\n- [Terrakube](https://docs.terrakube.io/) relies on Dex for [user authentication](https://docs.terrakube.io/getting-started/deployment/user-authentication-dex). Its Helm chart uses Dex as a dependency.\n"
  },
  {
    "path": "Dockerfile",
    "content": "ARG BASE_IMAGE=alpine\n\nFROM --platform=$BUILDPLATFORM tonistiigi/xx:1.9.0@sha256:c64defb9ed5a91eacb37f96ccc3d4cd72521c4bd18d5442905b95e2226b0e707 AS xx\n\nFROM --platform=$BUILDPLATFORM golang:1.26.1-alpine3.22@sha256:07e91d24f6330432729082bb580983181809e0a48f0f38ecde26868d4568c6ac AS builder\n\nCOPY --from=xx / /\n\nRUN apk add --update alpine-sdk ca-certificates openssl clang lld\n\nARG TARGETPLATFORM\n\nRUN xx-apk --update add musl-dev gcc\n\n# lld has issues building static binaries for ppc so prefer ld for it\nRUN [ \"$(xx-info arch)\" != \"ppc64le\" ] || XX_CC_PREFER_LINKER=ld xx-clang --setup-target-triple\n\nRUN xx-go --wrap\n\nWORKDIR /usr/local/src/dex\n\nARG GOPROXY\n\nENV CGO_ENABLED=1\n\nCOPY go.mod go.sum ./\nCOPY api/v2/go.mod api/v2/go.sum ./api/v2/\nRUN go mod download\n\nCOPY . .\n\n# Propagate Dex version from build args to the build environment\nARG VERSION\nRUN make release-binary\n\nRUN xx-verify /go/bin/dex && xx-verify /go/bin/docker-entrypoint\n\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS stager\n\nRUN mkdir -p /var/dex\nRUN mkdir -p /etc/dex\nCOPY config.docker.yaml /etc/dex/\n\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS gomplate\n\nARG TARGETOS\nARG TARGETARCH\nARG TARGETVARIANT\n\nENV GOMPLATE_VERSION=v5.0.0\n\nRUN wget -O /usr/local/bin/gomplate \\\n    \"https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${TARGETOS:-linux}-${TARGETARCH:-amd64}${TARGETVARIANT}\" \\\n    && chmod +x /usr/local/bin/gomplate\n\n# For Dependabot to detect base image versions\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS alpine\n\nFROM alpine AS user-setup\nRUN addgroup -g 1001 -S dex && adduser -u 1001 -S -G dex -D -H -s /sbin/nologin dex\n\nFROM gcr.io/distroless/static-debian13:nonroot@sha256:e3f945647ffb95b5839c07038d64f9811adf17308b9121d8a2b87b6a22a80a39 AS distroless\n\nFROM $BASE_IMAGE\n\n# Dex connectors, such as GitHub and Google logins require root certificates.\n# Proper installations should manage those certificates, but it's a bad user\n# experience when this doesn't work out of the box.\n#\n# See https://go.dev/src/crypto/x509/root_linux.go for Go root CA bundle locations.\nCOPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt\n\n# Ensure the dex user/group exist before setting ownership or switching to them.\nCOPY --from=user-setup /etc/passwd /etc/passwd\nCOPY --from=user-setup /etc/group /etc/group\n\nCOPY --from=stager --chown=1001:1001 /var/dex /var/dex\nCOPY --from=stager --chown=1001:1001 /etc/dex /etc/dex\n\n# Copy module files for CVE scanning / dependency analysis.\nCOPY --from=builder /usr/local/src/dex/go.mod /usr/local/src/dex/go.sum /usr/local/src/dex/\nCOPY --from=builder /usr/local/src/dex/api/v2/go.mod /usr/local/src/dex/api/v2/go.sum /usr/local/src/dex/api/v2/\n\nCOPY --from=builder /go/bin/dex /usr/local/bin/dex\nCOPY --from=builder /go/bin/docker-entrypoint /usr/local/bin/docker-entrypoint\nCOPY --from=builder /usr/local/src/dex/web /srv/dex/web\n\nCOPY --from=gomplate /usr/local/bin/gomplate /usr/local/bin/gomplate\n\nUSER dex:dex\n\nENTRYPOINT [\"/usr/local/bin/docker-entrypoint\"]\nCMD [\"dex\", \"serve\", \"/etc/dex/config.docker.yaml\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "MAINTAINERS",
    "content": "Joel Speed <Joel.speed@hotmail.co.uk> (@JoelSpeed)\nMaksim Nabokikh <max.nabokih@gmail.com> (@nabokihms)\nMark Sagi-Kazar <mark.sagikazar@gmail.com> (@sagikazarmark)\nNandor Kracser <bonifaido@gmail.com> (@bonifaido)\nRithu John <rithujohn191@gmail.com> (@rithujohn191)\nStephen Augustus <foo@auggie.dev> (@justaugustus)\n"
  },
  {
    "path": "Makefile",
    "content": "export PATH := $(abspath bin/protoc/bin/):$(abspath bin/):${PATH}\n\nOS = $(shell uname | tr A-Z a-z)\n\nuser=$(shell id -u -n)\ngroup=$(shell id -g -n)\n\n$( shell mkdir -p bin )\n\nPROJ      = dex\nORG_PATH  = github.com/dexidp\nREPO_PATH = $(ORG_PATH)/$(PROJ)\nVERSION  ?= $(shell ./scripts/git-version)\n\n\nexport GOBIN=$(PWD)/bin\nLD_FLAGS=\"-w -X main.version=$(VERSION)\"\n\n# Dependency versions\nGOLANGCI_VERSION   = 2.4.0\nGOTESTSUM_VERSION ?= 1.12.0\n\nPROTOC_VERSION             = 29.3\nPROTOC_GEN_GO_VERSION      = 1.36.5\nPROTOC_GEN_GO_GRPC_VERSION = 1.5.1\n\nKIND_VERSION    = 0.22.0\nKIND_NODE_IMAGE = \"kindest/node:v1.25.3@sha256:cd248d1438192f7814fbca8fede13cfe5b9918746dfa12583976158a834fd5c5\"\nKIND_TMP_DIR    = \"$(PWD)/bin/test/dex-kind-kubeconfig\"\n\n\n##@ Build\n\nbuild: bin/dex ## Build Dex binaries.\n\nexamples: bin/grpc-client bin/example-app ## Build example app.\n\n.PHONY: update-gomplate\nupdate-gomplate: ## Check and update gomplate version in Dockerfile.\n\t@./scripts/update-gomplate\n\n.PHONY: release-binary\nrelease-binary: LD_FLAGS = \"-w -X main.version=$(VERSION) -extldflags \\\"-static\\\"\"\nrelease-binary: ## Build release binaries (used to build a final container image).\n\t@go build -o /go/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex\n\t@go build -o /go/bin/docker-entrypoint -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/docker-entrypoint\n\nbin/dex:\n\t@mkdir -p bin/\n\t@go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex\n\nbin/grpc-client:\n\t@mkdir -p bin/\n\t@cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/grpc-client\n\nbin/example-app:\n\t@mkdir -p bin/\n\t@cd examples/ && go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/examples/example-app\n\n\n##@ Generate\n\n.PHONY: generate\ngenerate: generate-proto generate-proto-internal generate-ent go-mod-tidy ## Run all generators.\n\n.PHONY: generate-ent\ngenerate-ent: ## Generate code for database ORM.\n\t@go generate $(REPO_PATH)/storage/ent/\n\n.PHONY: generate-proto\ngenerate-proto: ## Generate the Dex client's protobuf code.\n\t@protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/v2/*.proto\n\t@protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. api/*.proto\n\n.PHONY: generate-proto-internal\ngenerate-proto-internal: ## Generate protobuf code for token encoding.\n\t@protoc --go_out=paths=source_relative:. server/internal/*.proto\n\ngo-mod-tidy: ## Run go mod tidy for all targets.\n\t@go mod tidy\n\t@cd examples/ && go mod tidy\n\t@cd api/v2/ && go mod tidy\n\nbin/protoc:\n\t@mkdir -p bin/protoc\nifeq ($(shell uname | tr A-Z a-z), darwin)\n\tcurl -L https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-osx-x86_64.zip > bin/protoc.zip\nendif\nifeq ($(shell uname | tr A-Z a-z), linux)\n\tcurl -L https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip > bin/protoc.zip\nendif\n\tunzip bin/protoc.zip -d bin/protoc\n\trm bin/protoc.zip\n\nbin/protoc-gen-go:\n\t@mkdir -p bin\n\tcurl -L https://github.com/protocolbuffers/protobuf-go/releases/download/v${PROTOC_GEN_GO_VERSION}/protoc-gen-go.v${PROTOC_GEN_GO_VERSION}.$(shell uname | tr A-Z a-z).amd64.tar.gz | tar -zOxf - protoc-gen-go > ./bin/protoc-gen-go\n\t@chmod +x ./bin/protoc-gen-go\n\nbin/protoc-gen-go-grpc:\n\t@mkdir -p bin\n\tcurl -L https://github.com/grpc/grpc-go/releases/download/cmd/protoc-gen-go-grpc/v${PROTOC_GEN_GO_GRPC_VERSION}/protoc-gen-go-grpc.v${PROTOC_GEN_GO_GRPC_VERSION}.$(shell uname | tr A-Z a-z).amd64.tar.gz | tar -zOxf - ./protoc-gen-go-grpc > ./bin/protoc-gen-go-grpc\n\t@chmod +x ./bin/protoc-gen-go-grpc\n\n##@ Verify\n\nverify: generate ## Verify that all the code was generated and committed to repository.\n\t@git diff --exit-code\n\n.PHONY: verify-proto\nverify-proto: generate-proto ## Verify that the Dex client's protobuf code was generated.\n\t@git diff --exit-code\n\n.PHONY: verify-proto\nverify-proto-internal: generate-proto-internal ## Verify internal protobuf code for token encoding was generated.\n\t@git diff --exit-code\n\n.PHONY: verify-ent\nverify-ent: generate-ent ## Verify code for database ORM was generated.\n\t@git diff --exit-code\n\n.PHONY: verify-go-mod\nverify-go-mod: go-mod-tidy ## Check that go.mod and go.sum formatted according to the changes.\n\t@git diff --exit-code\n\n##@ Test and Lint\n\ndeps: bin/gotestsum bin/golangci-lint bin/protoc bin/protoc-gen-go bin/protoc-gen-go-grpc bin/kind ## Install dev dependencies.\n\n# Detect if we're running in GitHub Actions\nifdef GITHUB_ACTIONS\nGOTESTSUM_FORMAT = github-actions\nelse\nGOTESTSUM_FORMAT = testname\nGOTESTSUM_FORMAT_ICONS = hivis\nendif\n\n.PHONY: test testrace testall\ntest: bin/gotestsum ## Test go code.\nifdef GOTESTSUM_FORMAT_ICONS\n\t@gotestsum --format $(GOTESTSUM_FORMAT) --format-icons $(GOTESTSUM_FORMAT_ICONS) -- -v ./...\nelse\n\t@gotestsum --format $(GOTESTSUM_FORMAT) -- -v ./...\nendif\n\ntestrace: bin/gotestsum ## Test go code and check for possible race conditions.\nifdef GOTESTSUM_FORMAT_ICONS\n\t@gotestsum --format $(GOTESTSUM_FORMAT) --format-icons $(GOTESTSUM_FORMAT_ICONS) -- -v --race ./...\nelse\n\t@gotestsum --format $(GOTESTSUM_FORMAT) -- -v --race ./...\nendif\n\ntestall: testrace ## Run all tests for go code.\n\n.PHONY: lint\nlint: ## Run linter.\n\t@golangci-lint version\n\t@golangci-lint run\n\n.PHONY: fix\nfix: ## Fix lint violations.\n\t@golangci-lint version\n\t@golangci-lint fmt\n\ndocker-compose.override.yaml:\n\tcp docker-compose.override.yaml.dist docker-compose.override.yaml\n\n.PHONY: up\nup: docker-compose.override.yaml ## Launch the development environment.\n\t@ if [ docker-compose.override.yaml -ot docker-compose.override.yaml.dist ]; then diff -u docker-compose.override.yaml docker-compose.override.yaml.dist || (echo \"!!! The distributed docker-compose.override.yaml example changed. Please update your file accordingly (or at least touch it). !!!\" && false); fi\n\tdocker-compose up -d\n\n.PHONY: down\ndown: clear ## Destroy the development environment.\n\tdocker-compose down --volumes --remove-orphans --rmi local\n\n.PHONY: kind-up kind-down kind-tests\nkind-up: ## Create a kind cluster.\n\t@mkdir -p bin/test\n\t@kind create cluster --image ${KIND_NODE_IMAGE} --kubeconfig ${KIND_TMP_DIR} --name dex-tests\n\nkind-tests: export DEX_KUBERNETES_CONFIG_PATH=${KIND_TMP_DIR}\nkind-tests: testall ## Run test on kind cluster (kind cluster must be created).\n\nkind-down: ## Delete the kind cluster.\n\t@kind delete cluster --name dex-tests\n\trm ${KIND_TMP_DIR}\n\nbin/golangci-lint:\n\t@mkdir -p bin\n\tcurl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | BINARY=golangci-lint bash -s -- v${GOLANGCI_VERSION}\n\nbin/gotestsum:\n\t@mkdir -p bin\n\tcurl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_$(shell uname | tr A-Z a-z)_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum\n\t@chmod +x ./bin/gotestsum\n\nbin/kind:\n\t@mkdir -p bin\n\tcurl -L https://github.com/kubernetes-sigs/kind/releases/download/v${KIND_VERSION}/kind-$(shell uname | tr A-Z a-z)-amd64 > ./bin/kind\n\t@chmod +x ./bin/kind\n\n##@ Clean\nclean: ## Delete all builds and downloaded dependencies.\n\t@rm -rf bin/\n\n\nFORMATTING_BEGIN_YELLOW = \\033[0;33m\nFORMATTING_BEGIN_BLUE = \\033[36m\nFORMATTING_END = \\033[0m\n\n.PHONY: help\nhelp:\n\t@printf -- \"${FORMATTING_BEGIN_BLUE}%s${FORMATTING_END}\\n\" \\\n\t\"\" \\\n\t\"       ___             \" \\\n\t\"      / _ \\_____ __    \" \\\n\t\"     / // / -_) \\ /    \" \\\n\t\"    /____/\\__/_\\_\\     \" \\\n\t\"\" \\\n\t\"-----------------------\" \\\n\t\"\"\n\t@awk 'BEGIN {\\\n\t    FS = \":.*##\"; \\\n\t    printf                \"Usage: ${FORMATTING_BEGIN_BLUE}OPTION${FORMATTING_END}=<value> make ${FORMATTING_BEGIN_YELLOW}<target>${FORMATTING_END}\\n\"\\\n\t  } \\\n\t  /^[a-zA-Z0-9_-]+:.*?##/ { printf \"  ${FORMATTING_BEGIN_BLUE}%-46s${FORMATTING_END} %s\\n\", $$1, $$2 } \\\n\t  /^.?.?##~/              { printf \"   %-46s${FORMATTING_BEGIN_YELLOW}%-46s${FORMATTING_END}\\n\", \"\", substr($$1, 6) } \\\n\t  /^##@/                  { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n"
  },
  {
    "path": "README.md",
    "content": "# dex - A federated OpenID Connect provider\n\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/dexidp/dex/ci.yaml?style=flat-square&branch=master)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/dexidp/dex/badge?style=flat-square)](https://api.securityscorecards.dev/projects/github.com/dexidp/dex)\n[![Go Report Card](https://goreportcard.com/badge/github.com/dexidp/dex?style=flat-square)](https://goreportcard.com/report/github.com/dexidp/dex)\n[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod&style=flat-square)](https://gitpod.io/#https://github.com/dexidp/dex)\n\n![logo](docs/logos/dex-horizontal-color.png)\n\nDex is an identity service that uses [OpenID Connect][openid-connect] to drive authentication for other apps.\n\nDex acts as a portal to other identity providers through [\"connectors.\"](#connectors) This lets dex defer authentication to LDAP servers, SAML providers, or established identity providers like GitHub, Google, and Active Directory. Clients write their authentication logic once to talk to dex, then dex handles the protocols for a given backend.\n\n## ID Tokens\n\nID Tokens are an OAuth2 extension introduced by OpenID Connect and dex's primary feature. ID Tokens are [JSON Web Tokens][jwt-io] (JWTs) signed by dex and returned as part of the OAuth2 response that attests to the end user's identity. An example JWT might look like:\n\n```\neyJhbGciOiJSUzI1NiIsImtpZCI6IjlkNDQ3NDFmNzczYjkzOGNmNjVkZDMyNjY4NWI4NjE4MGMzMjRkOTkifQ.eyJpc3MiOiJodHRwOi8vMTI3LjAuMC4xOjU1NTYvZGV4Iiwic3ViIjoiQ2djeU16UXlOelE1RWdabmFYUm9kV0kiLCJhdWQiOiJleGFtcGxlLWFwcCIsImV4cCI6MTQ5Mjg4MjA0MiwiaWF0IjoxNDkyNzk1NjQyLCJhdF9oYXNoIjoiYmk5NmdPWFpTaHZsV1l0YWw5RXFpdyIsImVtYWlsIjoiZXJpYy5jaGlhbmdAY29yZW9zLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJncm91cHMiOlsiYWRtaW5zIiwiZGV2ZWxvcGVycyJdLCJuYW1lIjoiRXJpYyBDaGlhbmcifQ.OhROPq_0eP-zsQRjg87KZ4wGkjiQGnTi5QuG877AdJDb3R2ZCOk2Vkf5SdP8cPyb3VMqL32G4hLDayniiv8f1_ZXAde0sKrayfQ10XAXFgZl_P1yilkLdknxn6nbhDRVllpWcB12ki9vmAxklAr0B1C4kr5nI3-BZLrFcUR5sQbxwJj4oW1OuG6jJCNGHXGNTBTNEaM28eD-9nhfBeuBTzzO7BKwPsojjj4C9ogU4JQhGvm_l4yfVi0boSx8c0FX3JsiB0yLa1ZdJVWVl9m90XmbWRSD85pNDQHcWZP9hR6CMgbvGkZsgjG32qeRwUL_eNkNowSBNWLrGNPoON1gMg\n```\n\nID Tokens contains standard claims assert which client app logged the user in, when the token expires, and the identity of the user.\n\n```json\n{\n  \"iss\": \"http://127.0.0.1:5556/dex\",\n  \"sub\": \"CgcyMzQyNzQ5EgZnaXRodWI\",\n  \"aud\": \"example-app\",\n  \"exp\": 1492882042,\n  \"iat\": 1492795642,\n  \"at_hash\": \"bi96gOXZShvlWYtal9Eqiw\",\n  \"email\": \"jane.doe@coreos.com\",\n  \"email_verified\": true,\n  \"groups\": [\n    \"admins\",\n    \"developers\"\n  ],\n  \"name\": \"Jane Doe\"\n}\n```\n\nBecause these tokens are signed by dex and [contain standard-based claims][standard-claims] other services can consume them as service-to-service credentials. Systems that can already consume OpenID Connect ID Tokens issued by dex include:\n\n* [Kubernetes][kubernetes]\n* [AWS STS][aws-sts]\n\nFor details on how to request or validate an ID Token, see [_\"Writing apps that use dex\"_][using-dex].\n\n## Kubernetes and Dex\n\nDex runs natively on top of any Kubernetes cluster using Custom Resource Definitions and can drive API server authentication through the OpenID Connect plugin. Clients, such as the [`kubernetes-dashboard`](https://github.com/kubernetes/dashboard) and `kubectl`, can act on behalf of users who can login to the cluster through any identity provider dex supports.\n\n* More docs for running dex as a Kubernetes authenticator can be found [here](https://dexidp.io/docs/guides/kubernetes/).\n* You can find more about companies and projects which use dex, [here](./ADOPTERS.md).\n\n## Connectors\n\nWhen a user logs in through dex, the user's identity is usually stored in another user-management system: a LDAP directory, a GitHub org, etc. Dex acts as a shim between a client app and the upstream identity provider. The client only needs to understand OpenID Connect to query dex, while dex implements an array of protocols for querying other user-management systems.\n\n![](docs/img/dex-flow.png)\n\nA \"connector\" is a strategy used by dex for authenticating a user against another identity provider. Dex implements connectors that target specific platforms such as GitHub, LinkedIn, and Microsoft as well as established protocols like LDAP and SAML.\n\nDepending on the connectors limitations in protocols can prevent dex from issuing [refresh tokens][scopes] or returning [group membership][scopes] claims. For example, because SAML doesn't provide a non-interactive way to refresh assertions, if a user logs in through the SAML connector dex won't issue a refresh token to its client. Refresh token support is required for clients that require offline access, such as `kubectl`.\n\nDex implements the following connectors:\n\n| Name | supports refresh tokens | supports groups claim | supports preferred_username claim | status | notes |\n| ---- | ----------------------- | --------------------- | --------------------------------- | ------ | ----- |\n| [LDAP](https://dexidp.io/docs/connectors/ldap/) | yes | yes | yes | stable | |\n| [GitHub](https://dexidp.io/docs/connectors/github/) | yes | yes | yes | stable | |\n| [SAML 2.0](https://dexidp.io/docs/connectors/saml/) | no | yes | no | stable | WARNING: Unmaintained and likely vulnerable to auth bypasses ([#1884](https://github.com/dexidp/dex/discussions/1884)) |\n| [GitLab](https://dexidp.io/docs/connectors/gitlab/) | yes | yes | yes | beta | |\n| [OpenID Connect](https://dexidp.io/docs/connectors/oidc/) | yes | yes | yes | beta | Includes Salesforce, Azure, etc. |\n| [OAuth 2.0](https://dexidp.io/docs/connectors/oauth/) | no | yes | yes | alpha | |\n| [Google](https://dexidp.io/docs/connectors/google/) | yes | yes | yes | alpha | |\n| [LinkedIn](https://dexidp.io/docs/connectors/linkedin/) | yes | no | no | beta | |\n| [Microsoft](https://dexidp.io/docs/connectors/microsoft/) | yes | yes | no | beta | |\n| [AuthProxy](https://dexidp.io/docs/connectors/authproxy/) | no | yes | no | alpha | Authentication proxies such as Apache2 mod_auth, etc. |\n| [Bitbucket Cloud](https://dexidp.io/docs/connectors/bitbucketcloud/) | yes | yes | no | alpha | |\n| [OpenShift](https://dexidp.io/docs/connectors/openshift/) | yes | yes | no | alpha | |\n| [Atlassian Crowd](https://dexidp.io/docs/connectors/atlassian-crowd/) | yes | yes | yes * | beta | preferred_username claim must be configured through config |\n| [Gitea](https://dexidp.io/docs/connectors/gitea/) | yes | no | yes | beta | |\n| [OpenStack Keystone](https://dexidp.io/docs/connectors/keystone/) | yes | yes | no | alpha | |\n\nStable, beta, and alpha are defined as:\n\n* Stable: well tested, in active use, and will not change in backward incompatible ways.\n* Beta: tested and unlikely to change in backward incompatible ways.\n* Alpha: may be untested by core maintainers and is subject to change in backward incompatible ways.\n\nAll changes or deprecations of connector features will be announced in the [release notes][release-notes].\n\n## Documentation\n\n* [Getting started](https://dexidp.io/docs/getting-started/)\n* [Intro to OpenID Connect](https://dexidp.io/docs/openid-connect/)\n* [Writing apps that use dex][using-dex]\n* [What's new in v2](https://dexidp.io/docs/archive/v2/)\n* [Custom scopes, claims, and client features](https://dexidp.io/docs/custom-scopes-claims-clients/)\n* [Storage options](https://dexidp.io/docs/storage/)\n* [gRPC API](https://dexidp.io/docs/api/)\n* [Using Kubernetes with dex](https://dexidp.io/docs/kubernetes/)\n* Client libraries\n  * [Go][go-oidc]\n\n## Reporting a vulnerability\n\nPlease see our [security policy](.github/SECURITY.md) for details about reporting vulnerabilities.\n\n## Getting help\n\n- For feature requests and bugs, file an [issue](https://github.com/dexidp/dex/issues).\n- For general discussion about both using and developing Dex:\n    - join the [#dexidp](https://cloud-native.slack.com/messages/dexidp) on the CNCF Slack\n    - open a new [discussion](https://github.com/dexidp/dex/discussions)\n    - join the [dex-dev](https://groups.google.com/forum/#!forum/dex-dev) mailing list\n\n[openid-connect]: https://openid.net/connect/\n[standard-claims]: https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims\n[scopes]: https://dexidp.io/docs/custom-scopes-claims-clients/#scopes\n[using-dex]: https://dexidp.io/docs/using-dex/\n[jwt-io]: https://jwt.io/\n[kubernetes]: https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens\n[aws-sts]: https://docs.aws.amazon.com/STS/latest/APIReference/Welcome.html\n[go-oidc]: https://github.com/coreos/go-oidc\n[issue-1065]: https://github.com/dexidp/dex/issues/1065\n[release-notes]: https://github.com/dexidp/dex/releases\n\n## Development\n\nWhen all coding and testing is done, please run the test suite:\n\n```shell\nmake testall\n```\n\nFor the best developer experience, install [Nix](https://builtwithnix.org/) and [direnv](https://direnv.net/).\n\nAlternatively, install Go and Docker manually or using a package manager. Install the rest of the dependencies by running `make deps`.\n\nFor release process, please read the [release documentation](https://dexidp.io/docs/development/releases/).\n\n## License\n\nThe project is licensed under the [Apache License, Version 2.0](LICENSE).\n"
  },
  {
    "path": "api/api.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v5.29.3\n// source: api/api.proto\n\npackage api\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Client represents an OAuth2 client.\ntype Client struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tId                string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tSecret            string                 `protobuf:\"bytes,2,opt,name=secret,proto3\" json:\"secret,omitempty\"`\n\tRedirectUris      []string               `protobuf:\"bytes,3,rep,name=redirect_uris,json=redirectUris,proto3\" json:\"redirect_uris,omitempty\"`\n\tTrustedPeers      []string               `protobuf:\"bytes,4,rep,name=trusted_peers,json=trustedPeers,proto3\" json:\"trusted_peers,omitempty\"`\n\tPublic            bool                   `protobuf:\"varint,5,opt,name=public,proto3\" json:\"public,omitempty\"`\n\tName              string                 `protobuf:\"bytes,6,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tLogoUrl           string                 `protobuf:\"bytes,7,opt,name=logo_url,json=logoUrl,proto3\" json:\"logo_url,omitempty\"`\n\tAllowedConnectors []string               `protobuf:\"bytes,8,rep,name=allowed_connectors,json=allowedConnectors,proto3\" json:\"allowed_connectors,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *Client) Reset() {\n\t*x = Client{}\n\tmi := &file_api_api_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Client) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Client) ProtoMessage() {}\n\nfunc (x *Client) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Client.ProtoReflect.Descriptor instead.\nfunc (*Client) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Client) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetSecret() string {\n\tif x != nil {\n\t\treturn x.Secret\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetRedirectUris() []string {\n\tif x != nil {\n\t\treturn x.RedirectUris\n\t}\n\treturn nil\n}\n\nfunc (x *Client) GetTrustedPeers() []string {\n\tif x != nil {\n\t\treturn x.TrustedPeers\n\t}\n\treturn nil\n}\n\nfunc (x *Client) GetPublic() bool {\n\tif x != nil {\n\t\treturn x.Public\n\t}\n\treturn false\n}\n\nfunc (x *Client) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetLogoUrl() string {\n\tif x != nil {\n\t\treturn x.LogoUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetAllowedConnectors() []string {\n\tif x != nil {\n\t\treturn x.AllowedConnectors\n\t}\n\treturn nil\n}\n\n// CreateClientReq is a request to make a client.\ntype CreateClientReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tClient        *Client                `protobuf:\"bytes,1,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateClientReq) Reset() {\n\t*x = CreateClientReq{}\n\tmi := &file_api_api_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateClientReq) ProtoMessage() {}\n\nfunc (x *CreateClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateClientReq.ProtoReflect.Descriptor instead.\nfunc (*CreateClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *CreateClientReq) GetClient() *Client {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn nil\n}\n\n// CreateClientResp returns the response from creating a client.\ntype CreateClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAlreadyExists bool                   `protobuf:\"varint,1,opt,name=already_exists,json=alreadyExists,proto3\" json:\"already_exists,omitempty\"`\n\tClient        *Client                `protobuf:\"bytes,2,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateClientResp) Reset() {\n\t*x = CreateClientResp{}\n\tmi := &file_api_api_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateClientResp) ProtoMessage() {}\n\nfunc (x *CreateClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateClientResp.ProtoReflect.Descriptor instead.\nfunc (*CreateClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *CreateClientResp) GetAlreadyExists() bool {\n\tif x != nil {\n\t\treturn x.AlreadyExists\n\t}\n\treturn false\n}\n\nfunc (x *CreateClientResp) GetClient() *Client {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn nil\n}\n\n// DeleteClientReq is a request to delete a client.\ntype DeleteClientReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The ID of the client.\n\tId            string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeleteClientReq) Reset() {\n\t*x = DeleteClientReq{}\n\tmi := &file_api_api_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeleteClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteClientReq) ProtoMessage() {}\n\nfunc (x *DeleteClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteClientReq.ProtoReflect.Descriptor instead.\nfunc (*DeleteClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *DeleteClientReq) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\n// DeleteClientResp determines if the client is deleted successfully.\ntype DeleteClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeleteClientResp) Reset() {\n\t*x = DeleteClientResp{}\n\tmi := &file_api_api_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeleteClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteClientResp) ProtoMessage() {}\n\nfunc (x *DeleteClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteClientResp.ProtoReflect.Descriptor instead.\nfunc (*DeleteClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *DeleteClientResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// UpdateClientReq is a request to update an existing client.\ntype UpdateClientReq struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tId                string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tRedirectUris      []string               `protobuf:\"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3\" json:\"redirect_uris,omitempty\"`\n\tTrustedPeers      []string               `protobuf:\"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3\" json:\"trusted_peers,omitempty\"`\n\tName              string                 `protobuf:\"bytes,4,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tLogoUrl           string                 `protobuf:\"bytes,5,opt,name=logo_url,json=logoUrl,proto3\" json:\"logo_url,omitempty\"`\n\tAllowedConnectors []string               `protobuf:\"bytes,6,rep,name=allowed_connectors,json=allowedConnectors,proto3\" json:\"allowed_connectors,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *UpdateClientReq) Reset() {\n\t*x = UpdateClientReq{}\n\tmi := &file_api_api_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdateClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateClientReq) ProtoMessage() {}\n\nfunc (x *UpdateClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateClientReq.ProtoReflect.Descriptor instead.\nfunc (*UpdateClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *UpdateClientReq) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateClientReq) GetRedirectUris() []string {\n\tif x != nil {\n\t\treturn x.RedirectUris\n\t}\n\treturn nil\n}\n\nfunc (x *UpdateClientReq) GetTrustedPeers() []string {\n\tif x != nil {\n\t\treturn x.TrustedPeers\n\t}\n\treturn nil\n}\n\nfunc (x *UpdateClientReq) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateClientReq) GetLogoUrl() string {\n\tif x != nil {\n\t\treturn x.LogoUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateClientReq) GetAllowedConnectors() []string {\n\tif x != nil {\n\t\treturn x.AllowedConnectors\n\t}\n\treturn nil\n}\n\n// UpdateClientResp returns the response from updating a client.\ntype UpdateClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdateClientResp) Reset() {\n\t*x = UpdateClientResp{}\n\tmi := &file_api_api_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdateClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateClientResp) ProtoMessage() {}\n\nfunc (x *UpdateClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateClientResp.ProtoReflect.Descriptor instead.\nfunc (*UpdateClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *UpdateClientResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// Password is an email for password mapping managed by the storage.\ntype Password struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\tEmail string                 `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\t// Currently we do not accept plain text passwords. Could be an option in the future.\n\tHash          []byte `protobuf:\"bytes,2,opt,name=hash,proto3\" json:\"hash,omitempty\"`\n\tUsername      string `protobuf:\"bytes,3,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tUserId        string `protobuf:\"bytes,4,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Password) Reset() {\n\t*x = Password{}\n\tmi := &file_api_api_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Password) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Password) ProtoMessage() {}\n\nfunc (x *Password) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Password.ProtoReflect.Descriptor instead.\nfunc (*Password) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *Password) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Password) GetHash() []byte {\n\tif x != nil {\n\t\treturn x.Hash\n\t}\n\treturn nil\n}\n\nfunc (x *Password) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Password) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\n// CreatePasswordReq is a request to make a password.\ntype CreatePasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPassword      *Password              `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreatePasswordReq) Reset() {\n\t*x = CreatePasswordReq{}\n\tmi := &file_api_api_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreatePasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreatePasswordReq) ProtoMessage() {}\n\nfunc (x *CreatePasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreatePasswordReq.ProtoReflect.Descriptor instead.\nfunc (*CreatePasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *CreatePasswordReq) GetPassword() *Password {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn nil\n}\n\n// CreatePasswordResp returns the response from creating a password.\ntype CreatePasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAlreadyExists bool                   `protobuf:\"varint,1,opt,name=already_exists,json=alreadyExists,proto3\" json:\"already_exists,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreatePasswordResp) Reset() {\n\t*x = CreatePasswordResp{}\n\tmi := &file_api_api_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreatePasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreatePasswordResp) ProtoMessage() {}\n\nfunc (x *CreatePasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreatePasswordResp.ProtoReflect.Descriptor instead.\nfunc (*CreatePasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *CreatePasswordResp) GetAlreadyExists() bool {\n\tif x != nil {\n\t\treturn x.AlreadyExists\n\t}\n\treturn false\n}\n\n// UpdatePasswordReq is a request to modify an existing password.\ntype UpdatePasswordReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The email used to lookup the password. This field cannot be modified\n\tEmail         string `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tNewHash       []byte `protobuf:\"bytes,2,opt,name=new_hash,json=newHash,proto3\" json:\"new_hash,omitempty\"`\n\tNewUsername   string `protobuf:\"bytes,3,opt,name=new_username,json=newUsername,proto3\" json:\"new_username,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdatePasswordReq) Reset() {\n\t*x = UpdatePasswordReq{}\n\tmi := &file_api_api_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdatePasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdatePasswordReq) ProtoMessage() {}\n\nfunc (x *UpdatePasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdatePasswordReq.ProtoReflect.Descriptor instead.\nfunc (*UpdatePasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{10}\n}\n\nfunc (x *UpdatePasswordReq) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdatePasswordReq) GetNewHash() []byte {\n\tif x != nil {\n\t\treturn x.NewHash\n\t}\n\treturn nil\n}\n\nfunc (x *UpdatePasswordReq) GetNewUsername() string {\n\tif x != nil {\n\t\treturn x.NewUsername\n\t}\n\treturn \"\"\n}\n\n// UpdatePasswordResp returns the response from modifying an existing password.\ntype UpdatePasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdatePasswordResp) Reset() {\n\t*x = UpdatePasswordResp{}\n\tmi := &file_api_api_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdatePasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdatePasswordResp) ProtoMessage() {}\n\nfunc (x *UpdatePasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdatePasswordResp.ProtoReflect.Descriptor instead.\nfunc (*UpdatePasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *UpdatePasswordResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// DeletePasswordReq is a request to delete a password.\ntype DeletePasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEmail         string                 `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeletePasswordReq) Reset() {\n\t*x = DeletePasswordReq{}\n\tmi := &file_api_api_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeletePasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeletePasswordReq) ProtoMessage() {}\n\nfunc (x *DeletePasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeletePasswordReq.ProtoReflect.Descriptor instead.\nfunc (*DeletePasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *DeletePasswordReq) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\n// DeletePasswordResp returns the response from deleting a password.\ntype DeletePasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeletePasswordResp) Reset() {\n\t*x = DeletePasswordResp{}\n\tmi := &file_api_api_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeletePasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeletePasswordResp) ProtoMessage() {}\n\nfunc (x *DeletePasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeletePasswordResp.ProtoReflect.Descriptor instead.\nfunc (*DeletePasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *DeletePasswordResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// ListPasswordReq is a request to enumerate passwords.\ntype ListPasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListPasswordReq) Reset() {\n\t*x = ListPasswordReq{}\n\tmi := &file_api_api_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPasswordReq) ProtoMessage() {}\n\nfunc (x *ListPasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListPasswordReq.ProtoReflect.Descriptor instead.\nfunc (*ListPasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{14}\n}\n\n// ListPasswordResp returns a list of passwords.\ntype ListPasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPasswords     []*Password            `protobuf:\"bytes,1,rep,name=passwords,proto3\" json:\"passwords,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListPasswordResp) Reset() {\n\t*x = ListPasswordResp{}\n\tmi := &file_api_api_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPasswordResp) ProtoMessage() {}\n\nfunc (x *ListPasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListPasswordResp.ProtoReflect.Descriptor instead.\nfunc (*ListPasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *ListPasswordResp) GetPasswords() []*Password {\n\tif x != nil {\n\t\treturn x.Passwords\n\t}\n\treturn nil\n}\n\n// VersionReq is a request to fetch version info.\ntype VersionReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VersionReq) Reset() {\n\t*x = VersionReq{}\n\tmi := &file_api_api_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionReq) ProtoMessage() {}\n\nfunc (x *VersionReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionReq.ProtoReflect.Descriptor instead.\nfunc (*VersionReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{16}\n}\n\n// VersionResp holds the version info of components.\ntype VersionResp struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Semantic version of the server.\n\tServer string `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\t// Numeric version of the API. It increases every time a new call is added to the API.\n\t// Clients should use this info to determine if the server supports specific features.\n\tApi           int32 `protobuf:\"varint,2,opt,name=api,proto3\" json:\"api,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VersionResp) Reset() {\n\t*x = VersionResp{}\n\tmi := &file_api_api_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionResp) ProtoMessage() {}\n\nfunc (x *VersionResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionResp.ProtoReflect.Descriptor instead.\nfunc (*VersionResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *VersionResp) GetServer() string {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn \"\"\n}\n\nfunc (x *VersionResp) GetApi() int32 {\n\tif x != nil {\n\t\treturn x.Api\n\t}\n\treturn 0\n}\n\n// RefreshTokenRef contains the metadata for a refresh token that is managed by the storage.\ntype RefreshTokenRef struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// ID of the refresh token.\n\tId            string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tClientId      string `protobuf:\"bytes,2,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tCreatedAt     int64  `protobuf:\"varint,5,opt,name=created_at,json=createdAt,proto3\" json:\"created_at,omitempty\"`\n\tLastUsed      int64  `protobuf:\"varint,6,opt,name=last_used,json=lastUsed,proto3\" json:\"last_used,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RefreshTokenRef) Reset() {\n\t*x = RefreshTokenRef{}\n\tmi := &file_api_api_proto_msgTypes[18]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RefreshTokenRef) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RefreshTokenRef) ProtoMessage() {}\n\nfunc (x *RefreshTokenRef) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[18]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RefreshTokenRef.ProtoReflect.Descriptor instead.\nfunc (*RefreshTokenRef) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *RefreshTokenRef) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *RefreshTokenRef) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\nfunc (x *RefreshTokenRef) GetCreatedAt() int64 {\n\tif x != nil {\n\t\treturn x.CreatedAt\n\t}\n\treturn 0\n}\n\nfunc (x *RefreshTokenRef) GetLastUsed() int64 {\n\tif x != nil {\n\t\treturn x.LastUsed\n\t}\n\treturn 0\n}\n\n// ListRefreshReq is a request to enumerate the refresh tokens of a user.\ntype ListRefreshReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The \"sub\" claim returned in the ID Token.\n\tUserId        string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRefreshReq) Reset() {\n\t*x = ListRefreshReq{}\n\tmi := &file_api_api_proto_msgTypes[19]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRefreshReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRefreshReq) ProtoMessage() {}\n\nfunc (x *ListRefreshReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[19]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRefreshReq.ProtoReflect.Descriptor instead.\nfunc (*ListRefreshReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{19}\n}\n\nfunc (x *ListRefreshReq) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\n// ListRefreshResp returns a list of refresh tokens for a user.\ntype ListRefreshResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRefreshTokens []*RefreshTokenRef     `protobuf:\"bytes,1,rep,name=refresh_tokens,json=refreshTokens,proto3\" json:\"refresh_tokens,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRefreshResp) Reset() {\n\t*x = ListRefreshResp{}\n\tmi := &file_api_api_proto_msgTypes[20]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRefreshResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRefreshResp) ProtoMessage() {}\n\nfunc (x *ListRefreshResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[20]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRefreshResp.ProtoReflect.Descriptor instead.\nfunc (*ListRefreshResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *ListRefreshResp) GetRefreshTokens() []*RefreshTokenRef {\n\tif x != nil {\n\t\treturn x.RefreshTokens\n\t}\n\treturn nil\n}\n\n// RevokeRefreshReq is a request to revoke the refresh token of the user-client pair.\ntype RevokeRefreshReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The \"sub\" claim returned in the ID Token.\n\tUserId        string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tClientId      string `protobuf:\"bytes,2,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RevokeRefreshReq) Reset() {\n\t*x = RevokeRefreshReq{}\n\tmi := &file_api_api_proto_msgTypes[21]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RevokeRefreshReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RevokeRefreshReq) ProtoMessage() {}\n\nfunc (x *RevokeRefreshReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[21]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RevokeRefreshReq.ProtoReflect.Descriptor instead.\nfunc (*RevokeRefreshReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *RevokeRefreshReq) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *RevokeRefreshReq) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\n// RevokeRefreshResp determines if the refresh token is revoked successfully.\ntype RevokeRefreshResp struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Set to true is refresh token was not found and token could not be revoked.\n\tNotFound      bool `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RevokeRefreshResp) Reset() {\n\t*x = RevokeRefreshResp{}\n\tmi := &file_api_api_proto_msgTypes[22]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RevokeRefreshResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RevokeRefreshResp) ProtoMessage() {}\n\nfunc (x *RevokeRefreshResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[22]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RevokeRefreshResp.ProtoReflect.Descriptor instead.\nfunc (*RevokeRefreshResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *RevokeRefreshResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\ntype VerifyPasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEmail         string                 `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tPassword      string                 `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VerifyPasswordReq) Reset() {\n\t*x = VerifyPasswordReq{}\n\tmi := &file_api_api_proto_msgTypes[23]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VerifyPasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifyPasswordReq) ProtoMessage() {}\n\nfunc (x *VerifyPasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[23]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifyPasswordReq.ProtoReflect.Descriptor instead.\nfunc (*VerifyPasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *VerifyPasswordReq) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *VerifyPasswordReq) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\ntype VerifyPasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tVerified      bool                   `protobuf:\"varint,1,opt,name=verified,proto3\" json:\"verified,omitempty\"`\n\tNotFound      bool                   `protobuf:\"varint,2,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VerifyPasswordResp) Reset() {\n\t*x = VerifyPasswordResp{}\n\tmi := &file_api_api_proto_msgTypes[24]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VerifyPasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifyPasswordResp) ProtoMessage() {}\n\nfunc (x *VerifyPasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_api_proto_msgTypes[24]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifyPasswordResp.ProtoReflect.Descriptor instead.\nfunc (*VerifyPasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_api_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *VerifyPasswordResp) GetVerified() bool {\n\tif x != nil {\n\t\treturn x.Verified\n\t}\n\treturn false\n}\n\nfunc (x *VerifyPasswordResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\nvar File_api_api_proto protoreflect.FileDescriptor\n\nvar file_api_api_proto_rawDesc = string([]byte{\n\t0x0a, 0x0d, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,\n\t0x03, 0x61, 0x70, 0x69, 0x22, 0xf0, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12,\n\t0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,\n\t0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72,\n\t0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,\n\t0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d,\n\t0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20,\n\t0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72,\n\t0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a,\n\t0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f,\n\t0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x08,\n\t0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e,\n\t0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x36, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74,\n\t0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c,\n\t0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69,\n\t0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22,\n\t0x5e, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65,\n\t0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72,\n\t0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c,\n\t0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69,\n\t0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22,\n\t0x21, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,\n\t0x69, 0x64, 0x22, 0x2f, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65,\n\t0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f,\n\t0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f,\n\t0x75, 0x6e, 0x64, 0x22, 0xc9, 0x01, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c,\n\t0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72,\n\t0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c,\n\t0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d,\n\t0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20,\n\t0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72,\n\t0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72,\n\t0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c,\n\t0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e,\n\t0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c,\n\t0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22,\n\t0x2f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64,\n\t0x22, 0x69, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x14, 0x0a, 0x05,\n\t0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61,\n\t0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c,\n\t0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61,\n\t0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x3e, 0x0a, 0x11, 0x43,\n\t0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71,\n\t0x12, 0x29, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,\n\t0x64, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3b, 0x0a, 0x12, 0x43,\n\t0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73,\n\t0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69,\n\t0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61,\n\t0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x67, 0x0a, 0x11, 0x55, 0x70, 0x64, 0x61,\n\t0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a,\n\t0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d,\n\t0x61, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x48, 0x61, 0x73, 0x68, 0x12, 0x21,\n\t0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d,\n\t0x65, 0x22, 0x31, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,\n\t0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66,\n\t0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46,\n\t0x6f, 0x75, 0x6e, 0x64, 0x22, 0x29, 0x0a, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61,\n\t0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61,\n\t0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22,\n\t0x31, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,\n\t0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75,\n\t0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75,\n\t0x6e, 0x64, 0x22, 0x11, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x52, 0x65, 0x71, 0x22, 0x3f, 0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73,\n\t0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2b, 0x0a, 0x09, 0x70, 0x61, 0x73,\n\t0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x09, 0x70, 0x61, 0x73,\n\t0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x0c, 0x0a, 0x0a, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f,\n\t0x6e, 0x52, 0x65, 0x71, 0x22, 0x37, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52,\n\t0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x61,\n\t0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x70, 0x69, 0x22, 0x7a, 0x0a,\n\t0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66,\n\t0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64,\n\t0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20,\n\t0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x1d, 0x0a,\n\t0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28,\n\t0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1b, 0x0a, 0x09,\n\t0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52,\n\t0x08, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x64, 0x22, 0x29, 0x0a, 0x0e, 0x4c, 0x69, 0x73,\n\t0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75,\n\t0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73,\n\t0x65, 0x72, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72,\n\t0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x0e, 0x72, 0x65, 0x66, 0x72, 0x65,\n\t0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,\n\t0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b,\n\t0x65, 0x6e, 0x52, 0x65, 0x66, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f,\n\t0x6b, 0x65, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x10, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65,\n\t0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72,\n\t0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49,\n\t0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x22, 0x30,\n\t0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52,\n\t0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64,\n\t0x22, 0x45, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x70,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x4d, 0x0a, 0x12, 0x56, 0x65, 0x72, 0x69, 0x66,\n\t0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1a, 0x0a,\n\t0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,\n\t0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74,\n\t0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f,\n\t0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x32, 0xc7, 0x05, 0x0a, 0x03, 0x44, 0x65, 0x78, 0x12, 0x3d,\n\t0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14,\n\t0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e,\n\t0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,\n\t0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a,\n\t0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e,\n\t0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,\n\t0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,\n\t0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c,\n\t0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43,\n\t0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x43,\n\t0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e,\n\t0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61,\n\t0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00,\n\t0x12, 0x43, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69,\n\t0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52,\n\t0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65,\n\t0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a,\n\t0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73,\n\t0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3e, 0x0a, 0x0d, 0x4c, 0x69,\n\t0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x14, 0x2e, 0x61, 0x70,\n\t0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65,\n\t0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73,\n\t0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65,\n\t0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e,\n\t0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3a, 0x0a,\n\t0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x13, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65,\n\t0x71, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72,\n\t0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x76,\n\t0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69,\n\t0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65,\n\t0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65,\n\t0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x56,\n\t0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e,\n\t0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69,\n\t0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00,\n\t0x42, 0x2f, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x6f, 0x73, 0x2e, 0x64,\n\t0x65, 0x78, 0x2e, 0x61, 0x70, 0x69, 0x5a, 0x19, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,\n\t0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x61, 0x70,\n\t0x69, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_api_api_proto_rawDescOnce sync.Once\n\tfile_api_api_proto_rawDescData []byte\n)\n\nfunc file_api_api_proto_rawDescGZIP() []byte {\n\tfile_api_api_proto_rawDescOnce.Do(func() {\n\t\tfile_api_api_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_api_proto_rawDesc), len(file_api_api_proto_rawDesc)))\n\t})\n\treturn file_api_api_proto_rawDescData\n}\n\nvar file_api_api_proto_msgTypes = make([]protoimpl.MessageInfo, 25)\nvar file_api_api_proto_goTypes = []any{\n\t(*Client)(nil),             // 0: api.Client\n\t(*CreateClientReq)(nil),    // 1: api.CreateClientReq\n\t(*CreateClientResp)(nil),   // 2: api.CreateClientResp\n\t(*DeleteClientReq)(nil),    // 3: api.DeleteClientReq\n\t(*DeleteClientResp)(nil),   // 4: api.DeleteClientResp\n\t(*UpdateClientReq)(nil),    // 5: api.UpdateClientReq\n\t(*UpdateClientResp)(nil),   // 6: api.UpdateClientResp\n\t(*Password)(nil),           // 7: api.Password\n\t(*CreatePasswordReq)(nil),  // 8: api.CreatePasswordReq\n\t(*CreatePasswordResp)(nil), // 9: api.CreatePasswordResp\n\t(*UpdatePasswordReq)(nil),  // 10: api.UpdatePasswordReq\n\t(*UpdatePasswordResp)(nil), // 11: api.UpdatePasswordResp\n\t(*DeletePasswordReq)(nil),  // 12: api.DeletePasswordReq\n\t(*DeletePasswordResp)(nil), // 13: api.DeletePasswordResp\n\t(*ListPasswordReq)(nil),    // 14: api.ListPasswordReq\n\t(*ListPasswordResp)(nil),   // 15: api.ListPasswordResp\n\t(*VersionReq)(nil),         // 16: api.VersionReq\n\t(*VersionResp)(nil),        // 17: api.VersionResp\n\t(*RefreshTokenRef)(nil),    // 18: api.RefreshTokenRef\n\t(*ListRefreshReq)(nil),     // 19: api.ListRefreshReq\n\t(*ListRefreshResp)(nil),    // 20: api.ListRefreshResp\n\t(*RevokeRefreshReq)(nil),   // 21: api.RevokeRefreshReq\n\t(*RevokeRefreshResp)(nil),  // 22: api.RevokeRefreshResp\n\t(*VerifyPasswordReq)(nil),  // 23: api.VerifyPasswordReq\n\t(*VerifyPasswordResp)(nil), // 24: api.VerifyPasswordResp\n}\nvar file_api_api_proto_depIdxs = []int32{\n\t0,  // 0: api.CreateClientReq.client:type_name -> api.Client\n\t0,  // 1: api.CreateClientResp.client:type_name -> api.Client\n\t7,  // 2: api.CreatePasswordReq.password:type_name -> api.Password\n\t7,  // 3: api.ListPasswordResp.passwords:type_name -> api.Password\n\t18, // 4: api.ListRefreshResp.refresh_tokens:type_name -> api.RefreshTokenRef\n\t1,  // 5: api.Dex.CreateClient:input_type -> api.CreateClientReq\n\t5,  // 6: api.Dex.UpdateClient:input_type -> api.UpdateClientReq\n\t3,  // 7: api.Dex.DeleteClient:input_type -> api.DeleteClientReq\n\t8,  // 8: api.Dex.CreatePassword:input_type -> api.CreatePasswordReq\n\t10, // 9: api.Dex.UpdatePassword:input_type -> api.UpdatePasswordReq\n\t12, // 10: api.Dex.DeletePassword:input_type -> api.DeletePasswordReq\n\t14, // 11: api.Dex.ListPasswords:input_type -> api.ListPasswordReq\n\t16, // 12: api.Dex.GetVersion:input_type -> api.VersionReq\n\t19, // 13: api.Dex.ListRefresh:input_type -> api.ListRefreshReq\n\t21, // 14: api.Dex.RevokeRefresh:input_type -> api.RevokeRefreshReq\n\t23, // 15: api.Dex.VerifyPassword:input_type -> api.VerifyPasswordReq\n\t2,  // 16: api.Dex.CreateClient:output_type -> api.CreateClientResp\n\t6,  // 17: api.Dex.UpdateClient:output_type -> api.UpdateClientResp\n\t4,  // 18: api.Dex.DeleteClient:output_type -> api.DeleteClientResp\n\t9,  // 19: api.Dex.CreatePassword:output_type -> api.CreatePasswordResp\n\t11, // 20: api.Dex.UpdatePassword:output_type -> api.UpdatePasswordResp\n\t13, // 21: api.Dex.DeletePassword:output_type -> api.DeletePasswordResp\n\t15, // 22: api.Dex.ListPasswords:output_type -> api.ListPasswordResp\n\t17, // 23: api.Dex.GetVersion:output_type -> api.VersionResp\n\t20, // 24: api.Dex.ListRefresh:output_type -> api.ListRefreshResp\n\t22, // 25: api.Dex.RevokeRefresh:output_type -> api.RevokeRefreshResp\n\t24, // 26: api.Dex.VerifyPassword:output_type -> api.VerifyPasswordResp\n\t16, // [16:27] is the sub-list for method output_type\n\t5,  // [5:16] is the sub-list for method input_type\n\t5,  // [5:5] is the sub-list for extension type_name\n\t5,  // [5:5] is the sub-list for extension extendee\n\t0,  // [0:5] is the sub-list for field type_name\n}\n\nfunc init() { file_api_api_proto_init() }\nfunc file_api_api_proto_init() {\n\tif File_api_api_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_api_api_proto_rawDesc), len(file_api_api_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   25,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_api_api_proto_goTypes,\n\t\tDependencyIndexes: file_api_api_proto_depIdxs,\n\t\tMessageInfos:      file_api_api_proto_msgTypes,\n\t}.Build()\n\tFile_api_api_proto = out.File\n\tfile_api_api_proto_goTypes = nil\n\tfile_api_api_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "api/api.proto",
    "content": "syntax = \"proto3\";\n\npackage api;\n\noption java_package = \"com.coreos.dex.api\";\noption go_package = \"github.com/dexidp/dex/api\";\n\n// Client represents an OAuth2 client.\nmessage Client {\n  string id = 1;\n  string secret = 2;\n  repeated string redirect_uris = 3;\n  repeated string trusted_peers = 4;\n  bool public = 5;\n  string name = 6;\n  string logo_url = 7;\n  repeated string allowed_connectors = 8;\n}\n\n// CreateClientReq is a request to make a client.\nmessage CreateClientReq {\n  Client client = 1;\n}\n\n// CreateClientResp returns the response from creating a client.\nmessage CreateClientResp {\n  bool already_exists = 1;\n  Client client = 2;\n}\n\n// DeleteClientReq is a request to delete a client.\nmessage DeleteClientReq {\n  // The ID of the client.\n  string id = 1;\n}\n\n// DeleteClientResp determines if the client is deleted successfully.\nmessage DeleteClientResp {\n  bool not_found = 1;\n}\n\n// UpdateClientReq is a request to update an existing client.\nmessage UpdateClientReq {\n    string id = 1;\n    repeated string redirect_uris = 2;\n    repeated string trusted_peers = 3;\n    string name = 4;\n    string logo_url = 5;\n    repeated string allowed_connectors = 6;\n}\n\n// UpdateClientResp returns the response from updating a client.\nmessage UpdateClientResp {\n    bool not_found = 1;\n}\n\n// TODO(ericchiang): expand this.\n\n// Password is an email for password mapping managed by the storage.\nmessage Password {\n  string email = 1;\n\n  // Currently we do not accept plain text passwords. Could be an option in the future.\n  bytes hash = 2;\n  string username = 3;\n  string user_id = 4;\n}\n\n// CreatePasswordReq is a request to make a password.\nmessage CreatePasswordReq {\n  Password password = 1;\n}\n\n// CreatePasswordResp returns the response from creating a password.\nmessage CreatePasswordResp {\n  bool already_exists = 1;\n}\n\n// UpdatePasswordReq is a request to modify an existing password.\nmessage UpdatePasswordReq {\n  // The email used to lookup the password. This field cannot be modified\n  string email = 1;\n  bytes new_hash = 2;\n  string new_username = 3;\n}\n\n// UpdatePasswordResp returns the response from modifying an existing password.\nmessage UpdatePasswordResp {\n  bool not_found = 1;\n}\n\n// DeletePasswordReq is a request to delete a password.\nmessage DeletePasswordReq {\n  string email = 1;\n}\n\n// DeletePasswordResp returns the response from deleting a password.\nmessage DeletePasswordResp {\n  bool not_found = 1;\n}\n\n// ListPasswordReq is a request to enumerate passwords.\nmessage ListPasswordReq {}\n\n// ListPasswordResp returns a list of passwords.\nmessage ListPasswordResp {\n  repeated Password passwords = 1;\n}\n\n// VersionReq is a request to fetch version info.\nmessage VersionReq {}\n\n// VersionResp holds the version info of components.\nmessage VersionResp {\n  // Semantic version of the server.\n  string server = 1;\n  // Numeric version of the API. It increases every time a new call is added to the API.\n  // Clients should use this info to determine if the server supports specific features.\n  int32 api = 2;\n}\n\n// RefreshTokenRef contains the metadata for a refresh token that is managed by the storage.\nmessage RefreshTokenRef {\n  // ID of the refresh token.\n  string id = 1;\n  string client_id = 2;\n  int64 created_at = 5;\n  int64 last_used = 6;\n}\n\n// ListRefreshReq is a request to enumerate the refresh tokens of a user.\nmessage ListRefreshReq {\n  // The \"sub\" claim returned in the ID Token.\n  string user_id = 1;\n}\n\n// ListRefreshResp returns a list of refresh tokens for a user.\nmessage ListRefreshResp {\n  repeated RefreshTokenRef refresh_tokens = 1;\n}\n\n// RevokeRefreshReq is a request to revoke the refresh token of the user-client pair.\nmessage RevokeRefreshReq {\n  // The \"sub\" claim returned in the ID Token.\n  string user_id = 1;\n  string client_id = 2;\n}\n\n// RevokeRefreshResp determines if the refresh token is revoked successfully.\nmessage RevokeRefreshResp {\n  // Set to true is refresh token was not found and token could not be revoked.\n  bool not_found = 1;\n}\n\nmessage VerifyPasswordReq {\n  string email = 1;\n  string password = 2;\n}\n\nmessage VerifyPasswordResp {\n  bool verified = 1;\n  bool not_found = 2;\n}\n\n// Dex represents the dex gRPC service.\nservice Dex {\n  // CreateClient creates a client.\n  rpc CreateClient(CreateClientReq) returns (CreateClientResp) {};\n  // UpdateClient updates an existing client\n  rpc UpdateClient(UpdateClientReq) returns (UpdateClientResp) {};\n  // DeleteClient deletes the provided client.\n  rpc DeleteClient(DeleteClientReq) returns (DeleteClientResp) {};\n  // CreatePassword creates a password.\n  rpc CreatePassword(CreatePasswordReq) returns (CreatePasswordResp) {};\n  // UpdatePassword modifies existing password.\n  rpc UpdatePassword(UpdatePasswordReq) returns (UpdatePasswordResp) {};\n  // DeletePassword deletes the password.\n  rpc DeletePassword(DeletePasswordReq) returns (DeletePasswordResp) {};\n  // ListPassword lists all password entries.\n  rpc ListPasswords(ListPasswordReq) returns (ListPasswordResp) {};\n  // GetVersion returns version information of the server.\n  rpc GetVersion(VersionReq) returns (VersionResp) {};\n  // ListRefresh lists all the refresh token entries for a particular user.\n  rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {};\n  // RevokeRefresh revokes the refresh token for the provided user-client pair.\n  //\n  // Note that each user-client pair can have only one refresh token at a time.\n  rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {};\n  // VerifyPassword returns whether a password matches a hash for a specific email or not.\n  rpc VerifyPassword(VerifyPasswordReq) returns (VerifyPasswordResp) {};\n}\n"
  },
  {
    "path": "api/api_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v5.29.3\n// source: api/api.proto\n\npackage api\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tDex_CreateClient_FullMethodName   = \"/api.Dex/CreateClient\"\n\tDex_UpdateClient_FullMethodName   = \"/api.Dex/UpdateClient\"\n\tDex_DeleteClient_FullMethodName   = \"/api.Dex/DeleteClient\"\n\tDex_CreatePassword_FullMethodName = \"/api.Dex/CreatePassword\"\n\tDex_UpdatePassword_FullMethodName = \"/api.Dex/UpdatePassword\"\n\tDex_DeletePassword_FullMethodName = \"/api.Dex/DeletePassword\"\n\tDex_ListPasswords_FullMethodName  = \"/api.Dex/ListPasswords\"\n\tDex_GetVersion_FullMethodName     = \"/api.Dex/GetVersion\"\n\tDex_ListRefresh_FullMethodName    = \"/api.Dex/ListRefresh\"\n\tDex_RevokeRefresh_FullMethodName  = \"/api.Dex/RevokeRefresh\"\n\tDex_VerifyPassword_FullMethodName = \"/api.Dex/VerifyPassword\"\n)\n\n// DexClient is the client API for Dex service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\n//\n// Dex represents the dex gRPC service.\ntype DexClient interface {\n\t// CreateClient creates a client.\n\tCreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error)\n\t// UpdateClient updates an existing client\n\tUpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error)\n\t// DeleteClient deletes the provided client.\n\tDeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error)\n\t// CreatePassword creates a password.\n\tCreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error)\n\t// UpdatePassword modifies existing password.\n\tUpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error)\n\t// DeletePassword deletes the password.\n\tDeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error)\n\t// ListPassword lists all password entries.\n\tListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error)\n\t// GetVersion returns version information of the server.\n\tGetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error)\n\t// ListRefresh lists all the refresh token entries for a particular user.\n\tListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error)\n\t// RevokeRefresh revokes the refresh token for the provided user-client pair.\n\t//\n\t// Note that each user-client pair can have only one refresh token at a time.\n\tRevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error)\n\t// VerifyPassword returns whether a password matches a hash for a specific email or not.\n\tVerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error)\n}\n\ntype dexClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewDexClient(cc grpc.ClientConnInterface) DexClient {\n\treturn &dexClient{cc}\n}\n\nfunc (c *dexClient) CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(CreateClientResp)\n\terr := c.cc.Invoke(ctx, Dex_CreateClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(UpdateClientResp)\n\terr := c.cc.Invoke(ctx, Dex_UpdateClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DeleteClientResp)\n\terr := c.cc.Invoke(ctx, Dex_DeleteClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(CreatePasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_CreatePassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(UpdatePasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_UpdatePassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DeletePasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_DeletePassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListPasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_ListPasswords_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(VersionResp)\n\terr := c.cc.Invoke(ctx, Dex_GetVersion_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListRefreshResp)\n\terr := c.cc.Invoke(ctx, Dex_ListRefresh_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(RevokeRefreshResp)\n\terr := c.cc.Invoke(ctx, Dex_RevokeRefresh_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(VerifyPasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_VerifyPassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// DexServer is the server API for Dex service.\n// All implementations must embed UnimplementedDexServer\n// for forward compatibility.\n//\n// Dex represents the dex gRPC service.\ntype DexServer interface {\n\t// CreateClient creates a client.\n\tCreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error)\n\t// UpdateClient updates an existing client\n\tUpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error)\n\t// DeleteClient deletes the provided client.\n\tDeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error)\n\t// CreatePassword creates a password.\n\tCreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error)\n\t// UpdatePassword modifies existing password.\n\tUpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error)\n\t// DeletePassword deletes the password.\n\tDeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error)\n\t// ListPassword lists all password entries.\n\tListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error)\n\t// GetVersion returns version information of the server.\n\tGetVersion(context.Context, *VersionReq) (*VersionResp, error)\n\t// ListRefresh lists all the refresh token entries for a particular user.\n\tListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error)\n\t// RevokeRefresh revokes the refresh token for the provided user-client pair.\n\t//\n\t// Note that each user-client pair can have only one refresh token at a time.\n\tRevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error)\n\t// VerifyPassword returns whether a password matches a hash for a specific email or not.\n\tVerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error)\n\tmustEmbedUnimplementedDexServer()\n}\n\n// UnimplementedDexServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedDexServer struct{}\n\nfunc (UnimplementedDexServer) CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateClient not implemented\")\n}\nfunc (UnimplementedDexServer) UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateClient not implemented\")\n}\nfunc (UnimplementedDexServer) DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeleteClient not implemented\")\n}\nfunc (UnimplementedDexServer) CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreatePassword not implemented\")\n}\nfunc (UnimplementedDexServer) UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdatePassword not implemented\")\n}\nfunc (UnimplementedDexServer) DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeletePassword not implemented\")\n}\nfunc (UnimplementedDexServer) ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListPasswords not implemented\")\n}\nfunc (UnimplementedDexServer) GetVersion(context.Context, *VersionReq) (*VersionResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetVersion not implemented\")\n}\nfunc (UnimplementedDexServer) ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListRefresh not implemented\")\n}\nfunc (UnimplementedDexServer) RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method RevokeRefresh not implemented\")\n}\nfunc (UnimplementedDexServer) VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifyPassword not implemented\")\n}\nfunc (UnimplementedDexServer) mustEmbedUnimplementedDexServer() {}\nfunc (UnimplementedDexServer) testEmbeddedByValue()             {}\n\n// UnsafeDexServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to DexServer will\n// result in compilation errors.\ntype UnsafeDexServer interface {\n\tmustEmbedUnimplementedDexServer()\n}\n\nfunc RegisterDexServer(s grpc.ServiceRegistrar, srv DexServer) {\n\t// If the following call pancis, it indicates UnimplementedDexServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Dex_ServiceDesc, srv)\n}\n\nfunc _Dex_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).CreateClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_CreateClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).CreateClient(ctx, req.(*CreateClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdateClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).UpdateClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_UpdateClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).UpdateClient(ctx, req.(*UpdateClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).DeleteClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_DeleteClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).DeleteClient(ctx, req.(*DeleteClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreatePasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).CreatePassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_CreatePassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdatePasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).UpdatePassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_UpdatePassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeletePasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).DeletePassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_DeletePassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_ListPasswords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListPasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).ListPasswords(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_ListPasswords_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).ListPasswords(ctx, req.(*ListPasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VersionReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).GetVersion(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_GetVersion_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).GetVersion(ctx, req.(*VersionReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_ListRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRefreshReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).ListRefresh(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_ListRefresh_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).ListRefresh(ctx, req.(*ListRefreshReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RevokeRefreshReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).RevokeRefresh(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_RevokeRefresh_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).RevokeRefresh(ctx, req.(*RevokeRefreshReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_VerifyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifyPasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).VerifyPassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_VerifyPassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).VerifyPassword(ctx, req.(*VerifyPasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// Dex_ServiceDesc is the grpc.ServiceDesc for Dex service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Dex_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"api.Dex\",\n\tHandlerType: (*DexServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"CreateClient\",\n\t\t\tHandler:    _Dex_CreateClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateClient\",\n\t\t\tHandler:    _Dex_UpdateClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeleteClient\",\n\t\t\tHandler:    _Dex_DeleteClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreatePassword\",\n\t\t\tHandler:    _Dex_CreatePassword_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdatePassword\",\n\t\t\tHandler:    _Dex_UpdatePassword_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeletePassword\",\n\t\t\tHandler:    _Dex_DeletePassword_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListPasswords\",\n\t\t\tHandler:    _Dex_ListPasswords_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetVersion\",\n\t\t\tHandler:    _Dex_GetVersion_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListRefresh\",\n\t\t\tHandler:    _Dex_ListRefresh_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RevokeRefresh\",\n\t\t\tHandler:    _Dex_RevokeRefresh_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifyPassword\",\n\t\t\tHandler:    _Dex_VerifyPassword_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"api/api.proto\",\n}\n"
  },
  {
    "path": "api/v2/api.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v5.29.3\n// source: api/v2/api.proto\n\npackage api\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// Client represents an OAuth2 client.\ntype Client struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tId                string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tSecret            string                 `protobuf:\"bytes,2,opt,name=secret,proto3\" json:\"secret,omitempty\"`\n\tRedirectUris      []string               `protobuf:\"bytes,3,rep,name=redirect_uris,json=redirectUris,proto3\" json:\"redirect_uris,omitempty\"`\n\tTrustedPeers      []string               `protobuf:\"bytes,4,rep,name=trusted_peers,json=trustedPeers,proto3\" json:\"trusted_peers,omitempty\"`\n\tPublic            bool                   `protobuf:\"varint,5,opt,name=public,proto3\" json:\"public,omitempty\"`\n\tName              string                 `protobuf:\"bytes,6,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tLogoUrl           string                 `protobuf:\"bytes,7,opt,name=logo_url,json=logoUrl,proto3\" json:\"logo_url,omitempty\"`\n\tAllowedConnectors []string               `protobuf:\"bytes,8,rep,name=allowed_connectors,json=allowedConnectors,proto3\" json:\"allowed_connectors,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *Client) Reset() {\n\t*x = Client{}\n\tmi := &file_api_v2_api_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Client) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Client) ProtoMessage() {}\n\nfunc (x *Client) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Client.ProtoReflect.Descriptor instead.\nfunc (*Client) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *Client) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetSecret() string {\n\tif x != nil {\n\t\treturn x.Secret\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetRedirectUris() []string {\n\tif x != nil {\n\t\treturn x.RedirectUris\n\t}\n\treturn nil\n}\n\nfunc (x *Client) GetTrustedPeers() []string {\n\tif x != nil {\n\t\treturn x.TrustedPeers\n\t}\n\treturn nil\n}\n\nfunc (x *Client) GetPublic() bool {\n\tif x != nil {\n\t\treturn x.Public\n\t}\n\treturn false\n}\n\nfunc (x *Client) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetLogoUrl() string {\n\tif x != nil {\n\t\treturn x.LogoUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *Client) GetAllowedConnectors() []string {\n\tif x != nil {\n\t\treturn x.AllowedConnectors\n\t}\n\treturn nil\n}\n\n// ClientInfo represents an OAuth2 client without sensitive information.\ntype ClientInfo struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tId                string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tRedirectUris      []string               `protobuf:\"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3\" json:\"redirect_uris,omitempty\"`\n\tTrustedPeers      []string               `protobuf:\"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3\" json:\"trusted_peers,omitempty\"`\n\tPublic            bool                   `protobuf:\"varint,4,opt,name=public,proto3\" json:\"public,omitempty\"`\n\tName              string                 `protobuf:\"bytes,5,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tLogoUrl           string                 `protobuf:\"bytes,6,opt,name=logo_url,json=logoUrl,proto3\" json:\"logo_url,omitempty\"`\n\tAllowedConnectors []string               `protobuf:\"bytes,7,rep,name=allowed_connectors,json=allowedConnectors,proto3\" json:\"allowed_connectors,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *ClientInfo) Reset() {\n\t*x = ClientInfo{}\n\tmi := &file_api_v2_api_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ClientInfo) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ClientInfo) ProtoMessage() {}\n\nfunc (x *ClientInfo) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ClientInfo.ProtoReflect.Descriptor instead.\nfunc (*ClientInfo) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *ClientInfo) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientInfo) GetRedirectUris() []string {\n\tif x != nil {\n\t\treturn x.RedirectUris\n\t}\n\treturn nil\n}\n\nfunc (x *ClientInfo) GetTrustedPeers() []string {\n\tif x != nil {\n\t\treturn x.TrustedPeers\n\t}\n\treturn nil\n}\n\nfunc (x *ClientInfo) GetPublic() bool {\n\tif x != nil {\n\t\treturn x.Public\n\t}\n\treturn false\n}\n\nfunc (x *ClientInfo) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientInfo) GetLogoUrl() string {\n\tif x != nil {\n\t\treturn x.LogoUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *ClientInfo) GetAllowedConnectors() []string {\n\tif x != nil {\n\t\treturn x.AllowedConnectors\n\t}\n\treturn nil\n}\n\n// GetClientReq is a request to retrieve client details.\ntype GetClientReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The ID of the client.\n\tId            string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetClientReq) Reset() {\n\t*x = GetClientReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[2]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetClientReq) ProtoMessage() {}\n\nfunc (x *GetClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[2]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetClientReq.ProtoReflect.Descriptor instead.\nfunc (*GetClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{2}\n}\n\nfunc (x *GetClientReq) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\n// GetClientResp returns the client details.\ntype GetClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tClient        *Client                `protobuf:\"bytes,1,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GetClientResp) Reset() {\n\t*x = GetClientResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[3]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GetClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GetClientResp) ProtoMessage() {}\n\nfunc (x *GetClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[3]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GetClientResp.ProtoReflect.Descriptor instead.\nfunc (*GetClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{3}\n}\n\nfunc (x *GetClientResp) GetClient() *Client {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn nil\n}\n\n// CreateClientReq is a request to make a client.\ntype CreateClientReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tClient        *Client                `protobuf:\"bytes,1,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateClientReq) Reset() {\n\t*x = CreateClientReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[4]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateClientReq) ProtoMessage() {}\n\nfunc (x *CreateClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[4]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateClientReq.ProtoReflect.Descriptor instead.\nfunc (*CreateClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{4}\n}\n\nfunc (x *CreateClientReq) GetClient() *Client {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn nil\n}\n\n// CreateClientResp returns the response from creating a client.\ntype CreateClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAlreadyExists bool                   `protobuf:\"varint,1,opt,name=already_exists,json=alreadyExists,proto3\" json:\"already_exists,omitempty\"`\n\tClient        *Client                `protobuf:\"bytes,2,opt,name=client,proto3\" json:\"client,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateClientResp) Reset() {\n\t*x = CreateClientResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[5]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateClientResp) ProtoMessage() {}\n\nfunc (x *CreateClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[5]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateClientResp.ProtoReflect.Descriptor instead.\nfunc (*CreateClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{5}\n}\n\nfunc (x *CreateClientResp) GetAlreadyExists() bool {\n\tif x != nil {\n\t\treturn x.AlreadyExists\n\t}\n\treturn false\n}\n\nfunc (x *CreateClientResp) GetClient() *Client {\n\tif x != nil {\n\t\treturn x.Client\n\t}\n\treturn nil\n}\n\n// DeleteClientReq is a request to delete a client.\ntype DeleteClientReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The ID of the client.\n\tId            string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeleteClientReq) Reset() {\n\t*x = DeleteClientReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[6]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeleteClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteClientReq) ProtoMessage() {}\n\nfunc (x *DeleteClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[6]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteClientReq.ProtoReflect.Descriptor instead.\nfunc (*DeleteClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{6}\n}\n\nfunc (x *DeleteClientReq) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\n// DeleteClientResp determines if the client is deleted successfully.\ntype DeleteClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeleteClientResp) Reset() {\n\t*x = DeleteClientResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[7]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeleteClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteClientResp) ProtoMessage() {}\n\nfunc (x *DeleteClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[7]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteClientResp.ProtoReflect.Descriptor instead.\nfunc (*DeleteClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{7}\n}\n\nfunc (x *DeleteClientResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// UpdateClientReq is a request to update an existing client.\ntype UpdateClientReq struct {\n\tstate             protoimpl.MessageState `protogen:\"open.v1\"`\n\tId                string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tRedirectUris      []string               `protobuf:\"bytes,2,rep,name=redirect_uris,json=redirectUris,proto3\" json:\"redirect_uris,omitempty\"`\n\tTrustedPeers      []string               `protobuf:\"bytes,3,rep,name=trusted_peers,json=trustedPeers,proto3\" json:\"trusted_peers,omitempty\"`\n\tName              string                 `protobuf:\"bytes,4,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tLogoUrl           string                 `protobuf:\"bytes,5,opt,name=logo_url,json=logoUrl,proto3\" json:\"logo_url,omitempty\"`\n\tAllowedConnectors []string               `protobuf:\"bytes,6,rep,name=allowed_connectors,json=allowedConnectors,proto3\" json:\"allowed_connectors,omitempty\"`\n\tunknownFields     protoimpl.UnknownFields\n\tsizeCache         protoimpl.SizeCache\n}\n\nfunc (x *UpdateClientReq) Reset() {\n\t*x = UpdateClientReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[8]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdateClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateClientReq) ProtoMessage() {}\n\nfunc (x *UpdateClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[8]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateClientReq.ProtoReflect.Descriptor instead.\nfunc (*UpdateClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{8}\n}\n\nfunc (x *UpdateClientReq) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateClientReq) GetRedirectUris() []string {\n\tif x != nil {\n\t\treturn x.RedirectUris\n\t}\n\treturn nil\n}\n\nfunc (x *UpdateClientReq) GetTrustedPeers() []string {\n\tif x != nil {\n\t\treturn x.TrustedPeers\n\t}\n\treturn nil\n}\n\nfunc (x *UpdateClientReq) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateClientReq) GetLogoUrl() string {\n\tif x != nil {\n\t\treturn x.LogoUrl\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateClientReq) GetAllowedConnectors() []string {\n\tif x != nil {\n\t\treturn x.AllowedConnectors\n\t}\n\treturn nil\n}\n\n// UpdateClientResp returns the response from updating a client.\ntype UpdateClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdateClientResp) Reset() {\n\t*x = UpdateClientResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[9]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdateClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateClientResp) ProtoMessage() {}\n\nfunc (x *UpdateClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[9]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateClientResp.ProtoReflect.Descriptor instead.\nfunc (*UpdateClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{9}\n}\n\nfunc (x *UpdateClientResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// ListClientReq is a request to enumerate clients.\ntype ListClientReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListClientReq) Reset() {\n\t*x = ListClientReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[10]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListClientReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListClientReq) ProtoMessage() {}\n\nfunc (x *ListClientReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[10]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListClientReq.ProtoReflect.Descriptor instead.\nfunc (*ListClientReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{10}\n}\n\n// ListClientResp returns a list of clients.\ntype ListClientResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tClients       []*ClientInfo          `protobuf:\"bytes,1,rep,name=clients,proto3\" json:\"clients,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListClientResp) Reset() {\n\t*x = ListClientResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[11]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListClientResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListClientResp) ProtoMessage() {}\n\nfunc (x *ListClientResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[11]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListClientResp.ProtoReflect.Descriptor instead.\nfunc (*ListClientResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{11}\n}\n\nfunc (x *ListClientResp) GetClients() []*ClientInfo {\n\tif x != nil {\n\t\treturn x.Clients\n\t}\n\treturn nil\n}\n\n// Password is an email for password mapping managed by the storage.\ntype Password struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\tEmail string                 `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\t// Currently we do not accept plain text passwords. Could be an option in the future.\n\tHash          []byte `protobuf:\"bytes,2,opt,name=hash,proto3\" json:\"hash,omitempty\"`\n\tUsername      string `protobuf:\"bytes,3,opt,name=username,proto3\" json:\"username,omitempty\"`\n\tUserId        string `protobuf:\"bytes,4,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Password) Reset() {\n\t*x = Password{}\n\tmi := &file_api_v2_api_proto_msgTypes[12]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Password) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Password) ProtoMessage() {}\n\nfunc (x *Password) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[12]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Password.ProtoReflect.Descriptor instead.\nfunc (*Password) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{12}\n}\n\nfunc (x *Password) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *Password) GetHash() []byte {\n\tif x != nil {\n\t\treturn x.Hash\n\t}\n\treturn nil\n}\n\nfunc (x *Password) GetUsername() string {\n\tif x != nil {\n\t\treturn x.Username\n\t}\n\treturn \"\"\n}\n\nfunc (x *Password) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\n// CreatePasswordReq is a request to make a password.\ntype CreatePasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPassword      *Password              `protobuf:\"bytes,1,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreatePasswordReq) Reset() {\n\t*x = CreatePasswordReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[13]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreatePasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreatePasswordReq) ProtoMessage() {}\n\nfunc (x *CreatePasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[13]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreatePasswordReq.ProtoReflect.Descriptor instead.\nfunc (*CreatePasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{13}\n}\n\nfunc (x *CreatePasswordReq) GetPassword() *Password {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn nil\n}\n\n// CreatePasswordResp returns the response from creating a password.\ntype CreatePasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAlreadyExists bool                   `protobuf:\"varint,1,opt,name=already_exists,json=alreadyExists,proto3\" json:\"already_exists,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreatePasswordResp) Reset() {\n\t*x = CreatePasswordResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[14]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreatePasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreatePasswordResp) ProtoMessage() {}\n\nfunc (x *CreatePasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[14]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreatePasswordResp.ProtoReflect.Descriptor instead.\nfunc (*CreatePasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{14}\n}\n\nfunc (x *CreatePasswordResp) GetAlreadyExists() bool {\n\tif x != nil {\n\t\treturn x.AlreadyExists\n\t}\n\treturn false\n}\n\n// UpdatePasswordReq is a request to modify an existing password.\ntype UpdatePasswordReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The email used to lookup the password. This field cannot be modified\n\tEmail         string `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tNewHash       []byte `protobuf:\"bytes,2,opt,name=new_hash,json=newHash,proto3\" json:\"new_hash,omitempty\"`\n\tNewUsername   string `protobuf:\"bytes,3,opt,name=new_username,json=newUsername,proto3\" json:\"new_username,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdatePasswordReq) Reset() {\n\t*x = UpdatePasswordReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[15]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdatePasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdatePasswordReq) ProtoMessage() {}\n\nfunc (x *UpdatePasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[15]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdatePasswordReq.ProtoReflect.Descriptor instead.\nfunc (*UpdatePasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{15}\n}\n\nfunc (x *UpdatePasswordReq) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdatePasswordReq) GetNewHash() []byte {\n\tif x != nil {\n\t\treturn x.NewHash\n\t}\n\treturn nil\n}\n\nfunc (x *UpdatePasswordReq) GetNewUsername() string {\n\tif x != nil {\n\t\treturn x.NewUsername\n\t}\n\treturn \"\"\n}\n\n// UpdatePasswordResp returns the response from modifying an existing password.\ntype UpdatePasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdatePasswordResp) Reset() {\n\t*x = UpdatePasswordResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[16]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdatePasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdatePasswordResp) ProtoMessage() {}\n\nfunc (x *UpdatePasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[16]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdatePasswordResp.ProtoReflect.Descriptor instead.\nfunc (*UpdatePasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{16}\n}\n\nfunc (x *UpdatePasswordResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// DeletePasswordReq is a request to delete a password.\ntype DeletePasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEmail         string                 `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeletePasswordReq) Reset() {\n\t*x = DeletePasswordReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[17]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeletePasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeletePasswordReq) ProtoMessage() {}\n\nfunc (x *DeletePasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[17]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeletePasswordReq.ProtoReflect.Descriptor instead.\nfunc (*DeletePasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{17}\n}\n\nfunc (x *DeletePasswordReq) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\n// DeletePasswordResp returns the response from deleting a password.\ntype DeletePasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeletePasswordResp) Reset() {\n\t*x = DeletePasswordResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[18]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeletePasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeletePasswordResp) ProtoMessage() {}\n\nfunc (x *DeletePasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[18]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeletePasswordResp.ProtoReflect.Descriptor instead.\nfunc (*DeletePasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{18}\n}\n\nfunc (x *DeletePasswordResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// ListPasswordReq is a request to enumerate passwords.\ntype ListPasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListPasswordReq) Reset() {\n\t*x = ListPasswordReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[19]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPasswordReq) ProtoMessage() {}\n\nfunc (x *ListPasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[19]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListPasswordReq.ProtoReflect.Descriptor instead.\nfunc (*ListPasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{19}\n}\n\n// ListPasswordResp returns a list of passwords.\ntype ListPasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tPasswords     []*Password            `protobuf:\"bytes,1,rep,name=passwords,proto3\" json:\"passwords,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListPasswordResp) Reset() {\n\t*x = ListPasswordResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[20]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListPasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListPasswordResp) ProtoMessage() {}\n\nfunc (x *ListPasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[20]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListPasswordResp.ProtoReflect.Descriptor instead.\nfunc (*ListPasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{20}\n}\n\nfunc (x *ListPasswordResp) GetPasswords() []*Password {\n\tif x != nil {\n\t\treturn x.Passwords\n\t}\n\treturn nil\n}\n\n// Connector is a strategy used by Dex for authenticating a user against another identity provider\ntype Connector struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tId            string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tType          string                 `protobuf:\"bytes,2,opt,name=type,proto3\" json:\"type,omitempty\"`\n\tName          string                 `protobuf:\"bytes,3,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tConfig        []byte                 `protobuf:\"bytes,4,opt,name=config,proto3\" json:\"config,omitempty\"`\n\tGrantTypes    []string               `protobuf:\"bytes,5,rep,name=grant_types,json=grantTypes,proto3\" json:\"grant_types,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *Connector) Reset() {\n\t*x = Connector{}\n\tmi := &file_api_v2_api_proto_msgTypes[21]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *Connector) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*Connector) ProtoMessage() {}\n\nfunc (x *Connector) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[21]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use Connector.ProtoReflect.Descriptor instead.\nfunc (*Connector) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{21}\n}\n\nfunc (x *Connector) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connector) GetType() string {\n\tif x != nil {\n\t\treturn x.Type\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connector) GetName() string {\n\tif x != nil {\n\t\treturn x.Name\n\t}\n\treturn \"\"\n}\n\nfunc (x *Connector) GetConfig() []byte {\n\tif x != nil {\n\t\treturn x.Config\n\t}\n\treturn nil\n}\n\nfunc (x *Connector) GetGrantTypes() []string {\n\tif x != nil {\n\t\treturn x.GrantTypes\n\t}\n\treturn nil\n}\n\n// CreateConnectorReq is a request to make a connector.\ntype CreateConnectorReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tConnector     *Connector             `protobuf:\"bytes,1,opt,name=connector,proto3\" json:\"connector,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateConnectorReq) Reset() {\n\t*x = CreateConnectorReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[22]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateConnectorReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateConnectorReq) ProtoMessage() {}\n\nfunc (x *CreateConnectorReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[22]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateConnectorReq.ProtoReflect.Descriptor instead.\nfunc (*CreateConnectorReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{22}\n}\n\nfunc (x *CreateConnectorReq) GetConnector() *Connector {\n\tif x != nil {\n\t\treturn x.Connector\n\t}\n\treturn nil\n}\n\n// CreateConnectorResp returns the response from creating a connector.\ntype CreateConnectorResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tAlreadyExists bool                   `protobuf:\"varint,1,opt,name=already_exists,json=alreadyExists,proto3\" json:\"already_exists,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *CreateConnectorResp) Reset() {\n\t*x = CreateConnectorResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[23]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *CreateConnectorResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*CreateConnectorResp) ProtoMessage() {}\n\nfunc (x *CreateConnectorResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[23]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use CreateConnectorResp.ProtoReflect.Descriptor instead.\nfunc (*CreateConnectorResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{23}\n}\n\nfunc (x *CreateConnectorResp) GetAlreadyExists() bool {\n\tif x != nil {\n\t\treturn x.AlreadyExists\n\t}\n\treturn false\n}\n\n// GrantTypes wraps a list of grant types to distinguish between\n// \"not specified\" (no update) and \"empty list\" (unrestricted).\ntype GrantTypes struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tGrantTypes    []string               `protobuf:\"bytes,1,rep,name=grant_types,json=grantTypes,proto3\" json:\"grant_types,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *GrantTypes) Reset() {\n\t*x = GrantTypes{}\n\tmi := &file_api_v2_api_proto_msgTypes[24]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *GrantTypes) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*GrantTypes) ProtoMessage() {}\n\nfunc (x *GrantTypes) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[24]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use GrantTypes.ProtoReflect.Descriptor instead.\nfunc (*GrantTypes) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{24}\n}\n\nfunc (x *GrantTypes) GetGrantTypes() []string {\n\tif x != nil {\n\t\treturn x.GrantTypes\n\t}\n\treturn nil\n}\n\n// UpdateConnectorReq is a request to modify an existing connector.\ntype UpdateConnectorReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The id used to lookup the connector. This field cannot be modified\n\tId        string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tNewType   string `protobuf:\"bytes,2,opt,name=new_type,json=newType,proto3\" json:\"new_type,omitempty\"`\n\tNewName   string `protobuf:\"bytes,3,opt,name=new_name,json=newName,proto3\" json:\"new_name,omitempty\"`\n\tNewConfig []byte `protobuf:\"bytes,4,opt,name=new_config,json=newConfig,proto3\" json:\"new_config,omitempty\"`\n\t// If set, updates the connector's allowed grant types.\n\t// An empty grant_types list means unrestricted (all grant types allowed).\n\t// If not set (null), grant types are not modified.\n\tNewGrantTypes *GrantTypes `protobuf:\"bytes,5,opt,name=new_grant_types,json=newGrantTypes,proto3\" json:\"new_grant_types,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdateConnectorReq) Reset() {\n\t*x = UpdateConnectorReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[25]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdateConnectorReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateConnectorReq) ProtoMessage() {}\n\nfunc (x *UpdateConnectorReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[25]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateConnectorReq.ProtoReflect.Descriptor instead.\nfunc (*UpdateConnectorReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{25}\n}\n\nfunc (x *UpdateConnectorReq) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateConnectorReq) GetNewType() string {\n\tif x != nil {\n\t\treturn x.NewType\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateConnectorReq) GetNewName() string {\n\tif x != nil {\n\t\treturn x.NewName\n\t}\n\treturn \"\"\n}\n\nfunc (x *UpdateConnectorReq) GetNewConfig() []byte {\n\tif x != nil {\n\t\treturn x.NewConfig\n\t}\n\treturn nil\n}\n\nfunc (x *UpdateConnectorReq) GetNewGrantTypes() *GrantTypes {\n\tif x != nil {\n\t\treturn x.NewGrantTypes\n\t}\n\treturn nil\n}\n\n// UpdateConnectorResp returns the response from modifying an existing connector.\ntype UpdateConnectorResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *UpdateConnectorResp) Reset() {\n\t*x = UpdateConnectorResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[26]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *UpdateConnectorResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*UpdateConnectorResp) ProtoMessage() {}\n\nfunc (x *UpdateConnectorResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[26]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use UpdateConnectorResp.ProtoReflect.Descriptor instead.\nfunc (*UpdateConnectorResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{26}\n}\n\nfunc (x *UpdateConnectorResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// DeleteConnectorReq is a request to delete a connector.\ntype DeleteConnectorReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tId            string                 `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeleteConnectorReq) Reset() {\n\t*x = DeleteConnectorReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[27]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeleteConnectorReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteConnectorReq) ProtoMessage() {}\n\nfunc (x *DeleteConnectorReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[27]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteConnectorReq.ProtoReflect.Descriptor instead.\nfunc (*DeleteConnectorReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{27}\n}\n\nfunc (x *DeleteConnectorReq) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\n// DeleteConnectorResp returns the response from deleting a connector.\ntype DeleteConnectorResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tNotFound      bool                   `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DeleteConnectorResp) Reset() {\n\t*x = DeleteConnectorResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[28]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DeleteConnectorResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DeleteConnectorResp) ProtoMessage() {}\n\nfunc (x *DeleteConnectorResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[28]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DeleteConnectorResp.ProtoReflect.Descriptor instead.\nfunc (*DeleteConnectorResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{28}\n}\n\nfunc (x *DeleteConnectorResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\n// ListConnectorReq is a request to enumerate connectors.\ntype ListConnectorReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListConnectorReq) Reset() {\n\t*x = ListConnectorReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[29]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListConnectorReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListConnectorReq) ProtoMessage() {}\n\nfunc (x *ListConnectorReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[29]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListConnectorReq.ProtoReflect.Descriptor instead.\nfunc (*ListConnectorReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{29}\n}\n\n// ListConnectorResp returns a list of connectors.\ntype ListConnectorResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tConnectors    []*Connector           `protobuf:\"bytes,1,rep,name=connectors,proto3\" json:\"connectors,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListConnectorResp) Reset() {\n\t*x = ListConnectorResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[30]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListConnectorResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListConnectorResp) ProtoMessage() {}\n\nfunc (x *ListConnectorResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[30]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListConnectorResp.ProtoReflect.Descriptor instead.\nfunc (*ListConnectorResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{30}\n}\n\nfunc (x *ListConnectorResp) GetConnectors() []*Connector {\n\tif x != nil {\n\t\treturn x.Connectors\n\t}\n\treturn nil\n}\n\n// VersionReq is a request to fetch version info.\ntype VersionReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VersionReq) Reset() {\n\t*x = VersionReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[31]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionReq) ProtoMessage() {}\n\nfunc (x *VersionReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[31]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionReq.ProtoReflect.Descriptor instead.\nfunc (*VersionReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{31}\n}\n\n// VersionResp holds the version info of components.\ntype VersionResp struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Semantic version of the server.\n\tServer string `protobuf:\"bytes,1,opt,name=server,proto3\" json:\"server,omitempty\"`\n\t// Numeric version of the API. It increases every time a new call is added to the API.\n\t// Clients should use this info to determine if the server supports specific features.\n\tApi           int32 `protobuf:\"varint,2,opt,name=api,proto3\" json:\"api,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VersionResp) Reset() {\n\t*x = VersionResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[32]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VersionResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VersionResp) ProtoMessage() {}\n\nfunc (x *VersionResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[32]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VersionResp.ProtoReflect.Descriptor instead.\nfunc (*VersionResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{32}\n}\n\nfunc (x *VersionResp) GetServer() string {\n\tif x != nil {\n\t\treturn x.Server\n\t}\n\treturn \"\"\n}\n\nfunc (x *VersionResp) GetApi() int32 {\n\tif x != nil {\n\t\treturn x.Api\n\t}\n\treturn 0\n}\n\n// DiscoveryReq is a request to fetch discover information.\ntype DiscoveryReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *DiscoveryReq) Reset() {\n\t*x = DiscoveryReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[33]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DiscoveryReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DiscoveryReq) ProtoMessage() {}\n\nfunc (x *DiscoveryReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[33]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DiscoveryReq.ProtoReflect.Descriptor instead.\nfunc (*DiscoveryReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{33}\n}\n\n// DiscoverResp holds the version oidc disovery info.\ntype DiscoveryResp struct {\n\tstate                             protoimpl.MessageState `protogen:\"open.v1\"`\n\tIssuer                            string                 `protobuf:\"bytes,1,opt,name=issuer,proto3\" json:\"issuer,omitempty\"`\n\tAuthorizationEndpoint             string                 `protobuf:\"bytes,2,opt,name=authorization_endpoint,json=authorizationEndpoint,proto3\" json:\"authorization_endpoint,omitempty\"`\n\tTokenEndpoint                     string                 `protobuf:\"bytes,3,opt,name=token_endpoint,json=tokenEndpoint,proto3\" json:\"token_endpoint,omitempty\"`\n\tJwksUri                           string                 `protobuf:\"bytes,4,opt,name=jwks_uri,json=jwksUri,proto3\" json:\"jwks_uri,omitempty\"`\n\tUserinfoEndpoint                  string                 `protobuf:\"bytes,5,opt,name=userinfo_endpoint,json=userinfoEndpoint,proto3\" json:\"userinfo_endpoint,omitempty\"`\n\tDeviceAuthorizationEndpoint       string                 `protobuf:\"bytes,6,opt,name=device_authorization_endpoint,json=deviceAuthorizationEndpoint,proto3\" json:\"device_authorization_endpoint,omitempty\"`\n\tIntrospectionEndpoint             string                 `protobuf:\"bytes,7,opt,name=introspection_endpoint,json=introspectionEndpoint,proto3\" json:\"introspection_endpoint,omitempty\"`\n\tGrantTypesSupported               []string               `protobuf:\"bytes,8,rep,name=grant_types_supported,json=grantTypesSupported,proto3\" json:\"grant_types_supported,omitempty\"`\n\tResponseTypesSupported            []string               `protobuf:\"bytes,9,rep,name=response_types_supported,json=responseTypesSupported,proto3\" json:\"response_types_supported,omitempty\"`\n\tSubjectTypesSupported             []string               `protobuf:\"bytes,10,rep,name=subject_types_supported,json=subjectTypesSupported,proto3\" json:\"subject_types_supported,omitempty\"`\n\tIdTokenSigningAlgValuesSupported  []string               `protobuf:\"bytes,11,rep,name=id_token_signing_alg_values_supported,json=idTokenSigningAlgValuesSupported,proto3\" json:\"id_token_signing_alg_values_supported,omitempty\"`\n\tCodeChallengeMethodsSupported     []string               `protobuf:\"bytes,12,rep,name=code_challenge_methods_supported,json=codeChallengeMethodsSupported,proto3\" json:\"code_challenge_methods_supported,omitempty\"`\n\tScopesSupported                   []string               `protobuf:\"bytes,13,rep,name=scopes_supported,json=scopesSupported,proto3\" json:\"scopes_supported,omitempty\"`\n\tTokenEndpointAuthMethodsSupported []string               `protobuf:\"bytes,14,rep,name=token_endpoint_auth_methods_supported,json=tokenEndpointAuthMethodsSupported,proto3\" json:\"token_endpoint_auth_methods_supported,omitempty\"`\n\tClaimsSupported                   []string               `protobuf:\"bytes,15,rep,name=claims_supported,json=claimsSupported,proto3\" json:\"claims_supported,omitempty\"`\n\tunknownFields                     protoimpl.UnknownFields\n\tsizeCache                         protoimpl.SizeCache\n}\n\nfunc (x *DiscoveryResp) Reset() {\n\t*x = DiscoveryResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[34]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *DiscoveryResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*DiscoveryResp) ProtoMessage() {}\n\nfunc (x *DiscoveryResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[34]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use DiscoveryResp.ProtoReflect.Descriptor instead.\nfunc (*DiscoveryResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{34}\n}\n\nfunc (x *DiscoveryResp) GetIssuer() string {\n\tif x != nil {\n\t\treturn x.Issuer\n\t}\n\treturn \"\"\n}\n\nfunc (x *DiscoveryResp) GetAuthorizationEndpoint() string {\n\tif x != nil {\n\t\treturn x.AuthorizationEndpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *DiscoveryResp) GetTokenEndpoint() string {\n\tif x != nil {\n\t\treturn x.TokenEndpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *DiscoveryResp) GetJwksUri() string {\n\tif x != nil {\n\t\treturn x.JwksUri\n\t}\n\treturn \"\"\n}\n\nfunc (x *DiscoveryResp) GetUserinfoEndpoint() string {\n\tif x != nil {\n\t\treturn x.UserinfoEndpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *DiscoveryResp) GetDeviceAuthorizationEndpoint() string {\n\tif x != nil {\n\t\treturn x.DeviceAuthorizationEndpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *DiscoveryResp) GetIntrospectionEndpoint() string {\n\tif x != nil {\n\t\treturn x.IntrospectionEndpoint\n\t}\n\treturn \"\"\n}\n\nfunc (x *DiscoveryResp) GetGrantTypesSupported() []string {\n\tif x != nil {\n\t\treturn x.GrantTypesSupported\n\t}\n\treturn nil\n}\n\nfunc (x *DiscoveryResp) GetResponseTypesSupported() []string {\n\tif x != nil {\n\t\treturn x.ResponseTypesSupported\n\t}\n\treturn nil\n}\n\nfunc (x *DiscoveryResp) GetSubjectTypesSupported() []string {\n\tif x != nil {\n\t\treturn x.SubjectTypesSupported\n\t}\n\treturn nil\n}\n\nfunc (x *DiscoveryResp) GetIdTokenSigningAlgValuesSupported() []string {\n\tif x != nil {\n\t\treturn x.IdTokenSigningAlgValuesSupported\n\t}\n\treturn nil\n}\n\nfunc (x *DiscoveryResp) GetCodeChallengeMethodsSupported() []string {\n\tif x != nil {\n\t\treturn x.CodeChallengeMethodsSupported\n\t}\n\treturn nil\n}\n\nfunc (x *DiscoveryResp) GetScopesSupported() []string {\n\tif x != nil {\n\t\treturn x.ScopesSupported\n\t}\n\treturn nil\n}\n\nfunc (x *DiscoveryResp) GetTokenEndpointAuthMethodsSupported() []string {\n\tif x != nil {\n\t\treturn x.TokenEndpointAuthMethodsSupported\n\t}\n\treturn nil\n}\n\nfunc (x *DiscoveryResp) GetClaimsSupported() []string {\n\tif x != nil {\n\t\treturn x.ClaimsSupported\n\t}\n\treturn nil\n}\n\n// RefreshTokenRef contains the metadata for a refresh token that is managed by the storage.\ntype RefreshTokenRef struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// ID of the refresh token.\n\tId            string `protobuf:\"bytes,1,opt,name=id,proto3\" json:\"id,omitempty\"`\n\tClientId      string `protobuf:\"bytes,2,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tCreatedAt     int64  `protobuf:\"varint,5,opt,name=created_at,json=createdAt,proto3\" json:\"created_at,omitempty\"`\n\tLastUsed      int64  `protobuf:\"varint,6,opt,name=last_used,json=lastUsed,proto3\" json:\"last_used,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RefreshTokenRef) Reset() {\n\t*x = RefreshTokenRef{}\n\tmi := &file_api_v2_api_proto_msgTypes[35]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RefreshTokenRef) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RefreshTokenRef) ProtoMessage() {}\n\nfunc (x *RefreshTokenRef) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[35]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RefreshTokenRef.ProtoReflect.Descriptor instead.\nfunc (*RefreshTokenRef) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{35}\n}\n\nfunc (x *RefreshTokenRef) GetId() string {\n\tif x != nil {\n\t\treturn x.Id\n\t}\n\treturn \"\"\n}\n\nfunc (x *RefreshTokenRef) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\nfunc (x *RefreshTokenRef) GetCreatedAt() int64 {\n\tif x != nil {\n\t\treturn x.CreatedAt\n\t}\n\treturn 0\n}\n\nfunc (x *RefreshTokenRef) GetLastUsed() int64 {\n\tif x != nil {\n\t\treturn x.LastUsed\n\t}\n\treturn 0\n}\n\n// ListRefreshReq is a request to enumerate the refresh tokens of a user.\ntype ListRefreshReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The \"sub\" claim returned in the ID Token.\n\tUserId        string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRefreshReq) Reset() {\n\t*x = ListRefreshReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[36]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRefreshReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRefreshReq) ProtoMessage() {}\n\nfunc (x *ListRefreshReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[36]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRefreshReq.ProtoReflect.Descriptor instead.\nfunc (*ListRefreshReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{36}\n}\n\nfunc (x *ListRefreshReq) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\n// ListRefreshResp returns a list of refresh tokens for a user.\ntype ListRefreshResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRefreshTokens []*RefreshTokenRef     `protobuf:\"bytes,1,rep,name=refresh_tokens,json=refreshTokens,proto3\" json:\"refresh_tokens,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *ListRefreshResp) Reset() {\n\t*x = ListRefreshResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[37]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *ListRefreshResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*ListRefreshResp) ProtoMessage() {}\n\nfunc (x *ListRefreshResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[37]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use ListRefreshResp.ProtoReflect.Descriptor instead.\nfunc (*ListRefreshResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{37}\n}\n\nfunc (x *ListRefreshResp) GetRefreshTokens() []*RefreshTokenRef {\n\tif x != nil {\n\t\treturn x.RefreshTokens\n\t}\n\treturn nil\n}\n\n// RevokeRefreshReq is a request to revoke the refresh token of the user-client pair.\ntype RevokeRefreshReq struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// The \"sub\" claim returned in the ID Token.\n\tUserId        string `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tClientId      string `protobuf:\"bytes,2,opt,name=client_id,json=clientId,proto3\" json:\"client_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RevokeRefreshReq) Reset() {\n\t*x = RevokeRefreshReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[38]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RevokeRefreshReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RevokeRefreshReq) ProtoMessage() {}\n\nfunc (x *RevokeRefreshReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[38]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RevokeRefreshReq.ProtoReflect.Descriptor instead.\nfunc (*RevokeRefreshReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{38}\n}\n\nfunc (x *RevokeRefreshReq) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *RevokeRefreshReq) GetClientId() string {\n\tif x != nil {\n\t\treturn x.ClientId\n\t}\n\treturn \"\"\n}\n\n// RevokeRefreshResp determines if the refresh token is revoked successfully.\ntype RevokeRefreshResp struct {\n\tstate protoimpl.MessageState `protogen:\"open.v1\"`\n\t// Set to true is refresh token was not found and token could not be revoked.\n\tNotFound      bool `protobuf:\"varint,1,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RevokeRefreshResp) Reset() {\n\t*x = RevokeRefreshResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[39]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RevokeRefreshResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RevokeRefreshResp) ProtoMessage() {}\n\nfunc (x *RevokeRefreshResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[39]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RevokeRefreshResp.ProtoReflect.Descriptor instead.\nfunc (*RevokeRefreshResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{39}\n}\n\nfunc (x *RevokeRefreshResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\ntype VerifyPasswordReq struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tEmail         string                 `protobuf:\"bytes,1,opt,name=email,proto3\" json:\"email,omitempty\"`\n\tPassword      string                 `protobuf:\"bytes,2,opt,name=password,proto3\" json:\"password,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VerifyPasswordReq) Reset() {\n\t*x = VerifyPasswordReq{}\n\tmi := &file_api_v2_api_proto_msgTypes[40]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VerifyPasswordReq) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifyPasswordReq) ProtoMessage() {}\n\nfunc (x *VerifyPasswordReq) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[40]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifyPasswordReq.ProtoReflect.Descriptor instead.\nfunc (*VerifyPasswordReq) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{40}\n}\n\nfunc (x *VerifyPasswordReq) GetEmail() string {\n\tif x != nil {\n\t\treturn x.Email\n\t}\n\treturn \"\"\n}\n\nfunc (x *VerifyPasswordReq) GetPassword() string {\n\tif x != nil {\n\t\treturn x.Password\n\t}\n\treturn \"\"\n}\n\ntype VerifyPasswordResp struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tVerified      bool                   `protobuf:\"varint,1,opt,name=verified,proto3\" json:\"verified,omitempty\"`\n\tNotFound      bool                   `protobuf:\"varint,2,opt,name=not_found,json=notFound,proto3\" json:\"not_found,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *VerifyPasswordResp) Reset() {\n\t*x = VerifyPasswordResp{}\n\tmi := &file_api_v2_api_proto_msgTypes[41]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *VerifyPasswordResp) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*VerifyPasswordResp) ProtoMessage() {}\n\nfunc (x *VerifyPasswordResp) ProtoReflect() protoreflect.Message {\n\tmi := &file_api_v2_api_proto_msgTypes[41]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use VerifyPasswordResp.ProtoReflect.Descriptor instead.\nfunc (*VerifyPasswordResp) Descriptor() ([]byte, []int) {\n\treturn file_api_v2_api_proto_rawDescGZIP(), []int{41}\n}\n\nfunc (x *VerifyPasswordResp) GetVerified() bool {\n\tif x != nil {\n\t\treturn x.Verified\n\t}\n\treturn false\n}\n\nfunc (x *VerifyPasswordResp) GetNotFound() bool {\n\tif x != nil {\n\t\treturn x.NotFound\n\t}\n\treturn false\n}\n\nvar File_api_v2_api_proto protoreflect.FileDescriptor\n\nvar file_api_v2_api_proto_rawDesc = string([]byte{\n\t0x0a, 0x10, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x2f, 0x61, 0x70, 0x69, 0x2e, 0x70, 0x72, 0x6f,\n\t0x74, 0x6f, 0x12, 0x03, 0x61, 0x70, 0x69, 0x22, 0xf0, 0x01, 0x0a, 0x06, 0x43, 0x6c, 0x69, 0x65,\n\t0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,\n\t0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65,\n\t0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,\n\t0x09, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12,\n\t0x23, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73,\n\t0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50,\n\t0x65, 0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x05,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04,\n\t0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,\n\t0x12, 0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x07, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61,\n\t0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72,\n\t0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64,\n\t0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0xdc, 0x01, 0x0a, 0x0a, 0x43,\n\t0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64,\n\t0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x55, 0x72, 0x69, 0x73, 0x12, 0x23,\n\t0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18,\n\t0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x50, 0x65,\n\t0x65, 0x72, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x18, 0x04, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x12, 0x12, 0x0a, 0x04, 0x6e,\n\t0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,\n\t0x19, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c,\n\t0x6c, 0x6f, 0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73,\n\t0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43,\n\t0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x1e, 0x0a, 0x0c, 0x47, 0x65, 0x74,\n\t0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x34, 0x0a, 0x0d, 0x47, 0x65, 0x74,\n\t0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c,\n\t0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69,\n\t0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22,\n\t0x36, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x65, 0x71, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x5e, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x61, 0x74,\n\t0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61,\n\t0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73,\n\t0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01,\n\t0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x22, 0x21, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74,\n\t0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x2f, 0x0a, 0x10, 0x44, 0x65,\n\t0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b,\n\t0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0xc9, 0x01, 0x0a, 0x0f,\n\t0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12,\n\t0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,\n\t0x23, 0x0a, 0x0d, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x75, 0x72, 0x69, 0x73,\n\t0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74,\n\t0x55, 0x72, 0x69, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x5f,\n\t0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x75,\n\t0x73, 0x74, 0x65, 0x64, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a,\n\t0x08, 0x6c, 0x6f, 0x67, 0x6f, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x07, 0x6c, 0x6f, 0x67, 0x6f, 0x55, 0x72, 0x6c, 0x12, 0x2d, 0x0a, 0x12, 0x61, 0x6c, 0x6c, 0x6f,\n\t0x77, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x06,\n\t0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x43, 0x6f, 0x6e,\n\t0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x2f, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74,\n\t0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e,\n\t0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,\n\t0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x0f, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74,\n\t0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x22, 0x3b, 0x0a, 0x0e, 0x4c, 0x69, 0x73,\n\t0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x29, 0x0a, 0x07, 0x63,\n\t0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x63,\n\t0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x69, 0x0a, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68,\n\t0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08,\n\t0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,\n\t0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72,\n\t0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49,\n\t0x64, 0x22, 0x3e, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,\n\t0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x29, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,\n\t0x64, 0x22, 0x3b, 0x0a, 0x12, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,\n\t0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61,\n\t0x64, 0x79, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,\n\t0x0d, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x67,\n\t0x0a, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,\n\t0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77,\n\t0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77,\n\t0x48, 0x61, 0x73, 0x68, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x65, 0x77, 0x5f, 0x75, 0x73, 0x65, 0x72,\n\t0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x65, 0x77, 0x55,\n\t0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x31, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74,\n\t0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a,\n\t0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x29, 0x0a, 0x11, 0x44, 0x65,\n\t0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12,\n\t0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,\n\t0x65, 0x6d, 0x61, 0x69, 0x6c, 0x22, 0x31, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50,\n\t0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e,\n\t0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08,\n\t0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x11, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74,\n\t0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x22, 0x3f, 0x0a, 0x10, 0x4c,\n\t0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x12,\n\t0x2b, 0x0a, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03,\n\t0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,\n\t0x64, 0x52, 0x09, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x22, 0x7c, 0x0a, 0x09,\n\t0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,\n\t0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70,\n\t0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a,\n\t0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,\n\t0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28,\n\t0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72, 0x61,\n\t0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a,\n\t0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0x42, 0x0a, 0x12, 0x43, 0x72,\n\t0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71,\n\t0x12, 0x2c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,\n\t0x74, 0x6f, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x22, 0x3c,\n\t0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f,\n\t0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79,\n\t0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x61,\n\t0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x45, 0x78, 0x69, 0x73, 0x74, 0x73, 0x22, 0x2d, 0x0a, 0x0a,\n\t0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x67, 0x72,\n\t0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x0a, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x22, 0xb2, 0x01, 0x0a, 0x12,\n\t0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52,\n\t0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02,\n\t0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a,\n\t0x08, 0x6e, 0x65, 0x77, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x07, 0x6e, 0x65, 0x77, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x77, 0x5f,\n\t0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6e, 0x65,\n\t0x77, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x37, 0x0a, 0x0f, 0x6e, 0x65, 0x77, 0x5f, 0x67,\n\t0x72, 0x61, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b,\n\t0x32, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65,\n\t0x73, 0x52, 0x0d, 0x6e, 0x65, 0x77, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x73,\n\t0x22, 0x32, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,\n\t0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66,\n\t0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46,\n\t0x6f, 0x75, 0x6e, 0x64, 0x22, 0x24, 0x0a, 0x12, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f,\n\t0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,\n\t0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x32, 0x0a, 0x13, 0x44, 0x65,\n\t0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73,\n\t0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x22, 0x12,\n\t0x0a, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52,\n\t0x65, 0x71, 0x22, 0x43, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,\n\t0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x2e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x6e, 0x65,\n\t0x63, 0x74, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x61, 0x70,\n\t0x69, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x0a, 0x63, 0x6f, 0x6e,\n\t0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x22, 0x0c, 0x0a, 0x0a, 0x56, 0x65, 0x72, 0x73, 0x69,\n\t0x6f, 0x6e, 0x52, 0x65, 0x71, 0x22, 0x37, 0x0a, 0x0b, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,\n\t0x52, 0x65, 0x73, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x01,\n\t0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03,\n\t0x61, 0x70, 0x69, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x61, 0x70, 0x69, 0x22, 0x0e,\n\t0x0a, 0x0c, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x22, 0xb0,\n\t0x06, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70,\n\t0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x16, 0x61, 0x75, 0x74, 0x68,\n\t0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69,\n\t0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72,\n\t0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12,\n\t0x25, 0x0a, 0x0e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e,\n\t0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e,\n\t0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x6a, 0x77, 0x6b, 0x73, 0x5f, 0x75,\n\t0x72, 0x69, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6a, 0x77, 0x6b, 0x73, 0x55, 0x72,\n\t0x69, 0x12, 0x2b, 0x0a, 0x11, 0x75, 0x73, 0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x5f, 0x65, 0x6e,\n\t0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x75, 0x73,\n\t0x65, 0x72, 0x69, 0x6e, 0x66, 0x6f, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x42,\n\t0x0a, 0x1d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,\n\t0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18,\n\t0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x41, 0x75, 0x74,\n\t0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69,\n\t0x6e, 0x74, 0x12, 0x35, 0x0a, 0x16, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74,\n\t0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01,\n\t0x28, 0x09, 0x52, 0x15, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x73, 0x70, 0x65, 0x63, 0x74, 0x69, 0x6f,\n\t0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x67, 0x72, 0x61,\n\t0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74,\n\t0x65, 0x64, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x54,\n\t0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x38, 0x0a,\n\t0x18, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f,\n\t0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52,\n\t0x16, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75,\n\t0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x75, 0x62, 0x6a, 0x65,\n\t0x63, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74,\n\t0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, 0x52, 0x15, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63,\n\t0x74, 0x54, 0x79, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12,\n\t0x4f, 0x0a, 0x25, 0x69, 0x64, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x73, 0x69, 0x67, 0x6e,\n\t0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6c, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x5f, 0x73,\n\t0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x20,\n\t0x69, 0x64, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x41, 0x6c,\n\t0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64,\n\t0x12, 0x47, 0x0a, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e,\n\t0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f,\n\t0x72, 0x74, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1d, 0x63, 0x6f, 0x64, 0x65,\n\t0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73,\n\t0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x73, 0x63, 0x6f,\n\t0x70, 0x65, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20,\n\t0x03, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f,\n\t0x72, 0x74, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x25, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x65, 0x6e,\n\t0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x6d, 0x65, 0x74, 0x68,\n\t0x6f, 0x64, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0e, 0x20,\n\t0x03, 0x28, 0x09, 0x52, 0x21, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69,\n\t0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x53, 0x75, 0x70,\n\t0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73,\n\t0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x09,\n\t0x52, 0x0f, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65,\n\t0x64, 0x22, 0x7a, 0x0a, 0x0f, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65,\n\t0x6e, 0x52, 0x65, 0x66, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x02, 0x69, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69,\n\t0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x49,\n\t0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18,\n\t0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74,\n\t0x12, 0x1b, 0x0a, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x06, 0x20,\n\t0x01, 0x28, 0x03, 0x52, 0x08, 0x6c, 0x61, 0x73, 0x74, 0x55, 0x73, 0x65, 0x64, 0x22, 0x29, 0x0a,\n\t0x0e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12,\n\t0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,\n\t0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x22, 0x4e, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74,\n\t0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x3b, 0x0a, 0x0e, 0x72,\n\t0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x01, 0x20,\n\t0x03, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73,\n\t0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x65, 0x66, 0x52, 0x0d, 0x72, 0x65, 0x66, 0x72, 0x65,\n\t0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x22, 0x48, 0x0a, 0x10, 0x52, 0x65, 0x76, 0x6f,\n\t0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x12, 0x17, 0x0a, 0x07,\n\t0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75,\n\t0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f,\n\t0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74,\n\t0x49, 0x64, 0x22, 0x30, 0x0a, 0x11, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72,\n\t0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66,\n\t0x6f, 0x75, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46,\n\t0x6f, 0x75, 0x6e, 0x64, 0x22, 0x45, 0x0a, 0x11, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61,\n\t0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61,\n\t0x69, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12,\n\t0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28,\n\t0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x4d, 0x0a, 0x12, 0x56,\n\t0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73,\n\t0x70, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x18, 0x01, 0x20,\n\t0x01, 0x28, 0x08, 0x52, 0x08, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x65, 0x64, 0x12, 0x1b, 0x0a,\n\t0x09, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08,\n\t0x52, 0x08, 0x6e, 0x6f, 0x74, 0x46, 0x6f, 0x75, 0x6e, 0x64, 0x32, 0x8b, 0x09, 0x0a, 0x03, 0x44,\n\t0x65, 0x78, 0x12, 0x34, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12,\n\t0x11, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x65, 0x71, 0x1a, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, 0x65,\n\t0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61,\n\t0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43,\n\t0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15,\n\t0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e,\n\t0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74,\n\t0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70,\n\t0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e,\n\t0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74,\n\t0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3d, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,\n\t0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c,\n\t0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52,\n\t0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6c, 0x69,\n\t0x65, 0x6e, 0x74, 0x73, 0x12, 0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43,\n\t0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,\n\t0x69, 0x73, 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12,\n\t0x43, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72,\n\t0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61,\n\t0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e,\n\t0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65,\n\t0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61,\n\t0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64,\n\t0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17,\n\t0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77,\n\t0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x44, 0x65, 0x6c,\n\t0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70,\n\t0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64,\n\t0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65,\n\t0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3e,\n\t0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12,\n\t0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74,\n\t0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46,\n\t0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f,\n\t0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f,\n\t0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69,\n\t0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72,\n\t0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,\n\t0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e,\n\t0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52,\n\t0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43,\n\t0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x46,\n\t0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f,\n\t0x72, 0x12, 0x17, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f,\n\t0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a, 0x18, 0x2e, 0x61, 0x70, 0x69,\n\t0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72,\n\t0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f,\n\t0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c,\n\t0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x1a,\n\t0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63,\n\t0x74, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x0a, 0x47, 0x65, 0x74,\n\t0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x0f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65,\n\t0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x1a, 0x10, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56,\n\t0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0c,\n\t0x47, 0x65, 0x74, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x12, 0x11, 0x2e, 0x61,\n\t0x70, 0x69, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52, 0x65, 0x71, 0x1a,\n\t0x12, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x52,\n\t0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3a, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66,\n\t0x72, 0x65, 0x73, 0x68, 0x12, 0x13, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52,\n\t0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x14, 0x2e, 0x61, 0x70, 0x69, 0x2e,\n\t0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73, 0x70, 0x22,\n\t0x00, 0x12, 0x40, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65,\n\t0x73, 0x68, 0x12, 0x15, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52,\n\t0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x71, 0x1a, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e,\n\t0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x52, 0x65, 0x66, 0x72, 0x65, 0x73, 0x68, 0x52, 0x65, 0x73,\n\t0x70, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x0e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73,\n\t0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x16, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69,\n\t0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e,\n\t0x61, 0x70, 0x69, 0x2e, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f,\n\t0x72, 0x64, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x36, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e,\n\t0x63, 0x6f, 0x72, 0x65, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x2e, 0x61, 0x70, 0x69, 0x5a, 0x20,\n\t0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x78, 0x69, 0x64,\n\t0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x32, 0x3b, 0x61, 0x70, 0x69,\n\t0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,\n})\n\nvar (\n\tfile_api_v2_api_proto_rawDescOnce sync.Once\n\tfile_api_v2_api_proto_rawDescData []byte\n)\n\nfunc file_api_v2_api_proto_rawDescGZIP() []byte {\n\tfile_api_v2_api_proto_rawDescOnce.Do(func() {\n\t\tfile_api_v2_api_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_api_v2_api_proto_rawDesc), len(file_api_v2_api_proto_rawDesc)))\n\t})\n\treturn file_api_v2_api_proto_rawDescData\n}\n\nvar file_api_v2_api_proto_msgTypes = make([]protoimpl.MessageInfo, 42)\nvar file_api_v2_api_proto_goTypes = []any{\n\t(*Client)(nil),              // 0: api.Client\n\t(*ClientInfo)(nil),          // 1: api.ClientInfo\n\t(*GetClientReq)(nil),        // 2: api.GetClientReq\n\t(*GetClientResp)(nil),       // 3: api.GetClientResp\n\t(*CreateClientReq)(nil),     // 4: api.CreateClientReq\n\t(*CreateClientResp)(nil),    // 5: api.CreateClientResp\n\t(*DeleteClientReq)(nil),     // 6: api.DeleteClientReq\n\t(*DeleteClientResp)(nil),    // 7: api.DeleteClientResp\n\t(*UpdateClientReq)(nil),     // 8: api.UpdateClientReq\n\t(*UpdateClientResp)(nil),    // 9: api.UpdateClientResp\n\t(*ListClientReq)(nil),       // 10: api.ListClientReq\n\t(*ListClientResp)(nil),      // 11: api.ListClientResp\n\t(*Password)(nil),            // 12: api.Password\n\t(*CreatePasswordReq)(nil),   // 13: api.CreatePasswordReq\n\t(*CreatePasswordResp)(nil),  // 14: api.CreatePasswordResp\n\t(*UpdatePasswordReq)(nil),   // 15: api.UpdatePasswordReq\n\t(*UpdatePasswordResp)(nil),  // 16: api.UpdatePasswordResp\n\t(*DeletePasswordReq)(nil),   // 17: api.DeletePasswordReq\n\t(*DeletePasswordResp)(nil),  // 18: api.DeletePasswordResp\n\t(*ListPasswordReq)(nil),     // 19: api.ListPasswordReq\n\t(*ListPasswordResp)(nil),    // 20: api.ListPasswordResp\n\t(*Connector)(nil),           // 21: api.Connector\n\t(*CreateConnectorReq)(nil),  // 22: api.CreateConnectorReq\n\t(*CreateConnectorResp)(nil), // 23: api.CreateConnectorResp\n\t(*GrantTypes)(nil),          // 24: api.GrantTypes\n\t(*UpdateConnectorReq)(nil),  // 25: api.UpdateConnectorReq\n\t(*UpdateConnectorResp)(nil), // 26: api.UpdateConnectorResp\n\t(*DeleteConnectorReq)(nil),  // 27: api.DeleteConnectorReq\n\t(*DeleteConnectorResp)(nil), // 28: api.DeleteConnectorResp\n\t(*ListConnectorReq)(nil),    // 29: api.ListConnectorReq\n\t(*ListConnectorResp)(nil),   // 30: api.ListConnectorResp\n\t(*VersionReq)(nil),          // 31: api.VersionReq\n\t(*VersionResp)(nil),         // 32: api.VersionResp\n\t(*DiscoveryReq)(nil),        // 33: api.DiscoveryReq\n\t(*DiscoveryResp)(nil),       // 34: api.DiscoveryResp\n\t(*RefreshTokenRef)(nil),     // 35: api.RefreshTokenRef\n\t(*ListRefreshReq)(nil),      // 36: api.ListRefreshReq\n\t(*ListRefreshResp)(nil),     // 37: api.ListRefreshResp\n\t(*RevokeRefreshReq)(nil),    // 38: api.RevokeRefreshReq\n\t(*RevokeRefreshResp)(nil),   // 39: api.RevokeRefreshResp\n\t(*VerifyPasswordReq)(nil),   // 40: api.VerifyPasswordReq\n\t(*VerifyPasswordResp)(nil),  // 41: api.VerifyPasswordResp\n}\nvar file_api_v2_api_proto_depIdxs = []int32{\n\t0,  // 0: api.GetClientResp.client:type_name -> api.Client\n\t0,  // 1: api.CreateClientReq.client:type_name -> api.Client\n\t0,  // 2: api.CreateClientResp.client:type_name -> api.Client\n\t1,  // 3: api.ListClientResp.clients:type_name -> api.ClientInfo\n\t12, // 4: api.CreatePasswordReq.password:type_name -> api.Password\n\t12, // 5: api.ListPasswordResp.passwords:type_name -> api.Password\n\t21, // 6: api.CreateConnectorReq.connector:type_name -> api.Connector\n\t24, // 7: api.UpdateConnectorReq.new_grant_types:type_name -> api.GrantTypes\n\t21, // 8: api.ListConnectorResp.connectors:type_name -> api.Connector\n\t35, // 9: api.ListRefreshResp.refresh_tokens:type_name -> api.RefreshTokenRef\n\t2,  // 10: api.Dex.GetClient:input_type -> api.GetClientReq\n\t4,  // 11: api.Dex.CreateClient:input_type -> api.CreateClientReq\n\t8,  // 12: api.Dex.UpdateClient:input_type -> api.UpdateClientReq\n\t6,  // 13: api.Dex.DeleteClient:input_type -> api.DeleteClientReq\n\t10, // 14: api.Dex.ListClients:input_type -> api.ListClientReq\n\t13, // 15: api.Dex.CreatePassword:input_type -> api.CreatePasswordReq\n\t15, // 16: api.Dex.UpdatePassword:input_type -> api.UpdatePasswordReq\n\t17, // 17: api.Dex.DeletePassword:input_type -> api.DeletePasswordReq\n\t19, // 18: api.Dex.ListPasswords:input_type -> api.ListPasswordReq\n\t22, // 19: api.Dex.CreateConnector:input_type -> api.CreateConnectorReq\n\t25, // 20: api.Dex.UpdateConnector:input_type -> api.UpdateConnectorReq\n\t27, // 21: api.Dex.DeleteConnector:input_type -> api.DeleteConnectorReq\n\t29, // 22: api.Dex.ListConnectors:input_type -> api.ListConnectorReq\n\t31, // 23: api.Dex.GetVersion:input_type -> api.VersionReq\n\t33, // 24: api.Dex.GetDiscovery:input_type -> api.DiscoveryReq\n\t36, // 25: api.Dex.ListRefresh:input_type -> api.ListRefreshReq\n\t38, // 26: api.Dex.RevokeRefresh:input_type -> api.RevokeRefreshReq\n\t40, // 27: api.Dex.VerifyPassword:input_type -> api.VerifyPasswordReq\n\t3,  // 28: api.Dex.GetClient:output_type -> api.GetClientResp\n\t5,  // 29: api.Dex.CreateClient:output_type -> api.CreateClientResp\n\t9,  // 30: api.Dex.UpdateClient:output_type -> api.UpdateClientResp\n\t7,  // 31: api.Dex.DeleteClient:output_type -> api.DeleteClientResp\n\t11, // 32: api.Dex.ListClients:output_type -> api.ListClientResp\n\t14, // 33: api.Dex.CreatePassword:output_type -> api.CreatePasswordResp\n\t16, // 34: api.Dex.UpdatePassword:output_type -> api.UpdatePasswordResp\n\t18, // 35: api.Dex.DeletePassword:output_type -> api.DeletePasswordResp\n\t20, // 36: api.Dex.ListPasswords:output_type -> api.ListPasswordResp\n\t23, // 37: api.Dex.CreateConnector:output_type -> api.CreateConnectorResp\n\t26, // 38: api.Dex.UpdateConnector:output_type -> api.UpdateConnectorResp\n\t28, // 39: api.Dex.DeleteConnector:output_type -> api.DeleteConnectorResp\n\t30, // 40: api.Dex.ListConnectors:output_type -> api.ListConnectorResp\n\t32, // 41: api.Dex.GetVersion:output_type -> api.VersionResp\n\t34, // 42: api.Dex.GetDiscovery:output_type -> api.DiscoveryResp\n\t37, // 43: api.Dex.ListRefresh:output_type -> api.ListRefreshResp\n\t39, // 44: api.Dex.RevokeRefresh:output_type -> api.RevokeRefreshResp\n\t41, // 45: api.Dex.VerifyPassword:output_type -> api.VerifyPasswordResp\n\t28, // [28:46] is the sub-list for method output_type\n\t10, // [10:28] is the sub-list for method input_type\n\t10, // [10:10] is the sub-list for extension type_name\n\t10, // [10:10] is the sub-list for extension extendee\n\t0,  // [0:10] is the sub-list for field type_name\n}\n\nfunc init() { file_api_v2_api_proto_init() }\nfunc file_api_v2_api_proto_init() {\n\tif File_api_v2_api_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_api_v2_api_proto_rawDesc), len(file_api_v2_api_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   42,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   1,\n\t\t},\n\t\tGoTypes:           file_api_v2_api_proto_goTypes,\n\t\tDependencyIndexes: file_api_v2_api_proto_depIdxs,\n\t\tMessageInfos:      file_api_v2_api_proto_msgTypes,\n\t}.Build()\n\tFile_api_v2_api_proto = out.File\n\tfile_api_v2_api_proto_goTypes = nil\n\tfile_api_v2_api_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "api/v2/api.proto",
    "content": "syntax = \"proto3\";\n\npackage api;\n\noption java_package = \"com.coreos.dex.api\";\noption go_package = \"github.com/dexidp/dex/api/v2;api\";\n\n// Client represents an OAuth2 client.\nmessage Client {\n  string id = 1;\n  string secret = 2;\n  repeated string redirect_uris = 3;\n  repeated string trusted_peers = 4;\n  bool public = 5;\n  string name = 6;\n  string logo_url = 7;\n  repeated string allowed_connectors = 8;\n}\n\n// ClientInfo represents an OAuth2 client without sensitive information.\nmessage ClientInfo {\n  string id = 1;\n  repeated string redirect_uris = 2;\n  repeated string trusted_peers = 3;\n  bool public = 4;\n  string name = 5;\n  string logo_url = 6;\n  repeated string allowed_connectors = 7;\n}\n\n// GetClientReq is a request to retrieve client details.\nmessage GetClientReq {\n  // The ID of the client.\n  string id = 1;\n}\n\n// GetClientResp returns the client details.\nmessage GetClientResp {\n  Client client = 1;\n}\n\n// CreateClientReq is a request to make a client.\nmessage CreateClientReq {\n  Client client = 1;\n}\n\n// CreateClientResp returns the response from creating a client.\nmessage CreateClientResp {\n  bool already_exists = 1;\n  Client client = 2;\n}\n\n// DeleteClientReq is a request to delete a client.\nmessage DeleteClientReq {\n  // The ID of the client.\n  string id = 1;\n}\n\n// DeleteClientResp determines if the client is deleted successfully.\nmessage DeleteClientResp {\n  bool not_found = 1;\n}\n\n// UpdateClientReq is a request to update an existing client.\nmessage UpdateClientReq {\n    string id = 1;\n    repeated string redirect_uris = 2;\n    repeated string trusted_peers = 3;\n    string name = 4;\n    string logo_url = 5;\n    repeated string allowed_connectors = 6;\n}\n\n// UpdateClientResp returns the response from updating a client.\nmessage UpdateClientResp {\n    bool not_found = 1;\n}\n\n// ListClientReq is a request to enumerate clients.\nmessage ListClientReq {}\n\n// ListClientResp returns a list of clients.\nmessage ListClientResp {\n  repeated ClientInfo clients = 1;\n}\n\n// TODO(ericchiang): expand this.\n\n// Password is an email for password mapping managed by the storage.\nmessage Password {\n  string email = 1;\n\n  // Currently we do not accept plain text passwords. Could be an option in the future.\n  bytes hash = 2;\n  string username = 3;\n  string user_id = 4;\n}\n\n// CreatePasswordReq is a request to make a password.\nmessage CreatePasswordReq {\n  Password password = 1;\n}\n\n// CreatePasswordResp returns the response from creating a password.\nmessage CreatePasswordResp {\n  bool already_exists = 1;\n}\n\n// UpdatePasswordReq is a request to modify an existing password.\nmessage UpdatePasswordReq {\n  // The email used to lookup the password. This field cannot be modified\n  string email = 1;\n  bytes new_hash = 2;\n  string new_username = 3;\n}\n\n// UpdatePasswordResp returns the response from modifying an existing password.\nmessage UpdatePasswordResp {\n  bool not_found = 1;\n}\n\n// DeletePasswordReq is a request to delete a password.\nmessage DeletePasswordReq {\n  string email = 1;\n}\n\n// DeletePasswordResp returns the response from deleting a password.\nmessage DeletePasswordResp {\n  bool not_found = 1;\n}\n\n// ListPasswordReq is a request to enumerate passwords.\nmessage ListPasswordReq {}\n\n// ListPasswordResp returns a list of passwords.\nmessage ListPasswordResp {\n  repeated Password passwords = 1;\n}\n\n// Connector is a strategy used by Dex for authenticating a user against another identity provider\nmessage Connector {\n  string id = 1;\n  string type = 2;\n  string name = 3;\n  bytes config = 4;\n  repeated string grant_types = 5;\n}\n\n// CreateConnectorReq is a request to make a connector.\nmessage CreateConnectorReq {\n  Connector connector = 1;\n}\n\n// CreateConnectorResp returns the response from creating a connector.\nmessage CreateConnectorResp {\n  bool already_exists = 1;\n}\n\n// GrantTypes wraps a list of grant types to distinguish between\n// \"not specified\" (no update) and \"empty list\" (unrestricted).\nmessage GrantTypes {\n  repeated string grant_types = 1;\n}\n\n// UpdateConnectorReq is a request to modify an existing connector.\nmessage UpdateConnectorReq {\n  // The id used to lookup the connector. This field cannot be modified\n  string id = 1;\n  string new_type = 2;\n  string new_name = 3;\n  bytes new_config = 4;\n  // If set, updates the connector's allowed grant types.\n  // An empty grant_types list means unrestricted (all grant types allowed).\n  // If not set (null), grant types are not modified.\n  GrantTypes new_grant_types = 5;\n}\n\n// UpdateConnectorResp returns the response from modifying an existing connector.\nmessage UpdateConnectorResp {\n  bool not_found = 1;\n}\n\n// DeleteConnectorReq is a request to delete a connector.\nmessage DeleteConnectorReq {\n  string id = 1;\n}\n\n// DeleteConnectorResp returns the response from deleting a connector.\nmessage DeleteConnectorResp {\n  bool not_found = 1;\n}\n\n// ListConnectorReq is a request to enumerate connectors.\nmessage ListConnectorReq {}\n\n// ListConnectorResp returns a list of connectors.\nmessage ListConnectorResp {\n  repeated Connector connectors = 1;\n}\n\n// VersionReq is a request to fetch version info.\nmessage VersionReq {}\n\n// VersionResp holds the version info of components.\nmessage VersionResp {\n  // Semantic version of the server.\n  string server = 1;\n  // Numeric version of the API. It increases every time a new call is added to the API.\n  // Clients should use this info to determine if the server supports specific features.\n  int32 api = 2;\n}\n\n// DiscoveryReq is a request to fetch discover information.\nmessage DiscoveryReq {}\n\n//DiscoverResp holds the version oidc disovery info.\nmessage DiscoveryResp {\n  string issuer = 1;\n  string authorization_endpoint = 2;\n  string token_endpoint = 3;\n  string jwks_uri = 4;\n  string userinfo_endpoint = 5;\n  string device_authorization_endpoint = 6;\n  string introspection_endpoint = 7;\n  repeated string grant_types_supported = 8;\n  repeated string response_types_supported = 9;\n  repeated string subject_types_supported = 10;\n  repeated string id_token_signing_alg_values_supported = 11;\n  repeated string code_challenge_methods_supported = 12;\n  repeated string scopes_supported = 13;\n  repeated string token_endpoint_auth_methods_supported = 14;\n  repeated string claims_supported = 15;\n}\n\n// RefreshTokenRef contains the metadata for a refresh token that is managed by the storage.\nmessage RefreshTokenRef {\n  // ID of the refresh token.\n  string id = 1;\n  string client_id = 2;\n  int64 created_at = 5;\n  int64 last_used = 6;\n}\n\n// ListRefreshReq is a request to enumerate the refresh tokens of a user.\nmessage ListRefreshReq {\n  // The \"sub\" claim returned in the ID Token.\n  string user_id = 1;\n}\n\n// ListRefreshResp returns a list of refresh tokens for a user.\nmessage ListRefreshResp {\n  repeated RefreshTokenRef refresh_tokens = 1;\n}\n\n// RevokeRefreshReq is a request to revoke the refresh token of the user-client pair.\nmessage RevokeRefreshReq {\n  // The \"sub\" claim returned in the ID Token.\n  string user_id = 1;\n  string client_id = 2;\n}\n\n// RevokeRefreshResp determines if the refresh token is revoked successfully.\nmessage RevokeRefreshResp {\n  // Set to true is refresh token was not found and token could not be revoked.\n  bool not_found = 1;\n}\n\nmessage VerifyPasswordReq {\n  string email = 1;\n  string password = 2;\n}\n\nmessage VerifyPasswordResp {\n  bool verified = 1;\n  bool not_found = 2;\n}\n\n// Dex represents the dex gRPC service.\nservice Dex {\n  // GetClient gets a client.\n  rpc GetClient(GetClientReq) returns (GetClientResp) {};\n  // CreateClient creates a client.\n  rpc CreateClient(CreateClientReq) returns (CreateClientResp) {};\n  // UpdateClient updates an existing client\n  rpc UpdateClient(UpdateClientReq) returns (UpdateClientResp) {};\n  // DeleteClient deletes the provided client.\n  rpc DeleteClient(DeleteClientReq) returns (DeleteClientResp) {};\n  // ListClients lists all client entries.\n  rpc ListClients(ListClientReq) returns (ListClientResp) {};\n  // CreatePassword creates a password.\n  rpc CreatePassword(CreatePasswordReq) returns (CreatePasswordResp) {};\n  // UpdatePassword modifies existing password.\n  rpc UpdatePassword(UpdatePasswordReq) returns (UpdatePasswordResp) {};\n  // DeletePassword deletes the password.\n  rpc DeletePassword(DeletePasswordReq) returns (DeletePasswordResp) {};\n  // ListPassword lists all password entries.\n  rpc ListPasswords(ListPasswordReq) returns (ListPasswordResp) {};\n  // CreateConnector creates a connector.\n  rpc CreateConnector(CreateConnectorReq) returns (CreateConnectorResp) {};\n  // UpdateConnector modifies existing connector.\n  rpc UpdateConnector(UpdateConnectorReq) returns (UpdateConnectorResp) {};\n  // DeleteConnector deletes the connector.\n  rpc DeleteConnector(DeleteConnectorReq) returns (DeleteConnectorResp) {};\n  // ListConnectors lists all connector entries.\n  rpc ListConnectors(ListConnectorReq) returns (ListConnectorResp) {};\n  // GetVersion returns version information of the server.\n  rpc GetVersion(VersionReq) returns (VersionResp) {};\n  // GetDiscovery returns discovery information of the server.\n  rpc GetDiscovery(DiscoveryReq) returns (DiscoveryResp) {};\n  // ListRefresh lists all the refresh token entries for a particular user.\n  rpc ListRefresh(ListRefreshReq) returns (ListRefreshResp) {};\n  // RevokeRefresh revokes the refresh token for the provided user-client pair.\n  //\n  // Note that each user-client pair can have only one refresh token at a time.\n  rpc RevokeRefresh(RevokeRefreshReq) returns (RevokeRefreshResp) {};\n  // VerifyPassword returns whether a password matches a hash for a specific email or not.\n  rpc VerifyPassword(VerifyPasswordReq) returns (VerifyPasswordResp) {};\n}\n"
  },
  {
    "path": "api/v2/api_grpc.pb.go",
    "content": "// Code generated by protoc-gen-go-grpc. DO NOT EDIT.\n// versions:\n// - protoc-gen-go-grpc v1.5.1\n// - protoc             v5.29.3\n// source: api/v2/api.proto\n\npackage api\n\nimport (\n\tcontext \"context\"\n\tgrpc \"google.golang.org/grpc\"\n\tcodes \"google.golang.org/grpc/codes\"\n\tstatus \"google.golang.org/grpc/status\"\n)\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the grpc package it is being compiled against.\n// Requires gRPC-Go v1.64.0 or later.\nconst _ = grpc.SupportPackageIsVersion9\n\nconst (\n\tDex_GetClient_FullMethodName       = \"/api.Dex/GetClient\"\n\tDex_CreateClient_FullMethodName    = \"/api.Dex/CreateClient\"\n\tDex_UpdateClient_FullMethodName    = \"/api.Dex/UpdateClient\"\n\tDex_DeleteClient_FullMethodName    = \"/api.Dex/DeleteClient\"\n\tDex_ListClients_FullMethodName     = \"/api.Dex/ListClients\"\n\tDex_CreatePassword_FullMethodName  = \"/api.Dex/CreatePassword\"\n\tDex_UpdatePassword_FullMethodName  = \"/api.Dex/UpdatePassword\"\n\tDex_DeletePassword_FullMethodName  = \"/api.Dex/DeletePassword\"\n\tDex_ListPasswords_FullMethodName   = \"/api.Dex/ListPasswords\"\n\tDex_CreateConnector_FullMethodName = \"/api.Dex/CreateConnector\"\n\tDex_UpdateConnector_FullMethodName = \"/api.Dex/UpdateConnector\"\n\tDex_DeleteConnector_FullMethodName = \"/api.Dex/DeleteConnector\"\n\tDex_ListConnectors_FullMethodName  = \"/api.Dex/ListConnectors\"\n\tDex_GetVersion_FullMethodName      = \"/api.Dex/GetVersion\"\n\tDex_GetDiscovery_FullMethodName    = \"/api.Dex/GetDiscovery\"\n\tDex_ListRefresh_FullMethodName     = \"/api.Dex/ListRefresh\"\n\tDex_RevokeRefresh_FullMethodName   = \"/api.Dex/RevokeRefresh\"\n\tDex_VerifyPassword_FullMethodName  = \"/api.Dex/VerifyPassword\"\n)\n\n// DexClient is the client API for Dex service.\n//\n// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.\n//\n// Dex represents the dex gRPC service.\ntype DexClient interface {\n\t// GetClient gets a client.\n\tGetClient(ctx context.Context, in *GetClientReq, opts ...grpc.CallOption) (*GetClientResp, error)\n\t// CreateClient creates a client.\n\tCreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error)\n\t// UpdateClient updates an existing client\n\tUpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error)\n\t// DeleteClient deletes the provided client.\n\tDeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error)\n\t// ListClients lists all client entries.\n\tListClients(ctx context.Context, in *ListClientReq, opts ...grpc.CallOption) (*ListClientResp, error)\n\t// CreatePassword creates a password.\n\tCreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error)\n\t// UpdatePassword modifies existing password.\n\tUpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error)\n\t// DeletePassword deletes the password.\n\tDeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error)\n\t// ListPassword lists all password entries.\n\tListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error)\n\t// CreateConnector creates a connector.\n\tCreateConnector(ctx context.Context, in *CreateConnectorReq, opts ...grpc.CallOption) (*CreateConnectorResp, error)\n\t// UpdateConnector modifies existing connector.\n\tUpdateConnector(ctx context.Context, in *UpdateConnectorReq, opts ...grpc.CallOption) (*UpdateConnectorResp, error)\n\t// DeleteConnector deletes the connector.\n\tDeleteConnector(ctx context.Context, in *DeleteConnectorReq, opts ...grpc.CallOption) (*DeleteConnectorResp, error)\n\t// ListConnectors lists all connector entries.\n\tListConnectors(ctx context.Context, in *ListConnectorReq, opts ...grpc.CallOption) (*ListConnectorResp, error)\n\t// GetVersion returns version information of the server.\n\tGetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error)\n\t// GetDiscovery returns discovery information of the server.\n\tGetDiscovery(ctx context.Context, in *DiscoveryReq, opts ...grpc.CallOption) (*DiscoveryResp, error)\n\t// ListRefresh lists all the refresh token entries for a particular user.\n\tListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error)\n\t// RevokeRefresh revokes the refresh token for the provided user-client pair.\n\t//\n\t// Note that each user-client pair can have only one refresh token at a time.\n\tRevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error)\n\t// VerifyPassword returns whether a password matches a hash for a specific email or not.\n\tVerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error)\n}\n\ntype dexClient struct {\n\tcc grpc.ClientConnInterface\n}\n\nfunc NewDexClient(cc grpc.ClientConnInterface) DexClient {\n\treturn &dexClient{cc}\n}\n\nfunc (c *dexClient) GetClient(ctx context.Context, in *GetClientReq, opts ...grpc.CallOption) (*GetClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(GetClientResp)\n\terr := c.cc.Invoke(ctx, Dex_GetClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) CreateClient(ctx context.Context, in *CreateClientReq, opts ...grpc.CallOption) (*CreateClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(CreateClientResp)\n\terr := c.cc.Invoke(ctx, Dex_CreateClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) UpdateClient(ctx context.Context, in *UpdateClientReq, opts ...grpc.CallOption) (*UpdateClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(UpdateClientResp)\n\terr := c.cc.Invoke(ctx, Dex_UpdateClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) DeleteClient(ctx context.Context, in *DeleteClientReq, opts ...grpc.CallOption) (*DeleteClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DeleteClientResp)\n\terr := c.cc.Invoke(ctx, Dex_DeleteClient_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) ListClients(ctx context.Context, in *ListClientReq, opts ...grpc.CallOption) (*ListClientResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListClientResp)\n\terr := c.cc.Invoke(ctx, Dex_ListClients_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) CreatePassword(ctx context.Context, in *CreatePasswordReq, opts ...grpc.CallOption) (*CreatePasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(CreatePasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_CreatePassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) UpdatePassword(ctx context.Context, in *UpdatePasswordReq, opts ...grpc.CallOption) (*UpdatePasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(UpdatePasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_UpdatePassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) DeletePassword(ctx context.Context, in *DeletePasswordReq, opts ...grpc.CallOption) (*DeletePasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DeletePasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_DeletePassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) ListPasswords(ctx context.Context, in *ListPasswordReq, opts ...grpc.CallOption) (*ListPasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListPasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_ListPasswords_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) CreateConnector(ctx context.Context, in *CreateConnectorReq, opts ...grpc.CallOption) (*CreateConnectorResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(CreateConnectorResp)\n\terr := c.cc.Invoke(ctx, Dex_CreateConnector_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) UpdateConnector(ctx context.Context, in *UpdateConnectorReq, opts ...grpc.CallOption) (*UpdateConnectorResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(UpdateConnectorResp)\n\terr := c.cc.Invoke(ctx, Dex_UpdateConnector_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) DeleteConnector(ctx context.Context, in *DeleteConnectorReq, opts ...grpc.CallOption) (*DeleteConnectorResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DeleteConnectorResp)\n\terr := c.cc.Invoke(ctx, Dex_DeleteConnector_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) ListConnectors(ctx context.Context, in *ListConnectorReq, opts ...grpc.CallOption) (*ListConnectorResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListConnectorResp)\n\terr := c.cc.Invoke(ctx, Dex_ListConnectors_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) GetVersion(ctx context.Context, in *VersionReq, opts ...grpc.CallOption) (*VersionResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(VersionResp)\n\terr := c.cc.Invoke(ctx, Dex_GetVersion_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) GetDiscovery(ctx context.Context, in *DiscoveryReq, opts ...grpc.CallOption) (*DiscoveryResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(DiscoveryResp)\n\terr := c.cc.Invoke(ctx, Dex_GetDiscovery_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) ListRefresh(ctx context.Context, in *ListRefreshReq, opts ...grpc.CallOption) (*ListRefreshResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(ListRefreshResp)\n\terr := c.cc.Invoke(ctx, Dex_ListRefresh_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) RevokeRefresh(ctx context.Context, in *RevokeRefreshReq, opts ...grpc.CallOption) (*RevokeRefreshResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(RevokeRefreshResp)\n\terr := c.cc.Invoke(ctx, Dex_RevokeRefresh_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\nfunc (c *dexClient) VerifyPassword(ctx context.Context, in *VerifyPasswordReq, opts ...grpc.CallOption) (*VerifyPasswordResp, error) {\n\tcOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)\n\tout := new(VerifyPasswordResp)\n\terr := c.cc.Invoke(ctx, Dex_VerifyPassword_FullMethodName, in, out, cOpts...)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn out, nil\n}\n\n// DexServer is the server API for Dex service.\n// All implementations must embed UnimplementedDexServer\n// for forward compatibility.\n//\n// Dex represents the dex gRPC service.\ntype DexServer interface {\n\t// GetClient gets a client.\n\tGetClient(context.Context, *GetClientReq) (*GetClientResp, error)\n\t// CreateClient creates a client.\n\tCreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error)\n\t// UpdateClient updates an existing client\n\tUpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error)\n\t// DeleteClient deletes the provided client.\n\tDeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error)\n\t// ListClients lists all client entries.\n\tListClients(context.Context, *ListClientReq) (*ListClientResp, error)\n\t// CreatePassword creates a password.\n\tCreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error)\n\t// UpdatePassword modifies existing password.\n\tUpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error)\n\t// DeletePassword deletes the password.\n\tDeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error)\n\t// ListPassword lists all password entries.\n\tListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error)\n\t// CreateConnector creates a connector.\n\tCreateConnector(context.Context, *CreateConnectorReq) (*CreateConnectorResp, error)\n\t// UpdateConnector modifies existing connector.\n\tUpdateConnector(context.Context, *UpdateConnectorReq) (*UpdateConnectorResp, error)\n\t// DeleteConnector deletes the connector.\n\tDeleteConnector(context.Context, *DeleteConnectorReq) (*DeleteConnectorResp, error)\n\t// ListConnectors lists all connector entries.\n\tListConnectors(context.Context, *ListConnectorReq) (*ListConnectorResp, error)\n\t// GetVersion returns version information of the server.\n\tGetVersion(context.Context, *VersionReq) (*VersionResp, error)\n\t// GetDiscovery returns discovery information of the server.\n\tGetDiscovery(context.Context, *DiscoveryReq) (*DiscoveryResp, error)\n\t// ListRefresh lists all the refresh token entries for a particular user.\n\tListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error)\n\t// RevokeRefresh revokes the refresh token for the provided user-client pair.\n\t//\n\t// Note that each user-client pair can have only one refresh token at a time.\n\tRevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error)\n\t// VerifyPassword returns whether a password matches a hash for a specific email or not.\n\tVerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error)\n\tmustEmbedUnimplementedDexServer()\n}\n\n// UnimplementedDexServer must be embedded to have\n// forward compatible implementations.\n//\n// NOTE: this should be embedded by value instead of pointer to avoid a nil\n// pointer dereference when methods are called.\ntype UnimplementedDexServer struct{}\n\nfunc (UnimplementedDexServer) GetClient(context.Context, *GetClientReq) (*GetClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetClient not implemented\")\n}\nfunc (UnimplementedDexServer) CreateClient(context.Context, *CreateClientReq) (*CreateClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateClient not implemented\")\n}\nfunc (UnimplementedDexServer) UpdateClient(context.Context, *UpdateClientReq) (*UpdateClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateClient not implemented\")\n}\nfunc (UnimplementedDexServer) DeleteClient(context.Context, *DeleteClientReq) (*DeleteClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeleteClient not implemented\")\n}\nfunc (UnimplementedDexServer) ListClients(context.Context, *ListClientReq) (*ListClientResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListClients not implemented\")\n}\nfunc (UnimplementedDexServer) CreatePassword(context.Context, *CreatePasswordReq) (*CreatePasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreatePassword not implemented\")\n}\nfunc (UnimplementedDexServer) UpdatePassword(context.Context, *UpdatePasswordReq) (*UpdatePasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdatePassword not implemented\")\n}\nfunc (UnimplementedDexServer) DeletePassword(context.Context, *DeletePasswordReq) (*DeletePasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeletePassword not implemented\")\n}\nfunc (UnimplementedDexServer) ListPasswords(context.Context, *ListPasswordReq) (*ListPasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListPasswords not implemented\")\n}\nfunc (UnimplementedDexServer) CreateConnector(context.Context, *CreateConnectorReq) (*CreateConnectorResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method CreateConnector not implemented\")\n}\nfunc (UnimplementedDexServer) UpdateConnector(context.Context, *UpdateConnectorReq) (*UpdateConnectorResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method UpdateConnector not implemented\")\n}\nfunc (UnimplementedDexServer) DeleteConnector(context.Context, *DeleteConnectorReq) (*DeleteConnectorResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method DeleteConnector not implemented\")\n}\nfunc (UnimplementedDexServer) ListConnectors(context.Context, *ListConnectorReq) (*ListConnectorResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListConnectors not implemented\")\n}\nfunc (UnimplementedDexServer) GetVersion(context.Context, *VersionReq) (*VersionResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetVersion not implemented\")\n}\nfunc (UnimplementedDexServer) GetDiscovery(context.Context, *DiscoveryReq) (*DiscoveryResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method GetDiscovery not implemented\")\n}\nfunc (UnimplementedDexServer) ListRefresh(context.Context, *ListRefreshReq) (*ListRefreshResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method ListRefresh not implemented\")\n}\nfunc (UnimplementedDexServer) RevokeRefresh(context.Context, *RevokeRefreshReq) (*RevokeRefreshResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method RevokeRefresh not implemented\")\n}\nfunc (UnimplementedDexServer) VerifyPassword(context.Context, *VerifyPasswordReq) (*VerifyPasswordResp, error) {\n\treturn nil, status.Errorf(codes.Unimplemented, \"method VerifyPassword not implemented\")\n}\nfunc (UnimplementedDexServer) mustEmbedUnimplementedDexServer() {}\nfunc (UnimplementedDexServer) testEmbeddedByValue()             {}\n\n// UnsafeDexServer may be embedded to opt out of forward compatibility for this service.\n// Use of this interface is not recommended, as added methods to DexServer will\n// result in compilation errors.\ntype UnsafeDexServer interface {\n\tmustEmbedUnimplementedDexServer()\n}\n\nfunc RegisterDexServer(s grpc.ServiceRegistrar, srv DexServer) {\n\t// If the following call pancis, it indicates UnimplementedDexServer was\n\t// embedded by pointer and is nil.  This will cause panics if an\n\t// unimplemented method is ever invoked, so we test this at initialization\n\t// time to prevent it from happening at runtime later due to I/O.\n\tif t, ok := srv.(interface{ testEmbeddedByValue() }); ok {\n\t\tt.testEmbeddedByValue()\n\t}\n\ts.RegisterService(&Dex_ServiceDesc, srv)\n}\n\nfunc _Dex_GetClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(GetClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).GetClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_GetClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).GetClient(ctx, req.(*GetClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).CreateClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_CreateClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).CreateClient(ctx, req.(*CreateClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdateClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).UpdateClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_UpdateClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).UpdateClient(ctx, req.(*UpdateClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).DeleteClient(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_DeleteClient_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).DeleteClient(ctx, req.(*DeleteClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_ListClients_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListClientReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).ListClients(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_ListClients_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).ListClients(ctx, req.(*ListClientReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_CreatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreatePasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).CreatePassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_CreatePassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).CreatePassword(ctx, req.(*CreatePasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_UpdatePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdatePasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).UpdatePassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_UpdatePassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).UpdatePassword(ctx, req.(*UpdatePasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_DeletePassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeletePasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).DeletePassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_DeletePassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).DeletePassword(ctx, req.(*DeletePasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_ListPasswords_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListPasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).ListPasswords(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_ListPasswords_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).ListPasswords(ctx, req.(*ListPasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_CreateConnector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(CreateConnectorReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).CreateConnector(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_CreateConnector_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).CreateConnector(ctx, req.(*CreateConnectorReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_UpdateConnector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(UpdateConnectorReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).UpdateConnector(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_UpdateConnector_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).UpdateConnector(ctx, req.(*UpdateConnectorReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_DeleteConnector_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DeleteConnectorReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).DeleteConnector(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_DeleteConnector_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).DeleteConnector(ctx, req.(*DeleteConnectorReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_ListConnectors_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListConnectorReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).ListConnectors(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_ListConnectors_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).ListConnectors(ctx, req.(*ListConnectorReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VersionReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).GetVersion(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_GetVersion_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).GetVersion(ctx, req.(*VersionReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_GetDiscovery_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(DiscoveryReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).GetDiscovery(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_GetDiscovery_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).GetDiscovery(ctx, req.(*DiscoveryReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_ListRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(ListRefreshReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).ListRefresh(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_ListRefresh_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).ListRefresh(ctx, req.(*ListRefreshReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_RevokeRefresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(RevokeRefreshReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).RevokeRefresh(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_RevokeRefresh_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).RevokeRefresh(ctx, req.(*RevokeRefreshReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\nfunc _Dex_VerifyPassword_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {\n\tin := new(VerifyPasswordReq)\n\tif err := dec(in); err != nil {\n\t\treturn nil, err\n\t}\n\tif interceptor == nil {\n\t\treturn srv.(DexServer).VerifyPassword(ctx, in)\n\t}\n\tinfo := &grpc.UnaryServerInfo{\n\t\tServer:     srv,\n\t\tFullMethod: Dex_VerifyPassword_FullMethodName,\n\t}\n\thandler := func(ctx context.Context, req interface{}) (interface{}, error) {\n\t\treturn srv.(DexServer).VerifyPassword(ctx, req.(*VerifyPasswordReq))\n\t}\n\treturn interceptor(ctx, in, info, handler)\n}\n\n// Dex_ServiceDesc is the grpc.ServiceDesc for Dex service.\n// It's only intended for direct use with grpc.RegisterService,\n// and not to be introspected or modified (even as a copy)\nvar Dex_ServiceDesc = grpc.ServiceDesc{\n\tServiceName: \"api.Dex\",\n\tHandlerType: (*DexServer)(nil),\n\tMethods: []grpc.MethodDesc{\n\t\t{\n\t\t\tMethodName: \"GetClient\",\n\t\t\tHandler:    _Dex_GetClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateClient\",\n\t\t\tHandler:    _Dex_CreateClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateClient\",\n\t\t\tHandler:    _Dex_UpdateClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeleteClient\",\n\t\t\tHandler:    _Dex_DeleteClient_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListClients\",\n\t\t\tHandler:    _Dex_ListClients_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreatePassword\",\n\t\t\tHandler:    _Dex_CreatePassword_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdatePassword\",\n\t\t\tHandler:    _Dex_UpdatePassword_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeletePassword\",\n\t\t\tHandler:    _Dex_DeletePassword_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListPasswords\",\n\t\t\tHandler:    _Dex_ListPasswords_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"CreateConnector\",\n\t\t\tHandler:    _Dex_CreateConnector_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"UpdateConnector\",\n\t\t\tHandler:    _Dex_UpdateConnector_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"DeleteConnector\",\n\t\t\tHandler:    _Dex_DeleteConnector_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListConnectors\",\n\t\t\tHandler:    _Dex_ListConnectors_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetVersion\",\n\t\t\tHandler:    _Dex_GetVersion_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"GetDiscovery\",\n\t\t\tHandler:    _Dex_GetDiscovery_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"ListRefresh\",\n\t\t\tHandler:    _Dex_ListRefresh_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"RevokeRefresh\",\n\t\t\tHandler:    _Dex_RevokeRefresh_Handler,\n\t\t},\n\t\t{\n\t\t\tMethodName: \"VerifyPassword\",\n\t\t\tHandler:    _Dex_VerifyPassword_Handler,\n\t\t},\n\t},\n\tStreams:  []grpc.StreamDesc{},\n\tMetadata: \"api/v2/api.proto\",\n}\n"
  },
  {
    "path": "api/v2/go.mod",
    "content": "module github.com/dexidp/dex/api/v2\n\ngo 1.25.0\n\nrequire (\n\tgoogle.golang.org/grpc v1.79.3\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tgolang.org/x/net v0.52.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/text v0.35.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect\n)\n"
  },
  {
    "path": "api/v2/go.sum",
    "content": "github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngolang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=\ngolang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=\ngolang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\n"
  },
  {
    "path": "cmd/dex/config.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"os\"\n\t\"strings\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n\n\t\"github.com/dexidp/dex/pkg/featureflags\"\n\t\"github.com/dexidp/dex/server\"\n\t\"github.com/dexidp/dex/server/signer\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent\"\n\t\"github.com/dexidp/dex/storage/etcd\"\n\t\"github.com/dexidp/dex/storage/kubernetes\"\n\t\"github.com/dexidp/dex/storage/memory\"\n\t\"github.com/dexidp/dex/storage/sql\"\n)\n\nfunc configUnmarshaller(b []byte, v interface{}) error {\n\tif !featureflags.ConfigDisallowUnknownFields.Enabled() {\n\t\treturn json.Unmarshal(b, v)\n\t}\n\tdec := json.NewDecoder(bytes.NewReader(b))\n\tdec.DisallowUnknownFields()\n\treturn dec.Decode(v)\n}\n\n// Config is the config format for the main application.\ntype Config struct {\n\tIssuer    string    `json:\"issuer\"`\n\tStorage   Storage   `json:\"storage\"`\n\tWeb       Web       `json:\"web\"`\n\tTelemetry Telemetry `json:\"telemetry\"`\n\tOAuth2    OAuth2    `json:\"oauth2\"`\n\tGRPC      GRPC      `json:\"grpc\"`\n\tExpiry    Expiry    `json:\"expiry\"`\n\tLogger    Logger    `json:\"logger\"`\n\n\tFrontend server.WebConfig `json:\"frontend\"`\n\n\t// Signer configuration controls signing of JWT tokens issued by Dex.\n\tSigner Signer `json:\"signer\"`\n\n\t// StaticConnectors are user defined connectors specified in the ConfigMap\n\t// Write operations, like updating a connector, will fail.\n\tStaticConnectors []Connector `json:\"connectors\"`\n\n\t// StaticClients cause the server to use this list of clients rather than\n\t// querying the storage. Write operations, like creating a client, will fail.\n\tStaticClients []storage.Client `json:\"staticClients\"`\n\n\t// If enabled, the server will maintain a list of passwords which can be used\n\t// to identify a user.\n\tEnablePasswordDB bool `json:\"enablePasswordDB\"`\n\n\t// StaticPasswords cause the server use this list of passwords rather than\n\t// querying the storage. Cannot be specified without enabling a passwords\n\t// database.\n\tStaticPasswords []password `json:\"staticPasswords\"`\n\n\t// Sessions holds authentication session configuration.\n\t// Requires DEX_SESSIONS_ENABLED=true feature flag.\n\tSessions *Sessions `json:\"sessions\"`\n\n\t// MFA holds multi-factor authentication configuration.\n\tMFA MFAConfig `json:\"mfa\"`\n}\n\n// MFAConfig holds multi-factor authentication settings.\ntype MFAConfig struct {\n\t// Authenticators defines MFA providers available for clients to reference.\n\tAuthenticators []MFAAuthenticator `json:\"authenticators\"`\n\n\t// DefaultMFAChain is the default ordered list of authenticator IDs applied\n\t// to clients that don't specify their own mfaChain. Empty means no MFA by default.\n\tDefaultMFAChain []string `json:\"defaultMFAChain\"`\n}\n\n// Validate the configuration\nfunc (c Config) Validate() error {\n\t// Fast checks. Perform these first for a more responsive CLI.\n\tchecks := []struct {\n\t\tbad    bool\n\t\terrMsg string\n\t}{\n\t\t{c.Issuer == \"\", \"no issuer specified in config file\"},\n\t\t{!c.EnablePasswordDB && len(c.StaticPasswords) != 0, \"cannot specify static passwords without enabling password db\"},\n\t\t{c.Storage.Config == nil, \"no storage supplied in config file\"},\n\t\t{c.Web.HTTP == \"\" && c.Web.HTTPS == \"\", \"must supply a HTTP/HTTPS  address to listen on\"},\n\t\t{c.Web.HTTPS != \"\" && c.Web.TLSCert == \"\", \"no cert specified for HTTPS\"},\n\t\t{c.Web.HTTPS != \"\" && c.Web.TLSKey == \"\", \"no private key specified for HTTPS\"},\n\t\t{c.Web.TLSMinVersion != \"\" && c.Web.TLSMinVersion != \"1.2\" && c.Web.TLSMinVersion != \"1.3\", \"supported TLS versions are: 1.2, 1.3\"},\n\t\t{c.Web.TLSMaxVersion != \"\" && c.Web.TLSMaxVersion != \"1.2\" && c.Web.TLSMaxVersion != \"1.3\", \"supported TLS versions are: 1.2, 1.3\"},\n\t\t{c.Web.TLSMaxVersion != \"\" && c.Web.TLSMinVersion != \"\" && c.Web.TLSMinVersion > c.Web.TLSMaxVersion, \"TLSMinVersion greater than TLSMaxVersion\"},\n\t\t{c.GRPC.TLSCert != \"\" && c.GRPC.Addr == \"\", \"no address specified for gRPC\"},\n\t\t{c.GRPC.TLSKey != \"\" && c.GRPC.Addr == \"\", \"no address specified for gRPC\"},\n\t\t{(c.GRPC.TLSCert == \"\") != (c.GRPC.TLSKey == \"\"), \"must specific both a gRPC TLS cert and key\"},\n\t\t{c.GRPC.TLSCert == \"\" && c.GRPC.TLSClientCA != \"\", \"cannot specify gRPC TLS client CA without a gRPC TLS cert\"},\n\t\t{c.GRPC.TLSMinVersion != \"\" && c.GRPC.TLSMinVersion != \"1.2\" && c.GRPC.TLSMinVersion != \"1.3\", \"supported TLS versions are: 1.2, 1.3\"},\n\t\t{c.GRPC.TLSMaxVersion != \"\" && c.GRPC.TLSMaxVersion != \"1.2\" && c.GRPC.TLSMaxVersion != \"1.3\", \"supported TLS versions are: 1.2, 1.3\"},\n\t\t{c.GRPC.TLSMaxVersion != \"\" && c.GRPC.TLSMinVersion != \"\" && c.GRPC.TLSMinVersion > c.GRPC.TLSMaxVersion, \"TLSMinVersion greater than TLSMaxVersion\"},\n\t}\n\n\tvar checkErrors []string\n\n\tfor _, check := range checks {\n\t\tif check.bad {\n\t\t\tcheckErrors = append(checkErrors, check.errMsg)\n\t\t}\n\t}\n\n\tif len(checkErrors) != 0 {\n\t\treturn fmt.Errorf(\"invalid Config:\\n\\t-\\t%s\", strings.Join(checkErrors, \"\\n\\t-\\t\"))\n\t}\n\n\tif c.Sessions != nil && !featureflags.SessionsEnabled.Enabled() {\n\t\treturn fmt.Errorf(\"sessions config requires sessions to be enabled (DEX_SESSIONS_ENABLED=true)\")\n\t}\n\n\tif err := c.validateMFA(); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c Config) validateMFA() error {\n\tmfa := c.MFA\n\tif len(mfa.Authenticators) == 0 && len(mfa.DefaultMFAChain) == 0 {\n\t\treturn nil\n\t}\n\n\tif !featureflags.SessionsEnabled.Enabled() {\n\t\treturn fmt.Errorf(\"mfa requires sessions to be enabled (DEX_SESSIONS_ENABLED=true)\")\n\t}\n\n\tknownTypes := map[string]bool{\"TOTP\": true}\n\tids := make(map[string]bool, len(mfa.Authenticators))\n\n\tfor _, auth := range mfa.Authenticators {\n\t\tif auth.ID == \"\" {\n\t\t\treturn fmt.Errorf(\"mfa.authenticators: authenticator must have an id\")\n\t\t}\n\t\tif ids[auth.ID] {\n\t\t\treturn fmt.Errorf(\"mfa.authenticators: duplicate authenticator id %q\", auth.ID)\n\t\t}\n\t\tids[auth.ID] = true\n\n\t\tif !knownTypes[auth.Type] {\n\t\t\treturn fmt.Errorf(\"mfa.authenticators: unknown type %q for authenticator %q\", auth.Type, auth.ID)\n\t\t}\n\t}\n\n\tfor _, authID := range mfa.DefaultMFAChain {\n\t\tif !ids[authID] {\n\t\t\treturn fmt.Errorf(\"mfa.defaultMFAChain: references unknown authenticator %q\", authID)\n\t\t}\n\t}\n\n\tfor _, client := range c.StaticClients {\n\t\tfor _, authID := range client.MFAChain {\n\t\t\tif !ids[authID] {\n\t\t\t\treturn fmt.Errorf(\"staticClients: client %q references unknown MFA authenticator %q\", client.ID, authID)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\ntype password storage.Password\n\nfunc (p *password) UnmarshalJSON(b []byte) error {\n\tvar data struct {\n\t\tEmail             string   `json:\"email\"`\n\t\tUsername          string   `json:\"username\"`\n\t\tName              string   `json:\"name\"`\n\t\tPreferredUsername string   `json:\"preferredUsername\"`\n\t\tEmailVerified     *bool    `json:\"emailVerified\"`\n\t\tUserID            string   `json:\"userID\"`\n\t\tHash              string   `json:\"hash\"`\n\t\tHashFromEnv       string   `json:\"hashFromEnv\"`\n\t\tGroups            []string `json:\"groups\"`\n\t}\n\tif err := configUnmarshaller(b, &data); err != nil {\n\t\treturn err\n\t}\n\t*p = password(storage.Password{\n\t\tEmail:             data.Email,\n\t\tUsername:          data.Username,\n\t\tName:              data.Name,\n\t\tPreferredUsername: data.PreferredUsername,\n\t\tEmailVerified:     data.EmailVerified,\n\t\tUserID:            data.UserID,\n\t\tGroups:            data.Groups,\n\t})\n\tif len(data.Hash) == 0 && len(data.HashFromEnv) > 0 {\n\t\tdata.Hash = os.Getenv(data.HashFromEnv)\n\t}\n\tif len(data.Hash) == 0 {\n\t\treturn fmt.Errorf(\"no password hash provided\")\n\t}\n\n\t// If this value is a valid bcrypt, use it.\n\t_, bcryptErr := bcrypt.Cost([]byte(data.Hash))\n\tif bcryptErr == nil {\n\t\tp.Hash = []byte(data.Hash)\n\t\treturn nil\n\t}\n\n\t// For backwards compatibility try to base64 decode this value.\n\thashBytes, err := base64.StdEncoding.DecodeString(data.Hash)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"malformed bcrypt hash: %v\", bcryptErr)\n\t}\n\tif _, err := bcrypt.Cost(hashBytes); err != nil {\n\t\treturn fmt.Errorf(\"malformed bcrypt hash: %v\", err)\n\t}\n\tp.Hash = hashBytes\n\treturn nil\n}\n\n// OAuth2 describes enabled OAuth2 extensions.\ntype OAuth2 struct {\n\t// list of allowed grant types,\n\t// defaults to all supported types\n\tGrantTypes []string `json:\"grantTypes\"`\n\n\tResponseTypes []string `json:\"responseTypes\"`\n\t// If specified, do not prompt the user to approve client authorization. The\n\t// act of logging in implies authorization.\n\tSkipApprovalScreen bool `json:\"skipApprovalScreen\"`\n\t// If specified, show the connector selection screen even if there's only one\n\tAlwaysShowLoginScreen bool `json:\"alwaysShowLoginScreen\"`\n\t// This is the connector that can be used for password grant\n\tPasswordConnector string `json:\"passwordConnector\"`\n\t// PKCE configuration\n\tPKCE PKCE `json:\"pkce\"`\n}\n\n// PKCE holds the PKCE (Proof Key for Code Exchange) configuration.\ntype PKCE struct {\n\t// If true, PKCE is required for all authorization code flows.\n\tEnforce bool `json:\"enforce\"`\n\t// Supported code challenge methods. Defaults to [\"S256\", \"plain\"].\n\tCodeChallengeMethodsSupported []string `json:\"codeChallengeMethodsSupported\"`\n}\n\n// Web is the config format for the HTTP server.\ntype Web struct {\n\tHTTP           string         `json:\"http\"`\n\tHTTPS          string         `json:\"https\"`\n\tHeaders        Headers        `json:\"headers\"`\n\tTLSCert        string         `json:\"tlsCert\"`\n\tTLSKey         string         `json:\"tlsKey\"`\n\tTLSMinVersion  string         `json:\"tlsMinVersion\"`\n\tTLSMaxVersion  string         `json:\"tlsMaxVersion\"`\n\tAllowedOrigins []string       `json:\"allowedOrigins\"`\n\tAllowedHeaders []string       `json:\"allowedHeaders\"`\n\tClientRemoteIP ClientRemoteIP `json:\"clientRemoteIP\"`\n}\n\ntype ClientRemoteIP struct {\n\tHeader         string   `json:\"header\"`\n\tTrustedProxies []string `json:\"trustedProxies\"`\n}\n\nfunc (cr *ClientRemoteIP) ParseTrustedProxies() ([]netip.Prefix, error) {\n\tif cr == nil {\n\t\treturn nil, nil\n\t}\n\n\ttrusted := make([]netip.Prefix, 0, len(cr.TrustedProxies))\n\tfor _, cidr := range cr.TrustedProxies {\n\t\tipNet, err := netip.ParsePrefix(cidr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse CIDR %q: %v\", cidr, err)\n\t\t}\n\t\ttrusted = append(trusted, ipNet)\n\t}\n\n\treturn trusted, nil\n}\n\ntype Headers struct {\n\t// Set the Content-Security-Policy header to HTTP responses.\n\t// Unset if blank.\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy\n\tContentSecurityPolicy string `json:\"Content-Security-Policy\"`\n\t// Set the X-Frame-Options header to HTTP responses.\n\t// Unset if blank. Accepted values are deny and sameorigin.\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options\n\tXFrameOptions string `json:\"X-Frame-Options\"`\n\t// Set the X-Content-Type-Options header to HTTP responses.\n\t// Unset if blank. Accepted value is nosniff.\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options\n\tXContentTypeOptions string `json:\"X-Content-Type-Options\"`\n\t// Set the X-XSS-Protection header to all responses.\n\t// Unset if blank.\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection\n\tXXSSProtection string `json:\"X-XSS-Protection\"`\n\t// Set the Strict-Transport-Security header to HTTP responses.\n\t// Unset if blank.\n\t// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security\n\tStrictTransportSecurity string `json:\"Strict-Transport-Security\"`\n}\n\nfunc (h *Headers) ToHTTPHeader() http.Header {\n\tif h == nil {\n\t\treturn make(map[string][]string)\n\t}\n\theader := make(map[string][]string)\n\tif h.ContentSecurityPolicy != \"\" {\n\t\theader[\"Content-Security-Policy\"] = []string{h.ContentSecurityPolicy}\n\t}\n\tif h.XFrameOptions != \"\" {\n\t\theader[\"X-Frame-Options\"] = []string{h.XFrameOptions}\n\t}\n\tif h.XContentTypeOptions != \"\" {\n\t\theader[\"X-Content-Type-Options\"] = []string{h.XContentTypeOptions}\n\t}\n\tif h.XXSSProtection != \"\" {\n\t\theader[\"X-XSS-Protection\"] = []string{h.XXSSProtection}\n\t}\n\tif h.StrictTransportSecurity != \"\" {\n\t\theader[\"Strict-Transport-Security\"] = []string{h.StrictTransportSecurity}\n\t}\n\treturn header\n}\n\n// Telemetry is the config format for telemetry including the HTTP server config.\ntype Telemetry struct {\n\tHTTP string `json:\"http\"`\n\t// EnableProfiling makes profiling endpoints available via web interface host:port/debug/pprof/\n\tEnableProfiling bool `json:\"enableProfiling\"`\n}\n\n// GRPC is the config for the gRPC API.\ntype GRPC struct {\n\t// The port to listen on.\n\tAddr          string `json:\"addr\"`\n\tTLSCert       string `json:\"tlsCert\"`\n\tTLSKey        string `json:\"tlsKey\"`\n\tTLSClientCA   string `json:\"tlsClientCA\"`\n\tTLSMinVersion string `json:\"tlsMinVersion\"`\n\tTLSMaxVersion string `json:\"tlsMaxVersion\"`\n\tReflection    bool   `json:\"reflection\"`\n}\n\n// Storage holds app's storage configuration.\ntype Storage struct {\n\tType   string        `json:\"type\"`\n\tConfig StorageConfig `json:\"config\"`\n}\n\n// StorageConfig is a configuration that can create a storage.\ntype StorageConfig interface {\n\tOpen(logger *slog.Logger) (storage.Storage, error)\n}\n\nvar (\n\t_ StorageConfig = (*etcd.Etcd)(nil)\n\t_ StorageConfig = (*kubernetes.Config)(nil)\n\t_ StorageConfig = (*memory.Config)(nil)\n\t_ StorageConfig = (*sql.SQLite3)(nil)\n\t_ StorageConfig = (*sql.Postgres)(nil)\n\t_ StorageConfig = (*sql.MySQL)(nil)\n\t_ StorageConfig = (*ent.SQLite3)(nil)\n\t_ StorageConfig = (*ent.Postgres)(nil)\n\t_ StorageConfig = (*ent.MySQL)(nil)\n)\n\nfunc getORMBasedSQLStorage(normal, entBased func() StorageConfig) func() StorageConfig {\n\treturn func() StorageConfig {\n\t\tif featureflags.EntEnabled.Enabled() {\n\t\t\treturn entBased()\n\t\t}\n\t\treturn normal()\n\t}\n}\n\n// Recursively expand environment variables in the map to avoid\n// issues with JSON special characters and escapes\nfunc expandEnvInMap(m map[string]interface{}) {\n\tfor k, v := range m {\n\t\tswitch vt := v.(type) {\n\t\tcase string:\n\t\t\tm[k] = os.ExpandEnv(vt)\n\t\tcase map[string]interface{}:\n\t\t\texpandEnvInMap(vt)\n\t\tcase []interface{}:\n\t\t\tfor i, item := range vt {\n\t\t\t\tif itemMap, ok := item.(map[string]interface{}); ok {\n\t\t\t\t\texpandEnvInMap(itemMap)\n\t\t\t\t} else if itemString, ok := item.(string); ok {\n\t\t\t\t\tvt[i] = os.ExpandEnv(itemString)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nvar storages = map[string]func() StorageConfig{\n\t\"etcd\":       func() StorageConfig { return new(etcd.Etcd) },\n\t\"kubernetes\": func() StorageConfig { return new(kubernetes.Config) },\n\t\"memory\":     func() StorageConfig { return new(memory.Config) },\n\t\"sqlite3\":    getORMBasedSQLStorage(func() StorageConfig { return new(sql.SQLite3) }, func() StorageConfig { return new(ent.SQLite3) }),\n\t\"postgres\":   getORMBasedSQLStorage(func() StorageConfig { return new(sql.Postgres) }, func() StorageConfig { return new(ent.Postgres) }),\n\t\"mysql\":      getORMBasedSQLStorage(func() StorageConfig { return new(sql.MySQL) }, func() StorageConfig { return new(ent.MySQL) }),\n}\n\n// UnmarshalJSON allows Storage to implement the unmarshaler interface to\n// dynamically determine the type of the storage config.\nfunc (s *Storage) UnmarshalJSON(b []byte) error {\n\tvar store struct {\n\t\tType   string          `json:\"type\"`\n\t\tConfig json.RawMessage `json:\"config\"`\n\t}\n\tif err := configUnmarshaller(b, &store); err != nil {\n\t\treturn fmt.Errorf(\"parse storage: %v\", err)\n\t}\n\tf, ok := storages[store.Type]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unknown storage type %q\", store.Type)\n\t}\n\n\tstorageConfig := f()\n\tif len(store.Config) != 0 {\n\t\tdata := []byte(store.Config)\n\t\tif featureflags.ExpandEnv.Enabled() {\n\t\t\tvar rawMap map[string]interface{}\n\t\t\tif err := configUnmarshaller(store.Config, &rawMap); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal config for env expansion: %v\", err)\n\t\t\t}\n\n\t\t\t// Recursively expand environment variables in the map to avoid\n\t\t\t// issues with JSON special characters and escapes\n\t\t\texpandEnvInMap(rawMap)\n\n\t\t\t// Marshal the expanded map back to JSON\n\t\t\texpandedData, err := json.Marshal(rawMap)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshal expanded config: %v\", err)\n\t\t\t}\n\n\t\t\tdata = expandedData\n\t\t}\n\n\t\tif err := configUnmarshaller(data, storageConfig); err != nil {\n\t\t\treturn fmt.Errorf(\"parse storage config: %v\", err)\n\t\t}\n\t}\n\t*s = Storage{\n\t\tType:   store.Type,\n\t\tConfig: storageConfig,\n\t}\n\treturn nil\n}\n\n// Signer holds app's signer configuration.\ntype Signer struct {\n\tType   string       `json:\"type\"`\n\tConfig SignerConfig `json:\"config\"`\n}\n\n// SignerConfig is a configuration that can create a signer.\ntype SignerConfig interface{}\n\nvar (\n\t_ SignerConfig = (*signer.LocalConfig)(nil)\n\t_ SignerConfig = (*signer.VaultConfig)(nil)\n)\n\nvar signerConfigs = map[string]func() SignerConfig{\n\t\"local\": func() SignerConfig { return new(signer.LocalConfig) },\n\t\"vault\": func() SignerConfig { return new(signer.VaultConfig) },\n}\n\n// UnmarshalJSON allows Signer to implement the unmarshaler interface to\n// dynamically determine the type of the signer config.\nfunc (s *Signer) UnmarshalJSON(b []byte) error {\n\tvar signerData struct {\n\t\tType   string          `json:\"type\"`\n\t\tConfig json.RawMessage `json:\"config\"`\n\t}\n\tif err := json.Unmarshal(b, &signerData); err != nil {\n\t\treturn fmt.Errorf(\"parse signer: %v\", err)\n\t}\n\n\tf, ok := signerConfigs[signerData.Type]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unknown signer type %q\", signerData.Type)\n\t}\n\n\tsignerConfig := f()\n\tif len(signerData.Config) != 0 {\n\t\tdata := []byte(signerData.Config)\n\t\tif featureflags.ExpandEnv.Enabled() {\n\t\t\tvar rawMap map[string]interface{}\n\t\t\tif err := json.Unmarshal(signerData.Config, &rawMap); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal config for env expansion: %v\", err)\n\t\t\t}\n\n\t\t\t// Recursively expand environment variables in the map\n\t\t\texpandEnvInMap(rawMap)\n\n\t\t\t// Marshal the expanded map back to JSON\n\t\t\texpandedData, err := json.Marshal(rawMap)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshal expanded config: %v\", err)\n\t\t\t}\n\n\t\t\tdata = expandedData\n\t\t}\n\n\t\tif err := json.Unmarshal(data, signerConfig); err != nil {\n\t\t\treturn fmt.Errorf(\"parse signer config: %v\", err)\n\t\t}\n\t}\n\n\t*s = Signer{\n\t\tType:   signerData.Type,\n\t\tConfig: signerConfig,\n\t}\n\treturn nil\n}\n\n// Connector is a magical type that can unmarshal YAML dynamically. The\n// Type field determines the connector type, which is then customized for Config.\ntype Connector struct {\n\tType string `json:\"type\"`\n\tName string `json:\"name\"`\n\tID   string `json:\"id\"`\n\n\tConfig     server.ConnectorConfig `json:\"config\"`\n\tGrantTypes []string               `json:\"grantTypes\"`\n}\n\n// UnmarshalJSON allows Connector to implement the unmarshaler interface to\n// dynamically determine the type of the connector config.\nfunc (c *Connector) UnmarshalJSON(b []byte) error {\n\tvar conn struct {\n\t\tType string `json:\"type\"`\n\t\tName string `json:\"name\"`\n\t\tID   string `json:\"id\"`\n\n\t\tConfig     json.RawMessage `json:\"config\"`\n\t\tGrantTypes []string        `json:\"grantTypes\"`\n\t}\n\tif err := configUnmarshaller(b, &conn); err != nil {\n\t\treturn fmt.Errorf(\"parse connector: %v\", err)\n\t}\n\tf, ok := server.ConnectorsConfig[conn.Type]\n\tif !ok {\n\t\treturn fmt.Errorf(\"unknown connector type %q\", conn.Type)\n\t}\n\n\tconnConfig := f()\n\tif len(conn.Config) != 0 {\n\t\tdata := []byte(conn.Config)\n\t\tif featureflags.ExpandEnv.Enabled() {\n\t\t\tvar rawMap map[string]interface{}\n\t\t\tif err := configUnmarshaller(conn.Config, &rawMap); err != nil {\n\t\t\t\treturn fmt.Errorf(\"unmarshal config for env expansion: %v\", err)\n\t\t\t}\n\n\t\t\t// Recursively expand environment variables in the map to avoid\n\t\t\t// issues with JSON special characters and escapes\n\t\t\texpandEnvInMap(rawMap)\n\n\t\t\t// Marshal the expanded map back to JSON\n\t\t\texpandedData, err := json.Marshal(rawMap)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"marshal expanded config: %v\", err)\n\t\t\t}\n\n\t\t\tdata = expandedData\n\t\t}\n\n\t\tif err := configUnmarshaller(data, connConfig); err != nil {\n\t\t\treturn fmt.Errorf(\"parse connector config: %v\", err)\n\t\t}\n\t}\n\n\t*c = Connector{\n\t\tType:       conn.Type,\n\t\tName:       conn.Name,\n\t\tID:         conn.ID,\n\t\tConfig:     connConfig,\n\t\tGrantTypes: conn.GrantTypes,\n\t}\n\treturn nil\n}\n\n// ToStorageConnector converts an object to storage connector type.\nfunc ToStorageConnector(c Connector) (storage.Connector, error) {\n\tdata, err := json.Marshal(c.Config)\n\tif err != nil {\n\t\treturn storage.Connector{}, fmt.Errorf(\"failed to marshal connector config: %v\", err)\n\t}\n\n\treturn storage.Connector{\n\t\tID:         c.ID,\n\t\tType:       c.Type,\n\t\tName:       c.Name,\n\t\tConfig:     data,\n\t\tGrantTypes: c.GrantTypes,\n\t}, nil\n}\n\n// Expiry holds configuration for the validity period of components.\ntype Expiry struct {\n\t// SigningKeys defines the duration of time after which the SigningKeys will be rotated.\n\tSigningKeys string `json:\"signingKeys\"`\n\n\t// IdTokens defines the duration of time for which the IdTokens will be valid.\n\tIDTokens string `json:\"idTokens\"`\n\n\t// AuthRequests defines the duration of time for which the AuthRequests will be valid.\n\tAuthRequests string `json:\"authRequests\"`\n\n\t// DeviceRequests defines the duration of time for which the DeviceRequests will be valid.\n\tDeviceRequests string `json:\"deviceRequests\"`\n\n\t// RefreshTokens defines refresh tokens expiry policy\n\tRefreshTokens RefreshToken `json:\"refreshTokens\"`\n}\n\n// Logger holds configuration required to customize logging for dex.\ntype Logger struct {\n\t// Level sets logging level severity.\n\tLevel slog.Level `json:\"level\"`\n\n\t// Format specifies the format to be used for logging.\n\tFormat string `json:\"format\"`\n\n\t// ExcludeFields specifies log attribute keys that should be dropped from all\n\t// log output. This is useful for suppressing PII fields like email, username,\n\t// preferred_username, or groups in environments subject to GDPR or similar\n\t// data-handling constraints.\n\tExcludeFields []string `json:\"excludeFields\"`\n}\n\ntype RefreshToken struct {\n\tDisableRotation   bool   `json:\"disableRotation\"`\n\tReuseInterval     string `json:\"reuseInterval\"`\n\tAbsoluteLifetime  string `json:\"absoluteLifetime\"`\n\tValidIfNotUsedFor string `json:\"validIfNotUsedFor\"`\n}\n\n// Sessions holds authentication session configuration.\ntype Sessions struct {\n\t// CookieName is the name of the session cookie. Defaults to \"dex_session\".\n\tCookieName string `json:\"cookieName\"`\n\t// AbsoluteLifetime is the maximum session lifetime from creation. Defaults to \"24h\".\n\tAbsoluteLifetime string `json:\"absoluteLifetime\"`\n\t// ValidIfNotUsedFor is the idle timeout. Defaults to \"1h\".\n\tValidIfNotUsedFor string `json:\"validIfNotUsedFor\"`\n\t// RememberMeCheckedByDefault controls the default state of the \"remember me\" checkbox.\n\tRememberMeCheckedByDefault *bool `json:\"rememberMeCheckedByDefault\"`\n}\n\n// MFAAuthenticator defines a multi-factor authentication provider.\ntype MFAAuthenticator struct {\n\tID     string          `json:\"id\"`\n\tType   string          `json:\"type\"`\n\tConfig json.RawMessage `json:\"config\"`\n\n\t// ConnectorTypes limits this authenticator to specific connector types (e.g., \"ldap\", \"oidc\", \"saml\").\n\t// If empty, the authenticator applies to all connector types.\n\tConnectorTypes []string `json:\"connectorTypes\"`\n}\n\n// TOTPConfig holds configuration for a TOTP authenticator.\ntype TOTPConfig struct {\n\t// Issuer is the name of the service shown in the authenticator app.\n\tIssuer string `json:\"issuer\"`\n}\n"
  },
  {
    "path": "cmd/dex/config_test.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/ghodss/yaml\"\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/dexidp/dex/connector/mock\"\n\t\"github.com/dexidp/dex/connector/oidc\"\n\t\"github.com/dexidp/dex/server\"\n\t\"github.com/dexidp/dex/server/signer\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/sql\"\n)\n\nvar _ = yaml.YAMLToJSON\n\nfunc boolPtr(v bool) *bool {\n\treturn &v\n}\n\nfunc TestValidConfiguration(t *testing.T) {\n\tconfiguration := Config{\n\t\tIssuer: \"http://127.0.0.1:5556/dex\",\n\t\tStorage: Storage{\n\t\t\tType: \"sqlite3\",\n\t\t\tConfig: &sql.SQLite3{\n\t\t\t\tFile: \"examples/dex.db\",\n\t\t\t},\n\t\t},\n\t\tWeb: Web{\n\t\t\tHTTP: \"127.0.0.1:5556\",\n\t\t},\n\t\tStaticConnectors: []Connector{\n\t\t\t{\n\t\t\t\tType:   \"mockCallback\",\n\t\t\t\tID:     \"mock\",\n\t\t\t\tName:   \"Example\",\n\t\t\t\tConfig: &mock.CallbackConfig{},\n\t\t\t},\n\t\t},\n\t}\n\n\tif err := configuration.Validate(); err != nil {\n\t\tt.Fatalf(\"this configuration should have been valid: %v\", err)\n\t}\n}\n\nfunc TestInvalidConfiguration(t *testing.T) {\n\tconfiguration := Config{}\n\terr := configuration.Validate()\n\tif err == nil {\n\t\tt.Fatal(\"this configuration should be invalid\")\n\t}\n\tgot := err.Error()\n\twanted := `invalid Config:\n\t-\tno issuer specified in config file\n\t-\tno storage supplied in config file\n\t-\tmust supply a HTTP/HTTPS  address to listen on`\n\tif got != wanted {\n\t\tt.Fatalf(\"Expected error message to be %q, got %q\", wanted, got)\n\t}\n}\n\nfunc TestUnmarshalConfig(t *testing.T) {\n\trawConfig := []byte(`\nissuer: http://127.0.0.1:5556/dex\nstorage:\n  type: postgres\n  config:\n    host: 10.0.0.1\n    port: 65432\n    maxOpenConns: 5\n    maxIdleConns: 3\n    connMaxLifetime: 30\n    connectionTimeout: 3\nweb:\n  https: 127.0.0.1:5556\n  tlsMinVersion: 1.3\n  tlsMaxVersion: 1.2\n  headers:\n    Strict-Transport-Security: \"max-age=31536000; includeSubDomains\"\n\nfrontend:\n  dir: ./web\n  extra:\n    foo: bar\n\nstaticClients:\n- id: example-app\n  redirectURIs:\n  - 'http://127.0.0.1:5555/callback'\n  name: 'Example App'\n  secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n\noauth2:\n  alwaysShowLoginScreen: true\n  grantTypes:\n  - refresh_token\n  - \"urn:ietf:params:oauth:grant-type:token-exchange\"\n\nconnectors:\n- type: mockCallback\n  id: mock\n  name: Example\n  grantTypes:\n  - authorization_code\n  - \"urn:ietf:params:oauth:grant-type:token-exchange\"\n- type: oidc\n  id: google\n  name: Google\n  config:\n    issuer: https://accounts.google.com\n    clientID: foo\n    clientSecret: bar\n    redirectURI: http://127.0.0.1:5556/dex/callback/google\n\nenablePasswordDB: true\nstaticPasswords:\n- email: \"admin@example.com\"\n  # bcrypt hash of the string \"password\"\n  hash: \"$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy\"\n  username: \"admin\"\n  name: \"Admin User\"\n  emailVerified: false\n  preferredUsername: \"admin-public\"\n  groups:\n  - \"team-a\"\n  - \"team-a/admins\"\n  userID: \"08a8684b-db88-4b73-90a9-3cd1661f5466\"\n- email: \"foo@example.com\"\n  # base64'd value of the same bcrypt hash above. We want to be able to parse both of these\n  hash: \"JDJhJDEwJDMzRU1UMGNWWVZsUHk2V0FNQ0xzY2VMWWpXaHVIcGJ6NXl1Wnh1L0dBRmowM0o5THl0anV5\"\n  username: \"foo\"\n  userID: \"41331323-6f44-45e6-b3b9-2c4b60c02be5\"\n\nexpiry:\n  signingKeys: \"7h\"\n  idTokens: \"25h\"\n  authRequests: \"25h\"\n  deviceRequests: \"10m\"\n\nlogger:\n  level: \"debug\"\n  format: \"json\"\n\nadditionalFeatures: [\n\t\"ConnectorsCRUD\"\n]\n`)\n\n\twant := Config{\n\t\tIssuer: \"http://127.0.0.1:5556/dex\",\n\t\tStorage: Storage{\n\t\t\tType: \"postgres\",\n\t\t\tConfig: &sql.Postgres{\n\t\t\t\tNetworkDB: sql.NetworkDB{\n\t\t\t\t\tHost:              \"10.0.0.1\",\n\t\t\t\t\tPort:              65432,\n\t\t\t\t\tMaxOpenConns:      5,\n\t\t\t\t\tMaxIdleConns:      3,\n\t\t\t\t\tConnMaxLifetime:   30,\n\t\t\t\t\tConnectionTimeout: 3,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tWeb: Web{\n\t\t\tHTTPS:         \"127.0.0.1:5556\",\n\t\t\tTLSMinVersion: \"1.3\",\n\t\t\tTLSMaxVersion: \"1.2\",\n\t\t\tHeaders: Headers{\n\t\t\t\tStrictTransportSecurity: \"max-age=31536000; includeSubDomains\",\n\t\t\t},\n\t\t},\n\t\tFrontend: server.WebConfig{\n\t\t\tDir: \"./web\",\n\t\t\tExtra: map[string]string{\n\t\t\t\t\"foo\": \"bar\",\n\t\t\t},\n\t\t},\n\t\tStaticClients: []storage.Client{\n\t\t\t{\n\t\t\t\tID:     \"example-app\",\n\t\t\t\tSecret: \"ZXhhbXBsZS1hcHAtc2VjcmV0\",\n\t\t\t\tName:   \"Example App\",\n\t\t\t\tRedirectURIs: []string{\n\t\t\t\t\t\"http://127.0.0.1:5555/callback\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOAuth2: OAuth2{\n\t\t\tAlwaysShowLoginScreen: true,\n\t\t\tGrantTypes: []string{\n\t\t\t\t\"refresh_token\",\n\t\t\t\t\"urn:ietf:params:oauth:grant-type:token-exchange\",\n\t\t\t},\n\t\t},\n\t\tStaticConnectors: []Connector{\n\t\t\t{\n\t\t\t\tType:   \"mockCallback\",\n\t\t\t\tID:     \"mock\",\n\t\t\t\tName:   \"Example\",\n\t\t\t\tConfig: &mock.CallbackConfig{},\n\t\t\t\tGrantTypes: []string{\n\t\t\t\t\t\"authorization_code\",\n\t\t\t\t\t\"urn:ietf:params:oauth:grant-type:token-exchange\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: \"oidc\",\n\t\t\t\tID:   \"google\",\n\t\t\t\tName: \"Google\",\n\t\t\t\tConfig: &oidc.Config{\n\t\t\t\t\tIssuer:       \"https://accounts.google.com\",\n\t\t\t\t\tClientID:     \"foo\",\n\t\t\t\t\tClientSecret: \"bar\",\n\t\t\t\t\tRedirectURI:  \"http://127.0.0.1:5556/dex/callback/google\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tEnablePasswordDB: true,\n\t\tStaticPasswords: []password{\n\t\t\t{\n\t\t\t\tEmail:             \"admin@example.com\",\n\t\t\t\tHash:              []byte(\"$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy\"),\n\t\t\t\tUsername:          \"admin\",\n\t\t\t\tName:              \"Admin User\",\n\t\t\t\tEmailVerified:     boolPtr(false),\n\t\t\t\tPreferredUsername: \"admin-public\",\n\t\t\t\tUserID:            \"08a8684b-db88-4b73-90a9-3cd1661f5466\",\n\t\t\t\tGroups: []string{\n\t\t\t\t\t\"team-a\",\n\t\t\t\t\t\"team-a/admins\",\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tEmail:    \"foo@example.com\",\n\t\t\t\tHash:     []byte(\"$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy\"),\n\t\t\t\tUsername: \"foo\",\n\t\t\t\tUserID:   \"41331323-6f44-45e6-b3b9-2c4b60c02be5\",\n\t\t\t},\n\t\t},\n\t\tExpiry: Expiry{\n\t\t\tSigningKeys:    \"7h\",\n\t\t\tIDTokens:       \"25h\",\n\t\t\tAuthRequests:   \"25h\",\n\t\t\tDeviceRequests: \"10m\",\n\t\t},\n\t\tLogger: Logger{\n\t\t\tLevel:  slog.LevelDebug,\n\t\t\tFormat: \"json\",\n\t\t},\n\t}\n\n\tvar c Config\n\tif err := yaml.Unmarshal(rawConfig, &c); err != nil {\n\t\tt.Fatalf(\"failed to decode config: %v\", err)\n\t}\n\n\tif diff := pretty.Compare(c, want); diff != \"\" {\n\t\tt.Errorf(\"got!=want: %s\", diff)\n\t}\n}\n\nfunc TestUnmarshalConfigWithEnvNoExpand(t *testing.T) {\n\t// If the env variable DEX_EXPAND_ENV is set and has a \"falsy\" value, os.ExpandEnv is disabled.\n\t// ParseBool: \"It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False.\"\n\tcheckUnmarshalConfigWithEnv(t, \"0\", false)\n\tcheckUnmarshalConfigWithEnv(t, \"f\", false)\n\tcheckUnmarshalConfigWithEnv(t, \"F\", false)\n\tcheckUnmarshalConfigWithEnv(t, \"FALSE\", false)\n\tcheckUnmarshalConfigWithEnv(t, \"false\", false)\n\tcheckUnmarshalConfigWithEnv(t, \"False\", false)\n\tos.Unsetenv(\"DEX_EXPAND_ENV\")\n}\n\nfunc TestUnmarshalConfigWithEnvExpand(t *testing.T) {\n\t// If the env variable DEX_EXPAND_ENV is unset or has a \"truthy\" or unknown value, os.ExpandEnv is enabled.\n\t// ParseBool: \"It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False.\"\n\tcheckUnmarshalConfigWithEnv(t, \"1\", true)\n\tcheckUnmarshalConfigWithEnv(t, \"t\", true)\n\tcheckUnmarshalConfigWithEnv(t, \"T\", true)\n\tcheckUnmarshalConfigWithEnv(t, \"TRUE\", true)\n\tcheckUnmarshalConfigWithEnv(t, \"true\", true)\n\tcheckUnmarshalConfigWithEnv(t, \"True\", true)\n\t// Values that can't be parsed as bool:\n\tcheckUnmarshalConfigWithEnv(t, \"UNSET\", true)\n\tcheckUnmarshalConfigWithEnv(t, \"\", true)\n\tcheckUnmarshalConfigWithEnv(t, \"whatever - true is default\", true)\n\tos.Unsetenv(\"DEX_EXPAND_ENV\")\n}\n\nfunc checkUnmarshalConfigWithEnv(t *testing.T, dexExpandEnv string, wantExpandEnv bool) {\n\t// For hashFromEnv:\n\tos.Setenv(\"DEX_FOO_USER_PASSWORD\", \"$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy\")\n\t// For os.ExpandEnv ($VAR -> value_of_VAR):\n\tos.Setenv(\"DEX_FOO_POSTGRES_HOST\", \"10.0.0.1\")\n\tos.Setenv(\"DEX_FOO_POSTGRES_PASSWORD\", `psql\"test\\pass`)\n\tos.Setenv(\"DEX_FOO_OIDC_CLIENT_SECRET\", `abc\"def\\ghi`)\n\tif dexExpandEnv != \"UNSET\" {\n\t\tos.Setenv(\"DEX_EXPAND_ENV\", dexExpandEnv)\n\t} else {\n\t\tos.Unsetenv(\"DEX_EXPAND_ENV\")\n\t}\n\n\trawConfig := []byte(`\nissuer: http://127.0.0.1:5556/dex\nstorage:\n  type: postgres\n  config:\n    # Env variables are expanded in raw YAML source.\n    # Single quotes work fine, as long as the env variable doesn't contain any.\n    host: '$DEX_FOO_POSTGRES_HOST'\n    password: '$DEX_FOO_POSTGRES_PASSWORD'\n    port: 65432\n    maxOpenConns: 5\n    maxIdleConns: 3\n    connMaxLifetime: 30\n    connectionTimeout: 3\nweb:\n  http: 127.0.0.1:5556\n\nfrontend:\n  dir: ./web\n  extra:\n    foo: bar\n\nstaticClients:\n- id: example-app\n  redirectURIs:\n  - 'http://127.0.0.1:5555/callback'\n  name: 'Example App'\n  secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n\noauth2:\n  alwaysShowLoginScreen: true\n\nconnectors:\n- type: mockCallback\n  id: mock\n  name: Example\n- type: oidc\n  id: google\n  name: Google\n  config:\n    issuer: https://accounts.google.com\n    clientID: foo\n    # Env variables are expanded in raw YAML source.\n    # Single quotes work fine, as long as the env variable doesn't contain any.\n    clientSecret: '$DEX_FOO_OIDC_CLIENT_SECRET'\n    redirectURI: http://127.0.0.1:5556/dex/callback/google\n\nenablePasswordDB: true\nstaticPasswords:\n- email: \"admin@example.com\"\n  # bcrypt hash of the string \"password\"\n  hash: \"$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy\"\n  username: \"admin\"\n  userID: \"08a8684b-db88-4b73-90a9-3cd1661f5466\"\n- email: \"foo@example.com\"\n  hashFromEnv: \"DEX_FOO_USER_PASSWORD\"\n  username: \"foo\"\n  userID: \"41331323-6f44-45e6-b3b9-2c4b60c02be5\"\n\nexpiry:\n  signingKeys: \"7h\"\n  idTokens: \"25h\"\n  authRequests: \"25h\"\n\nlogger:\n  level: \"debug\"\n  format: \"json\"\n`)\n\n\t// This is not a valid hostname. It's only used to check whether os.ExpandEnv was applied or not.\n\twantPostgresHost := \"$DEX_FOO_POSTGRES_HOST\"\n\twantPostgresPassword := \"$DEX_FOO_POSTGRES_PASSWORD\"\n\twantOidcClientSecret := \"$DEX_FOO_OIDC_CLIENT_SECRET\"\n\tif wantExpandEnv {\n\t\twantPostgresHost = \"10.0.0.1\"\n\t\twantPostgresPassword = `psql\"test\\pass`\n\t\twantOidcClientSecret = `abc\"def\\ghi`\n\t}\n\n\twant := Config{\n\t\tIssuer: \"http://127.0.0.1:5556/dex\",\n\t\tStorage: Storage{\n\t\t\tType: \"postgres\",\n\t\t\tConfig: &sql.Postgres{\n\t\t\t\tNetworkDB: sql.NetworkDB{\n\t\t\t\t\tHost:              wantPostgresHost,\n\t\t\t\t\tPassword:          wantPostgresPassword,\n\t\t\t\t\tPort:              65432,\n\t\t\t\t\tMaxOpenConns:      5,\n\t\t\t\t\tMaxIdleConns:      3,\n\t\t\t\t\tConnMaxLifetime:   30,\n\t\t\t\t\tConnectionTimeout: 3,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tWeb: Web{\n\t\t\tHTTP: \"127.0.0.1:5556\",\n\t\t},\n\t\tFrontend: server.WebConfig{\n\t\t\tDir: \"./web\",\n\t\t\tExtra: map[string]string{\n\t\t\t\t\"foo\": \"bar\",\n\t\t\t},\n\t\t},\n\t\tStaticClients: []storage.Client{\n\t\t\t{\n\t\t\t\tID:     \"example-app\",\n\t\t\t\tSecret: \"ZXhhbXBsZS1hcHAtc2VjcmV0\",\n\t\t\t\tName:   \"Example App\",\n\t\t\t\tRedirectURIs: []string{\n\t\t\t\t\t\"http://127.0.0.1:5555/callback\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tOAuth2: OAuth2{\n\t\t\tAlwaysShowLoginScreen: true,\n\t\t},\n\t\tStaticConnectors: []Connector{\n\t\t\t{\n\t\t\t\tType:   \"mockCallback\",\n\t\t\t\tID:     \"mock\",\n\t\t\t\tName:   \"Example\",\n\t\t\t\tConfig: &mock.CallbackConfig{},\n\t\t\t},\n\t\t\t{\n\t\t\t\tType: \"oidc\",\n\t\t\t\tID:   \"google\",\n\t\t\t\tName: \"Google\",\n\t\t\t\tConfig: &oidc.Config{\n\t\t\t\t\tIssuer:       \"https://accounts.google.com\",\n\t\t\t\t\tClientID:     \"foo\",\n\t\t\t\t\tClientSecret: wantOidcClientSecret,\n\t\t\t\t\tRedirectURI:  \"http://127.0.0.1:5556/dex/callback/google\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tEnablePasswordDB: true,\n\t\tStaticPasswords: []password{\n\t\t\t{\n\t\t\t\tEmail:    \"admin@example.com\",\n\t\t\t\tHash:     []byte(\"$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy\"),\n\t\t\t\tUsername: \"admin\",\n\t\t\t\tUserID:   \"08a8684b-db88-4b73-90a9-3cd1661f5466\",\n\t\t\t},\n\t\t\t{\n\t\t\t\tEmail:    \"foo@example.com\",\n\t\t\t\tHash:     []byte(\"$2a$10$33EMT0cVYVlPy6WAMCLsceLYjWhuHpbz5yuZxu/GAFj03J9Lytjuy\"),\n\t\t\t\tUsername: \"foo\",\n\t\t\t\tUserID:   \"41331323-6f44-45e6-b3b9-2c4b60c02be5\",\n\t\t\t},\n\t\t},\n\t\tExpiry: Expiry{\n\t\t\tSigningKeys:  \"7h\",\n\t\t\tIDTokens:     \"25h\",\n\t\t\tAuthRequests: \"25h\",\n\t\t},\n\t\tLogger: Logger{\n\t\t\tLevel:  slog.LevelDebug,\n\t\t\tFormat: \"json\",\n\t\t},\n\t}\n\n\tvar c Config\n\tif err := yaml.Unmarshal(rawConfig, &c); err != nil {\n\t\tt.Fatalf(\"failed to decode config: %v\", err)\n\t}\n\n\tif diff := pretty.Compare(c, want); diff != \"\" {\n\t\tt.Errorf(\"got!=want: %s\", diff)\n\t}\n}\n\nfunc TestSignerConfigUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tconfig  string\n\t\twantErr bool\n\t\tcheck   func(*Config) error\n\t}{\n\t\t{\n\t\t\tname: \"local signer with rotation period\",\n\t\t\tconfig: `\nissuer: http://127.0.0.1:5556/dex\nstorage:\n  type: memory\nweb:\n  http: 0.0.0.0:5556\nsigner:\n  type: local\n  config:\n    keysRotationPeriod: 6h\nenablePasswordDB: true\n`,\n\t\t\twantErr: false,\n\t\t\tcheck: func(c *Config) error {\n\t\t\t\tif c.Signer.Type != \"local\" {\n\t\t\t\t\tt.Errorf(\"expected signer type 'local', got %q\", c.Signer.Type)\n\t\t\t\t}\n\t\t\t\tif localConfig, ok := c.Signer.Config.(*signer.LocalConfig); !ok {\n\t\t\t\t\tt.Error(\"expected LocalConfig\")\n\t\t\t\t} else if localConfig.KeysRotationPeriod != \"6h\" {\n\t\t\t\t\tt.Errorf(\"expected keys rotation period '6h', got %q\", localConfig.KeysRotationPeriod)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"vault signer\",\n\t\t\tconfig: `\nissuer: http://127.0.0.1:5556/dex\nstorage:\n  type: memory\nweb:\n  http: 0.0.0.0:5556\nsigner:\n  type: vault\n  config:\n    addr: http://localhost:8200\n    token: test-token\n    keyName: test-key\nenablePasswordDB: true\n`,\n\t\t\twantErr: false,\n\t\t\tcheck: func(c *Config) error {\n\t\t\t\tif c.Signer.Type != \"vault\" {\n\t\t\t\t\tt.Errorf(\"expected signer type 'vault', got %q\", c.Signer.Type)\n\t\t\t\t}\n\t\t\t\tif vaultConfig, ok := c.Signer.Config.(*signer.VaultConfig); !ok {\n\t\t\t\t\tt.Error(\"expected VaultConfig\")\n\t\t\t\t} else {\n\t\t\t\t\tif vaultConfig.Addr != \"http://localhost:8200\" {\n\t\t\t\t\t\tt.Errorf(\"expected addr 'http://localhost:8200', got %q\", vaultConfig.Addr)\n\t\t\t\t\t}\n\t\t\t\t\tif vaultConfig.Token != \"test-token\" {\n\t\t\t\t\t\tt.Errorf(\"expected token 'test-token', got %q\", vaultConfig.Token)\n\t\t\t\t\t}\n\t\t\t\t\tif vaultConfig.KeyName != \"test-key\" {\n\t\t\t\t\t\tt.Errorf(\"expected keyName 'test-key', got %q\", vaultConfig.KeyName)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"default to local when no signer specified\",\n\t\t\tconfig: `\nissuer: http://127.0.0.1:5556/dex\nstorage:\n  type: memory\nweb:\n  http: 0.0.0.0:5556\nenablePasswordDB: true\n`,\n\t\t\twantErr: false,\n\t\t\tcheck: func(c *Config) error {\n\t\t\t\tif c.Signer.Type != \"\" {\n\t\t\t\t\tt.Errorf(\"expected signer type '', got %q\", c.Signer.Type)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar c Config\n\t\t\tdata, err := yaml.YAMLToJSON([]byte(tt.config))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to convert yaml to json: %v\", err)\n\t\t\t}\n\n\t\t\terr = json.Unmarshal(data, &c)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"Unmarshal() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif err == nil && tt.check != nil {\n\t\t\t\tif err := tt.check(&c); err != nil {\n\t\t\t\t\tt.Errorf(\"check failed: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/dex/excluding_handler.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n)\n\n// excludingHandler is an slog.Handler wrapper that drops log attributes\n// whose keys match a configured set. This allows PII fields like email,\n// username, or groups to be redacted at the logger level rather than\n// requiring per-callsite suppression logic.\ntype excludingHandler struct {\n\tinner   slog.Handler\n\texclude map[string]bool\n}\n\nfunc newExcludingHandler(inner slog.Handler, fields []string) slog.Handler {\n\tif len(fields) == 0 {\n\t\treturn inner\n\t}\n\tm := make(map[string]bool, len(fields))\n\tfor _, f := range fields {\n\t\tm[f] = true\n\t}\n\treturn &excludingHandler{inner: inner, exclude: m}\n}\n\nfunc (h *excludingHandler) Enabled(ctx context.Context, level slog.Level) bool {\n\treturn h.inner.Enabled(ctx, level)\n}\n\nfunc (h *excludingHandler) Handle(ctx context.Context, record slog.Record) error {\n\t// Rebuild the record without excluded attributes.\n\tfiltered := slog.NewRecord(record.Time, record.Level, record.Message, record.PC)\n\trecord.Attrs(func(a slog.Attr) bool {\n\t\tif !h.exclude[a.Key] {\n\t\t\tfiltered.AddAttrs(a)\n\t\t}\n\t\treturn true\n\t})\n\treturn h.inner.Handle(ctx, filtered)\n}\n\nfunc (h *excludingHandler) WithAttrs(attrs []slog.Attr) slog.Handler {\n\tvar kept []slog.Attr\n\tfor _, a := range attrs {\n\t\tif !h.exclude[a.Key] {\n\t\t\tkept = append(kept, a)\n\t\t}\n\t}\n\treturn &excludingHandler{inner: h.inner.WithAttrs(kept), exclude: h.exclude}\n}\n\nfunc (h *excludingHandler) WithGroup(name string) slog.Handler {\n\treturn &excludingHandler{inner: h.inner.WithGroup(name), exclude: h.exclude}\n}\n"
  },
  {
    "path": "cmd/dex/excluding_handler_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"testing\"\n)\n\nfunc TestExcludingHandler(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\texclude    []string\n\t\tlogAttrs   []slog.Attr\n\t\twantKeys   []string\n\t\tabsentKeys []string\n\t}{\n\t\t{\n\t\t\tname:    \"no exclusions\",\n\t\t\texclude: nil,\n\t\t\tlogAttrs: []slog.Attr{\n\t\t\t\tslog.String(\"email\", \"user@example.com\"),\n\t\t\t\tslog.String(\"connector_id\", \"github\"),\n\t\t\t},\n\t\t\twantKeys: []string{\"email\", \"connector_id\"},\n\t\t},\n\t\t{\n\t\t\tname:    \"exclude email\",\n\t\t\texclude: []string{\"email\"},\n\t\t\tlogAttrs: []slog.Attr{\n\t\t\t\tslog.String(\"email\", \"user@example.com\"),\n\t\t\t\tslog.String(\"connector_id\", \"github\"),\n\t\t\t},\n\t\t\twantKeys:   []string{\"connector_id\"},\n\t\t\tabsentKeys: []string{\"email\"},\n\t\t},\n\t\t{\n\t\t\tname:    \"exclude multiple fields\",\n\t\t\texclude: []string{\"email\", \"username\", \"groups\"},\n\t\t\tlogAttrs: []slog.Attr{\n\t\t\t\tslog.String(\"email\", \"user@example.com\"),\n\t\t\t\tslog.String(\"username\", \"johndoe\"),\n\t\t\t\tslog.String(\"connector_id\", \"github\"),\n\t\t\t\tslog.Any(\"groups\", []string{\"admin\"}),\n\t\t\t},\n\t\t\twantKeys:   []string{\"connector_id\"},\n\t\t\tabsentKeys: []string{\"email\", \"username\", \"groups\"},\n\t\t},\n\t\t{\n\t\t\tname:    \"exclude non-existent field is harmless\",\n\t\t\texclude: []string{\"nonexistent\"},\n\t\t\tlogAttrs: []slog.Attr{\n\t\t\t\tslog.String(\"email\", \"user@example.com\"),\n\t\t\t},\n\t\t\twantKeys: []string{\"email\"},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar buf bytes.Buffer\n\t\t\tinner := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo})\n\t\t\thandler := newExcludingHandler(inner, tt.exclude)\n\t\t\tlogger := slog.New(handler)\n\n\t\t\tattrs := make([]any, 0, len(tt.logAttrs)*2)\n\t\t\tfor _, a := range tt.logAttrs {\n\t\t\t\tattrs = append(attrs, a)\n\t\t\t}\n\t\t\tlogger.Info(\"test message\", attrs...)\n\n\t\t\tvar result map[string]any\n\t\t\tif err := json.Unmarshal(buf.Bytes(), &result); err != nil {\n\t\t\t\tt.Fatalf(\"failed to parse log output: %v\", err)\n\t\t\t}\n\n\t\t\tfor _, key := range tt.wantKeys {\n\t\t\t\tif _, ok := result[key]; !ok {\n\t\t\t\t\tt.Errorf(\"expected key %q in log output\", key)\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor _, key := range tt.absentKeys {\n\t\t\t\tif _, ok := result[key]; ok {\n\t\t\t\t\tt.Errorf(\"expected key %q to be absent from log output\", key)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestExcludingHandlerWithAttrs(t *testing.T) {\n\tvar buf bytes.Buffer\n\tinner := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo})\n\thandler := newExcludingHandler(inner, []string{\"email\"})\n\tlogger := slog.New(handler)\n\n\t// Pre-bind an excluded attr via With\n\tchild := logger.With(\"email\", \"user@example.com\", \"connector_id\", \"github\")\n\tchild.Info(\"login successful\")\n\n\tvar result map[string]any\n\tif err := json.Unmarshal(buf.Bytes(), &result); err != nil {\n\t\tt.Fatalf(\"failed to parse log output: %v\", err)\n\t}\n\n\tif _, ok := result[\"email\"]; ok {\n\t\tt.Error(\"expected email to be excluded from WithAttrs output\")\n\t}\n\tif _, ok := result[\"connector_id\"]; !ok {\n\t\tt.Error(\"expected connector_id to be present\")\n\t}\n}\n\nfunc TestExcludingHandlerEnabled(t *testing.T) {\n\tinner := slog.NewJSONHandler(&bytes.Buffer{}, &slog.HandlerOptions{Level: slog.LevelWarn})\n\thandler := newExcludingHandler(inner, []string{\"email\"})\n\n\tif handler.Enabled(context.Background(), slog.LevelInfo) {\n\t\tt.Error(\"expected Info to be disabled when handler level is Warn\")\n\t}\n\tif !handler.Enabled(context.Background(), slog.LevelWarn) {\n\t\tt.Error(\"expected Warn to be enabled\")\n\t}\n}\n\nfunc TestExcludingHandlerNilFields(t *testing.T) {\n\tvar buf bytes.Buffer\n\tinner := slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelInfo})\n\n\t// With nil/empty fields, should return the inner handler directly\n\thandler := newExcludingHandler(inner, nil)\n\tif _, ok := handler.(*excludingHandler); ok {\n\t\tt.Error(\"expected nil fields to return inner handler directly, not wrap it\")\n\t}\n\n\thandler = newExcludingHandler(inner, []string{})\n\tif _, ok := handler.(*excludingHandler); ok {\n\t\tt.Error(\"expected empty fields to return inner handler directly, not wrap it\")\n\t}\n}\n"
  },
  {
    "path": "cmd/dex/logger.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/dexidp/dex/server\"\n)\n\nvar logFormats = []string{\"json\", \"text\"}\n\nfunc newLogger(level slog.Level, format string, excludeFields []string) (*slog.Logger, error) {\n\tvar handler slog.Handler\n\tswitch strings.ToLower(format) {\n\tcase \"\", \"text\":\n\t\thandler = slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{\n\t\t\tLevel: level,\n\t\t})\n\tcase \"json\":\n\t\thandler = slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{\n\t\t\tLevel: level,\n\t\t})\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"log format is not one of the supported values (%s): %s\", strings.Join(logFormats, \", \"), format)\n\t}\n\n\thandler = newExcludingHandler(handler, excludeFields)\n\n\treturn slog.New(newRequestContextHandler(handler)), nil\n}\n\nvar _ slog.Handler = requestContextHandler{}\n\ntype requestContextHandler struct {\n\thandler slog.Handler\n}\n\nfunc newRequestContextHandler(handler slog.Handler) slog.Handler {\n\treturn requestContextHandler{\n\t\thandler: handler,\n\t}\n}\n\nfunc (h requestContextHandler) Enabled(ctx context.Context, level slog.Level) bool {\n\treturn h.handler.Enabled(ctx, level)\n}\n\nfunc (h requestContextHandler) Handle(ctx context.Context, record slog.Record) error {\n\tif v, ok := ctx.Value(server.RequestKeyRemoteIP).(string); ok {\n\t\trecord.AddAttrs(slog.String(string(server.RequestKeyRemoteIP), v))\n\t}\n\n\tif v, ok := ctx.Value(server.RequestKeyRequestID).(string); ok {\n\t\trecord.AddAttrs(slog.String(string(server.RequestKeyRequestID), v))\n\t}\n\n\treturn h.handler.Handle(ctx, record)\n}\n\nfunc (h requestContextHandler) WithAttrs(attrs []slog.Attr) slog.Handler {\n\treturn requestContextHandler{h.handler.WithAttrs(attrs)}\n}\n\nfunc (h requestContextHandler) WithGroup(name string) slog.Handler {\n\treturn requestContextHandler{h.handler.WithGroup(name)}\n}\n"
  },
  {
    "path": "cmd/dex/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nfunc commandRoot() *cobra.Command {\n\trootCmd := &cobra.Command{\n\t\tUse: \"dex\",\n\t\tRun: func(cmd *cobra.Command, args []string) {\n\t\t\tcmd.Help()\n\t\t\tos.Exit(2)\n\t\t},\n\t}\n\trootCmd.AddCommand(commandServe())\n\trootCmd.AddCommand(commandVersion())\n\treturn rootCmd\n}\n\nfunc main() {\n\tif err := commandRoot().Execute(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err.Error())\n\t\tos.Exit(2)\n\t}\n}\n"
  },
  {
    "path": "cmd/dex/serve.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/pprof\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\tgosundheit \"github.com/AppsFlyer/go-sundheit\"\n\t\"github.com/AppsFlyer/go-sundheit/checks\"\n\tgosundheithttp \"github.com/AppsFlyer/go-sundheit/http\"\n\t\"github.com/fsnotify/fsnotify\"\n\t\"github.com/ghodss/yaml\"\n\tgrpcprometheus \"github.com/grpc-ecosystem/go-grpc-prometheus\"\n\t\"github.com/oklog/run\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/collectors\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/spf13/cobra\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\t\"google.golang.org/grpc/reflection\"\n\n\t\"github.com/dexidp/dex/api/v2\"\n\t\"github.com/dexidp/dex/pkg/featureflags\"\n\t\"github.com/dexidp/dex/server\"\n\t\"github.com/dexidp/dex/server/signer\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\ntype serveOptions struct {\n\t// Config file path\n\tconfig string\n\n\t// Flags\n\twebHTTPAddr   string\n\twebHTTPSAddr  string\n\ttelemetryAddr string\n\tgrpcAddr      string\n}\n\nvar buildInfo = prometheus.NewGaugeVec(\n\tprometheus.GaugeOpts{\n\t\tName:      \"build_info\",\n\t\tNamespace: \"dex\",\n\t\tHelp:      \"A metric with a constant '1' value labeled by version from which Dex was built.\",\n\t},\n\t[]string{\"version\", \"go_version\", \"platform\"},\n)\n\nfunc commandServe() *cobra.Command {\n\toptions := serveOptions{}\n\n\tcmd := &cobra.Command{\n\t\tUse:     \"serve [flags] [config file]\",\n\t\tShort:   \"Launch Dex\",\n\t\tExample: \"dex serve config.yaml\",\n\t\tArgs:    cobra.ExactArgs(1),\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tcmd.SilenceUsage = true\n\t\t\tcmd.SilenceErrors = true\n\n\t\t\toptions.config = args[0]\n\n\t\t\treturn runServe(options)\n\t\t},\n\t}\n\n\tflags := cmd.Flags()\n\n\tflags.StringVar(&options.webHTTPAddr, \"web-http-addr\", \"\", \"Web HTTP address\")\n\tflags.StringVar(&options.webHTTPSAddr, \"web-https-addr\", \"\", \"Web HTTPS address\")\n\tflags.StringVar(&options.telemetryAddr, \"telemetry-addr\", \"\", \"Telemetry address\")\n\tflags.StringVar(&options.grpcAddr, \"grpc-addr\", \"\", \"gRPC API address\")\n\n\treturn cmd\n}\n\nfunc runServe(options serveOptions) error {\n\tconfigFile := options.config\n\tconfigData, err := os.ReadFile(configFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read config file %s: %v\", configFile, err)\n\t}\n\n\tvar c Config\n\n\tjsonConfigData, err := yaml.YAMLToJSON(configData)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parse config file %s: %v\", configFile, err)\n\t}\n\n\tif err := configUnmarshaller(jsonConfigData, &c); err != nil {\n\t\treturn fmt.Errorf(\"error unmarshalling config file %s: %v\", configFile, err)\n\t}\n\n\tapplyConfigOverrides(options, &c)\n\n\tlogger, err := newLogger(c.Logger.Level, c.Logger.Format, c.Logger.ExcludeFields)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid config: %v\", err)\n\t}\n\n\tlogger.Info(\n\t\t\"Version info\",\n\t\t\"dex_version\", version,\n\t\tslog.Group(\"go\",\n\t\t\t\"version\", runtime.Version(),\n\t\t\t\"os\", runtime.GOOS,\n\t\t\t\"arch\", runtime.GOARCH,\n\t\t),\n\t)\n\n\tif c.Logger.Level != slog.LevelInfo {\n\t\tlogger.Info(\"config using log level\", \"level\", c.Logger.Level)\n\t}\n\tif err := c.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\tlogger.Info(\"config issuer\", \"issuer\", c.Issuer)\n\n\tprometheusRegistry := prometheus.NewRegistry()\n\n\tprometheusRegistry.MustRegister(buildInfo)\n\trecordBuildInfo()\n\n\terr = prometheusRegistry.Register(collectors.NewGoCollector())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to register Go runtime metrics: %v\", err)\n\t}\n\n\terr = prometheusRegistry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to register process metrics: %v\", err)\n\t}\n\n\tgrpcMetrics := grpcprometheus.NewServerMetrics()\n\terr = prometheusRegistry.Register(grpcMetrics)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to register gRPC server metrics: %v\", err)\n\t}\n\n\tvar grpcOptions []grpc.ServerOption\n\n\tallowedTLSCiphers := []uint16{\n\t\ttls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,\n\t\ttls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,\n\t\ttls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,\n\t\ttls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,\n\t\ttls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,\n\t\ttls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,\n\t\ttls.TLS_RSA_WITH_AES_128_GCM_SHA256,\n\t\ttls.TLS_RSA_WITH_AES_256_GCM_SHA384,\n\t}\n\n\tallowedTLSVersions := map[string]int{\n\t\t\"1.2\": tls.VersionTLS12,\n\t\t\"1.3\": tls.VersionTLS13,\n\t}\n\n\tif c.GRPC.TLSCert != \"\" {\n\t\ttlsMinVersion := tls.VersionTLS12\n\t\tif c.GRPC.TLSMinVersion != \"\" {\n\t\t\ttlsMinVersion = allowedTLSVersions[c.GRPC.TLSMinVersion]\n\t\t}\n\t\ttlsMaxVersion := 0 // default for max is whatever Go defaults to\n\t\tif c.GRPC.TLSMaxVersion != \"\" {\n\t\t\ttlsMaxVersion = allowedTLSVersions[c.GRPC.TLSMaxVersion]\n\t\t}\n\t\tbaseTLSConfig := &tls.Config{\n\t\t\tMinVersion:               uint16(tlsMinVersion),\n\t\t\tMaxVersion:               uint16(tlsMaxVersion),\n\t\t\tCipherSuites:             allowedTLSCiphers,\n\t\t\tPreferServerCipherSuites: true,\n\t\t}\n\n\t\ttlsConfig, err := newTLSReloader(logger, c.GRPC.TLSCert, c.GRPC.TLSKey, c.GRPC.TLSClientCA, baseTLSConfig)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid config: get gRPC TLS: %v\", err)\n\t\t}\n\n\t\tif c.GRPC.TLSClientCA != \"\" {\n\t\t\t// Only add metrics if client auth is enabled\n\t\t\tgrpcOptions = append(grpcOptions,\n\t\t\t\tgrpc.StreamInterceptor(grpcMetrics.StreamServerInterceptor()),\n\t\t\t\tgrpc.UnaryInterceptor(grpcMetrics.UnaryServerInterceptor()),\n\t\t\t)\n\t\t}\n\n\t\tgrpcOptions = append(grpcOptions, grpc.Creds(credentials.NewTLS(tlsConfig)))\n\t}\n\n\ts, err := c.Storage.Config.Open(logger)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize storage: %v\", err)\n\t}\n\tdefer s.Close()\n\n\tlogger.Info(\"config storage\", \"storage_type\", c.Storage.Type)\n\n\tif len(c.StaticClients) > 0 {\n\t\tfor i, client := range c.StaticClients {\n\t\t\tif client.Name == \"\" {\n\t\t\t\treturn fmt.Errorf(\"invalid config: Name field is required for a client\")\n\t\t\t}\n\t\t\tif client.ID == \"\" && client.IDEnv == \"\" {\n\t\t\t\treturn fmt.Errorf(\"invalid config: ID or IDEnv field is required for a client\")\n\t\t\t}\n\t\t\tif client.IDEnv != \"\" {\n\t\t\t\tif client.ID != \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"invalid config: ID and IDEnv fields are exclusive for client %q\", client.ID)\n\t\t\t\t}\n\t\t\t\tc.StaticClients[i].ID = os.Getenv(client.IDEnv)\n\t\t\t}\n\t\t\tif client.Secret == \"\" && client.SecretEnv == \"\" && !client.Public {\n\t\t\t\treturn fmt.Errorf(\"invalid config: Secret or SecretEnv field is required for client %q\", client.ID)\n\t\t\t}\n\t\t\tif client.SecretEnv != \"\" {\n\t\t\t\tif client.Secret != \"\" {\n\t\t\t\t\treturn fmt.Errorf(\"invalid config: Secret and SecretEnv fields are exclusive for client %q\", client.ID)\n\t\t\t\t}\n\t\t\t\tc.StaticClients[i].Secret = os.Getenv(client.SecretEnv)\n\t\t\t}\n\t\t\tlogger.Info(\"config static client\", \"client_name\", client.Name)\n\t\t}\n\t\ts = storage.WithStaticClients(s, c.StaticClients)\n\t}\n\tif len(c.StaticPasswords) > 0 {\n\t\tpasswords := make([]storage.Password, len(c.StaticPasswords))\n\t\tfor i, p := range c.StaticPasswords {\n\t\t\tpasswords[i] = storage.Password(p)\n\t\t}\n\t\ts = storage.WithStaticPasswords(s, passwords, logger)\n\t}\n\n\tstorageConnectors := make([]storage.Connector, len(c.StaticConnectors))\n\tfor i, c := range c.StaticConnectors {\n\t\tif c.ID == \"\" || c.Name == \"\" || c.Type == \"\" {\n\t\t\treturn fmt.Errorf(\"invalid config: ID, Type and Name fields are required for a connector\")\n\t\t}\n\t\tif c.Config == nil {\n\t\t\treturn fmt.Errorf(\"invalid config: no config field for connector %q\", c.ID)\n\t\t}\n\t\tfor _, gt := range c.GrantTypes {\n\t\t\tif !server.ConnectorGrantTypes[gt] {\n\t\t\t\treturn fmt.Errorf(\"invalid config: unknown grant type %q for connector %q\", gt, c.ID)\n\t\t\t}\n\t\t}\n\t\tlogger.Info(\"config connector\", \"connector_id\", c.ID)\n\n\t\t// convert to a storage connector object\n\t\tconn, err := ToStorageConnector(c)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to initialize storage connectors: %v\", err)\n\t\t}\n\t\tstorageConnectors[i] = conn\n\t}\n\n\tif c.EnablePasswordDB {\n\t\tstorageConnectors = append(storageConnectors, storage.Connector{\n\t\t\tID:   server.LocalConnector,\n\t\t\tName: \"Email\",\n\t\t\tType: server.LocalConnector,\n\t\t})\n\t\tlogger.Info(\"config connector: local passwords enabled\")\n\t}\n\n\ts = storage.WithStaticConnectors(s, storageConnectors)\n\n\tif len(c.OAuth2.ResponseTypes) > 0 {\n\t\tlogger.Info(\"config response types accepted\", \"response_types\", c.OAuth2.ResponseTypes)\n\t}\n\tif c.OAuth2.SkipApprovalScreen {\n\t\tlogger.Info(\"config skipping approval screen\")\n\t}\n\tif c.OAuth2.PasswordConnector != \"\" {\n\t\tlogger.Info(\"config using password grant connector\", \"password_connector\", c.OAuth2.PasswordConnector)\n\t}\n\tif len(c.Web.AllowedOrigins) > 0 {\n\t\tlogger.Info(\"config allowed origins\", \"origins\", c.Web.AllowedOrigins)\n\t}\n\tif featureflags.ContinueOnConnectorFailure.Enabled() {\n\t\tlogger.Info(\"continue on connector failure feature flag enabled\")\n\t}\n\n\t// explicitly convert to UTC.\n\tnow := func() time.Time { return time.Now().UTC() }\n\n\thealthChecker := gosundheit.New()\n\n\t// Parse expiry durations\n\tidTokensValidFor := 24 * time.Hour // default\n\tif c.Expiry.IDTokens != \"\" {\n\t\tvar err error\n\t\tidTokensValidFor, err = time.ParseDuration(c.Expiry.IDTokens)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid config value %q for id token expiry: %v\", c.Expiry.IDTokens, err)\n\t\t}\n\t\tlogger.Info(\"config id tokens\", \"valid_for\", idTokensValidFor)\n\t}\n\n\t// Create signer\n\tvar signerInstance signer.Signer\n\tswitch c.Signer.Type {\n\tcase \"vault\":\n\t\tvaultConfig, ok := c.Signer.Config.(*signer.VaultConfig)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"invalid vault signer config\")\n\t\t}\n\t\tsignerInstance, err = vaultConfig.Open(context.Background())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open vault signer: %v\", err)\n\t\t}\n\t\tlogger.Info(\"signer configured\", \"type\", \"vault\")\n\tcase \"local\":\n\t\tlocalConfig, ok := c.Signer.Config.(*signer.LocalConfig)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"invalid local signer config\")\n\t\t}\n\t\tif localConfig.KeysRotationPeriod == \"\" {\n\t\t\treturn fmt.Errorf(\"failed to open local signer: signer.config.keysRotationPeriod must be specified\")\n\t\t}\n\t\tif c.Expiry.SigningKeys != \"\" {\n\t\t\tlogger.Warn(\"both expiry.signingKeys and signer.config.keysRotationPeriod specified, using signer.config.keysRotationPeriod\")\n\t\t}\n\t\tsignerInstance, err = localConfig.Open(context.Background(), s, idTokensValidFor, now, logger)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open local signer: %v\", err)\n\t\t}\n\t\tlogger.Info(\"signer configured\", \"type\", \"local\", \"keys_rotation_period\", localConfig.KeysRotationPeriod)\n\tcase \"\": // Default to local signer\n\t\t// Handle deprecated expiry.signingKeys configuration\n\t\tif c.Expiry.SigningKeys != \"\" {\n\t\t\tlogger.Warn(\"config expiry.signingKeys will be removed in a future release\",\n\t\t\t\t\"use_instead\", \"signer.config.keysRotationPeriod\",\n\t\t\t\t\"current_value\", c.Expiry.SigningKeys, \"deprecated\", true)\n\t\t} else {\n\t\t\tc.Expiry.SigningKeys = \"6h\"\n\t\t}\n\t\tlocalConfig := signer.LocalConfig{KeysRotationPeriod: c.Expiry.SigningKeys}\n\t\tsignerInstance, err = localConfig.Open(context.Background(), s, idTokensValidFor, now, logger)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to open local signer: %v\", err)\n\t\t}\n\t\tlogger.Info(\"signer configured\", \"type\", \"local\", \"keys_rotation_period\", localConfig.KeysRotationPeriod)\n\tdefault:\n\t\treturn fmt.Errorf(\"unknown signer type %q\", c.Signer.Type)\n\t}\n\n\tserverConfig := server.Config{\n\t\tAllowedGrantTypes:      c.OAuth2.GrantTypes,\n\t\tSupportedResponseTypes: c.OAuth2.ResponseTypes,\n\t\tSkipApprovalScreen:     c.OAuth2.SkipApprovalScreen,\n\t\tAlwaysShowLoginScreen:  c.OAuth2.AlwaysShowLoginScreen,\n\t\tPasswordConnector:      c.OAuth2.PasswordConnector,\n\t\tPKCE: server.PKCEConfig{\n\t\t\tEnforce:                       c.OAuth2.PKCE.Enforce,\n\t\t\tCodeChallengeMethodsSupported: c.OAuth2.PKCE.CodeChallengeMethodsSupported,\n\t\t},\n\t\tHeaders:                    c.Web.Headers.ToHTTPHeader(),\n\t\tAllowedOrigins:             c.Web.AllowedOrigins,\n\t\tAllowedHeaders:             c.Web.AllowedHeaders,\n\t\tIssuer:                     c.Issuer,\n\t\tStorage:                    s,\n\t\tWeb:                        c.Frontend,\n\t\tLogger:                     logger,\n\t\tNow:                        now,\n\t\tPrometheusRegistry:         prometheusRegistry,\n\t\tHealthChecker:              healthChecker,\n\t\tContinueOnConnectorFailure: featureflags.ContinueOnConnectorFailure.Enabled(),\n\t\tSigner:                     signerInstance,\n\t\tIDTokensValidFor:           idTokensValidFor,\n\t\tMFAProviders:               buildMFAProviders(c.MFA.Authenticators, logger),\n\t\tDefaultMFAChain:            c.MFA.DefaultMFAChain,\n\t}\n\n\tif c.Expiry.AuthRequests != \"\" {\n\t\tauthRequests, err := time.ParseDuration(c.Expiry.AuthRequests)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid config value %q for auth request expiry: %v\", c.Expiry.AuthRequests, err)\n\t\t}\n\t\tlogger.Info(\"config auth requests\", \"valid_for\", authRequests)\n\t\tserverConfig.AuthRequestsValidFor = authRequests\n\t}\n\tif c.Expiry.DeviceRequests != \"\" {\n\t\tdeviceRequests, err := time.ParseDuration(c.Expiry.DeviceRequests)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid config value %q for device request expiry: %v\", c.Expiry.DeviceRequests, err)\n\t\t}\n\t\tlogger.Info(\"config device requests\", \"valid_for\", deviceRequests)\n\t\tserverConfig.DeviceRequestsValidFor = deviceRequests\n\t}\n\trefreshTokenPolicy, err := server.NewRefreshTokenPolicy(\n\t\tlogger,\n\t\tc.Expiry.RefreshTokens.DisableRotation,\n\t\tc.Expiry.RefreshTokens.ValidIfNotUsedFor,\n\t\tc.Expiry.RefreshTokens.AbsoluteLifetime,\n\t\tc.Expiry.RefreshTokens.ReuseInterval,\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"invalid refresh token expiration policy config: %v\", err)\n\t}\n\n\tserverConfig.RefreshTokenPolicy = refreshTokenPolicy\n\n\tif featureflags.SessionsEnabled.Enabled() {\n\t\tsessionConfig, err := parseSessionConfig(c.Sessions)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid session config: %v\", err)\n\t\t}\n\t\tserverConfig.SessionConfig = sessionConfig\n\t\tlogger.Info(\"config sessions\",\n\t\t\t\"cookie_name\", sessionConfig.CookieName,\n\t\t\t\"absolute_lifetime\", sessionConfig.AbsoluteLifetime,\n\t\t\t\"valid_if_not_used_for\", sessionConfig.ValidIfNotUsedFor,\n\t\t)\n\t}\n\n\tserverConfig.RealIPHeader = c.Web.ClientRemoteIP.Header\n\tserverConfig.TrustedRealIPCIDRs, err = c.Web.ClientRemoteIP.ParseTrustedProxies()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to parse client remote IP settings: %v\", err)\n\t}\n\n\tserv, err := server.NewServer(context.Background(), serverConfig)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to initialize server: %v\", err)\n\t}\n\n\ttelemetryRouter := http.NewServeMux()\n\ttelemetryRouter.Handle(\"/metrics\", promhttp.HandlerFor(prometheusRegistry, promhttp.HandlerOpts{}))\n\n\t// Configure health checker\n\t{\n\t\thandler := gosundheithttp.HandleHealthJSON(healthChecker)\n\t\ttelemetryRouter.Handle(\"/healthz\", handler)\n\n\t\t// Kubernetes style health checks\n\t\ttelemetryRouter.HandleFunc(\"/healthz/live\", func(w http.ResponseWriter, _ *http.Request) {\n\t\t\t_, _ = w.Write([]byte(\"ok\"))\n\t\t})\n\t\ttelemetryRouter.Handle(\"/healthz/ready\", handler)\n\t}\n\n\thealthChecker.RegisterCheck(\n\t\t&checks.CustomCheck{\n\t\t\tCheckName: \"storage\",\n\t\t\tCheckFunc: storage.NewCustomHealthCheckFunc(serverConfig.Storage, serverConfig.Now),\n\t\t},\n\t\tgosundheit.ExecutionPeriod(15*time.Second),\n\t\tgosundheit.InitiallyPassing(true),\n\t)\n\n\tvar group run.Group\n\n\t// Set up telemetry server\n\tif c.Telemetry.HTTP != \"\" {\n\t\tconst name = \"telemetry\"\n\n\t\tlogger.Info(\"listening on\", \"server\", name, \"address\", c.Telemetry.HTTP)\n\n\t\tl, err := net.Listen(\"tcp\", c.Telemetry.HTTP)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"listening (%s) on %s: %v\", name, c.Telemetry.HTTP, err)\n\t\t}\n\n\t\tif c.Telemetry.EnableProfiling {\n\t\t\tpprofHandler(telemetryRouter)\n\t\t}\n\n\t\tserver := &http.Server{\n\t\t\tHandler: telemetryRouter,\n\t\t}\n\t\tdefer server.Close()\n\n\t\tgroup.Add(func() error {\n\t\t\treturn server.Serve(l)\n\t\t}, func(err error) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Minute)\n\t\t\tdefer cancel()\n\n\t\t\tlogger.Debug(\"starting graceful shutdown\", \"server\", name)\n\t\t\tif err := server.Shutdown(ctx); err != nil {\n\t\t\t\tlogger.Error(\"graceful shutdown\", \"server\", name, \"err\", err)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Set up http server\n\tif c.Web.HTTP != \"\" {\n\t\tconst name = \"http\"\n\n\t\tlogger.Info(\"listening on\", \"server\", name, \"address\", c.Web.HTTP)\n\n\t\tl, err := net.Listen(\"tcp\", c.Web.HTTP)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"listening (%s) on %s: %v\", name, c.Web.HTTP, err)\n\t\t}\n\n\t\tserver := &http.Server{\n\t\t\tHandler: serv,\n\t\t}\n\t\tdefer server.Close()\n\n\t\tgroup.Add(func() error {\n\t\t\treturn server.Serve(l)\n\t\t}, func(err error) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Minute)\n\t\t\tdefer cancel()\n\n\t\t\tlogger.Debug(\"starting graceful shutdown\", \"server\", name)\n\t\t\tif err := server.Shutdown(ctx); err != nil {\n\t\t\t\tlogger.Error(\"graceful shutdown\", \"server\", name, \"err\", err)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Set up https server\n\tif c.Web.HTTPS != \"\" {\n\t\tconst name = \"https\"\n\n\t\tlogger.Info(\"listening on\", \"server\", name, \"address\", c.Web.HTTPS)\n\n\t\tl, err := net.Listen(\"tcp\", c.Web.HTTPS)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"listening (%s) on %s: %v\", name, c.Web.HTTPS, err)\n\t\t}\n\n\t\ttlsMinVersion := tls.VersionTLS12\n\t\tif c.Web.TLSMinVersion != \"\" {\n\t\t\ttlsMinVersion = allowedTLSVersions[c.Web.TLSMinVersion]\n\t\t}\n\t\ttlsMaxVersion := 0 // default for max is whatever Go defaults to\n\t\tif c.Web.TLSMaxVersion != \"\" {\n\t\t\ttlsMaxVersion = allowedTLSVersions[c.Web.TLSMaxVersion]\n\t\t}\n\n\t\tbaseTLSConfig := &tls.Config{\n\t\t\tMinVersion:               uint16(tlsMinVersion),\n\t\t\tMaxVersion:               uint16(tlsMaxVersion),\n\t\t\tCipherSuites:             allowedTLSCiphers,\n\t\t\tPreferServerCipherSuites: true,\n\t\t}\n\n\t\ttlsConfig, err := newTLSReloader(logger, c.Web.TLSCert, c.Web.TLSKey, \"\", baseTLSConfig)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"invalid config: get HTTP TLS: %v\", err)\n\t\t}\n\n\t\tserver := &http.Server{\n\t\t\tHandler:   serv,\n\t\t\tTLSConfig: tlsConfig,\n\t\t}\n\t\tdefer server.Close()\n\n\t\tgroup.Add(func() error {\n\t\t\treturn server.ServeTLS(l, \"\", \"\")\n\t\t}, func(err error) {\n\t\t\tctx, cancel := context.WithTimeout(context.Background(), time.Minute)\n\t\t\tdefer cancel()\n\n\t\t\tlogger.Debug(\"starting graceful shutdown\", \"server\", name)\n\t\t\tif err := server.Shutdown(ctx); err != nil {\n\t\t\t\tlogger.Error(\"graceful shutdown\", \"server\", name, \"err\", err)\n\t\t\t}\n\t\t})\n\t}\n\n\t// Set up grpc server\n\tif c.GRPC.Addr != \"\" {\n\t\tlogger.Info(\"listening on\", \"server\", \"grpc\", \"address\", c.GRPC.Addr)\n\n\t\tgrpcListener, err := net.Listen(\"tcp\", c.GRPC.Addr)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"listening (grpc) on %s: %w\", c.GRPC.Addr, err)\n\t\t}\n\n\t\tgrpcSrv := grpc.NewServer(grpcOptions...)\n\t\tapi.RegisterDexServer(grpcSrv, server.NewAPI(serverConfig.Storage, logger, version, serv))\n\n\t\tgrpcMetrics.InitializeMetrics(grpcSrv)\n\t\tif c.GRPC.Reflection {\n\t\t\tlogger.Info(\"enabling reflection in grpc service\")\n\t\t\treflection.Register(grpcSrv)\n\t\t}\n\n\t\tgroup.Add(func() error {\n\t\t\treturn grpcSrv.Serve(grpcListener)\n\t\t}, func(err error) {\n\t\t\tlogger.Debug(\"starting graceful shutdown\", \"server\", \"grpc\")\n\t\t\tgrpcSrv.GracefulStop()\n\t\t})\n\t}\n\n\tgroup.Add(run.SignalHandler(context.Background(), os.Interrupt, syscall.SIGTERM))\n\tif err := group.Run(); err != nil {\n\t\tif _, ok := err.(run.SignalError); !ok {\n\t\t\treturn fmt.Errorf(\"run groups: %w\", err)\n\t\t}\n\t\tlogger.Info(\"shutdown now\", \"err\", err)\n\t}\n\treturn nil\n}\n\nfunc applyConfigOverrides(options serveOptions, config *Config) {\n\tif options.webHTTPAddr != \"\" {\n\t\tconfig.Web.HTTP = options.webHTTPAddr\n\t}\n\n\tif options.webHTTPSAddr != \"\" {\n\t\tconfig.Web.HTTPS = options.webHTTPSAddr\n\t}\n\n\tif options.telemetryAddr != \"\" {\n\t\tconfig.Telemetry.HTTP = options.telemetryAddr\n\t}\n\n\tif options.grpcAddr != \"\" {\n\t\tconfig.GRPC.Addr = options.grpcAddr\n\t}\n\n\tif config.Frontend.Dir == \"\" {\n\t\tconfig.Frontend.Dir = os.Getenv(\"DEX_FRONTEND_DIR\")\n\t}\n\n\tif len(config.OAuth2.GrantTypes) == 0 {\n\t\tconfig.OAuth2.GrantTypes = []string{\n\t\t\t\"authorization_code\",\n\t\t\t\"implicit\",\n\t\t\t\"password\",\n\t\t\t\"refresh_token\",\n\t\t\t\"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\"urn:ietf:params:oauth:grant-type:token-exchange\",\n\t\t}\n\t\tif featureflags.ClientCredentialGrantEnabledByDefault.Enabled() {\n\t\t\tconfig.OAuth2.GrantTypes = append(config.OAuth2.GrantTypes, \"client_credentials\")\n\t\t}\n\t}\n}\n\nfunc pprofHandler(router *http.ServeMux) {\n\trouter.HandleFunc(\"/debug/pprof/\", pprof.Index)\n\trouter.HandleFunc(\"/debug/pprof/cmdline\", pprof.Cmdline)\n\trouter.HandleFunc(\"/debug/pprof/profile\", pprof.Profile)\n\trouter.HandleFunc(\"/debug/pprof/symbol\", pprof.Symbol)\n\trouter.HandleFunc(\"/debug/pprof/trace\", pprof.Trace)\n}\n\n// newTLSReloader returns a [tls.Config] with GetCertificate or GetConfigForClient set\n// to reload certificates from the given paths on SIGHUP or on file creates (atomic update via rename).\nfunc newTLSReloader(logger *slog.Logger, certFile, keyFile, caFile string, baseConfig *tls.Config) (*tls.Config, error) {\n\t// trigger reload on channel\n\tsigc := make(chan os.Signal, 1)\n\tsignal.Notify(sigc, syscall.SIGHUP)\n\n\t// files to watch\n\twatchFiles := map[string]struct{}{\n\t\tcertFile: {},\n\t\tkeyFile:  {},\n\t}\n\tif caFile != \"\" {\n\t\twatchFiles[caFile] = struct{}{}\n\t}\n\twatchDirs := make(map[string]struct{}) // dedupe dirs\n\tfor f := range watchFiles {\n\t\tdir := filepath.Dir(f)\n\t\tif !strings.HasPrefix(f, dir) {\n\t\t\t// normalize name to have ./ prefix if only a local path was provided\n\t\t\t// can't pass \"\" to watcher.Add\n\t\t\twatchFiles[dir+string(filepath.Separator)+f] = struct{}{}\n\t\t}\n\t\twatchDirs[dir] = struct{}{}\n\t}\n\t// trigger reload on file change\n\twatcher, err := fsnotify.NewWatcher()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create watcher for TLS reloader: %v\", err)\n\t}\n\t// recommended by fsnotify: watch the dir to handle renames\n\t// https://pkg.go.dev/github.com/fsnotify/fsnotify#hdr-Watching_files\n\tfor dir := range watchDirs {\n\t\tlogger.Debug(\"watching dir\", \"dir\", dir)\n\t\terr := watcher.Add(dir)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"watch dir for TLS reloader: %v\", err)\n\t\t}\n\t}\n\n\t// load once outside the goroutine so we can return an error on misconfig\n\tinitialConfig, err := loadTLSConfig(certFile, keyFile, caFile, baseConfig)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"load TLS config: %v\", err)\n\t}\n\n\t// stored version of current tls config\n\tptr := &atomic.Pointer[tls.Config]{}\n\tptr.Store(initialConfig)\n\n\t// start background worker to reload certs\n\tgo func() {\n\tloop:\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase sig := <-sigc:\n\t\t\t\tlogger.Debug(\"reloading cert from signal\", \"signal\", sig)\n\t\t\tcase evt := <-watcher.Events:\n\t\t\t\tif _, ok := watchFiles[evt.Name]; !ok || !evt.Has(fsnotify.Create) {\n\t\t\t\t\tcontinue loop\n\t\t\t\t}\n\t\t\t\tlogger.Debug(\"reloading cert from fsnotify\", \"event\", evt.Name, \"operation\", evt.Op.String())\n\t\t\tcase err := <-watcher.Errors:\n\t\t\t\tlogger.Error(\"TLS reloader watch\", \"err\", err)\n\t\t\t}\n\n\t\t\tloaded, err := loadTLSConfig(certFile, keyFile, caFile, baseConfig)\n\t\t\tif err != nil {\n\t\t\t\tlogger.Error(\"reload TLS config\", \"err\", err)\n\t\t\t}\n\t\t\tptr.Store(loaded)\n\t\t}\n\t}()\n\n\t// https://pkg.go.dev/crypto/tls#baseConfig\n\t// Server configurations must set one of Certificates, GetCertificate or GetConfigForClient.\n\tif caFile != \"\" {\n\t\t// grpc will use this via tls.Server for mTLS\n\t\tinitialConfig.GetConfigForClient = func(chi *tls.ClientHelloInfo) (*tls.Config, error) { return ptr.Load(), nil }\n\t} else {\n\t\t// net/http only uses Certificates or GetCertificate\n\t\tinitialConfig.GetCertificate = func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { return &ptr.Load().Certificates[0], nil }\n\t}\n\treturn initialConfig, nil\n}\n\n// loadTLSConfig loads the given file paths into a [tls.Config]\nfunc loadTLSConfig(certFile, keyFile, caFile string, baseConfig *tls.Config) (*tls.Config, error) {\n\tcert, err := tls.LoadX509KeyPair(certFile, keyFile)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"loading TLS keypair: %v\", err)\n\t}\n\tloadedConfig := baseConfig.Clone() // copy\n\tloadedConfig.Certificates = []tls.Certificate{cert}\n\tif caFile != \"\" {\n\t\tcPool := x509.NewCertPool()\n\t\tclientCert, err := os.ReadFile(caFile)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"reading from client CA file: %v\", err)\n\t\t}\n\t\tif !cPool.AppendCertsFromPEM(clientCert) {\n\t\t\treturn nil, errors.New(\"failed to parse client CA\")\n\t\t}\n\n\t\tloadedConfig.ClientAuth = tls.RequireAndVerifyClientCert\n\t\tloadedConfig.ClientCAs = cPool\n\t}\n\treturn loadedConfig, nil\n}\n\n// recordBuildInfo publishes information about Dex version and runtime info through an info metric (gauge).\nfunc recordBuildInfo() {\n\tbuildInfo.WithLabelValues(version, runtime.Version(), fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH)).Set(1)\n}\n\nfunc parseSessionConfig(s *Sessions) (*server.SessionConfig, error) {\n\tsc := &server.SessionConfig{\n\t\tCookieName:                 \"dex_session\",\n\t\tAbsoluteLifetime:           24 * time.Hour,\n\t\tValidIfNotUsedFor:          1 * time.Hour,\n\t\tRememberMeCheckedByDefault: true,\n\t}\n\tif s != nil {\n\t\tif s.CookieName != \"\" {\n\t\t\tsc.CookieName = s.CookieName\n\t\t}\n\t\tif s.AbsoluteLifetime != \"\" {\n\t\t\td, err := time.ParseDuration(s.AbsoluteLifetime)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid absoluteLifetime %q: %v\", s.AbsoluteLifetime, err)\n\t\t\t}\n\t\t\tsc.AbsoluteLifetime = d\n\t\t}\n\t\tif s.ValidIfNotUsedFor != \"\" {\n\t\t\td, err := time.ParseDuration(s.ValidIfNotUsedFor)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"invalid validIfNotUsedFor %q: %v\", s.ValidIfNotUsedFor, err)\n\t\t\t}\n\t\t\tsc.ValidIfNotUsedFor = d\n\t\t}\n\t\tif s.RememberMeCheckedByDefault != nil {\n\t\t\tsc.RememberMeCheckedByDefault = *s.RememberMeCheckedByDefault\n\t\t}\n\t}\n\tif sc.AbsoluteLifetime <= 0 {\n\t\treturn nil, fmt.Errorf(\"absoluteLifetime must be positive, got %v\", sc.AbsoluteLifetime)\n\t}\n\tif sc.ValidIfNotUsedFor <= 0 {\n\t\treturn nil, fmt.Errorf(\"validIfNotUsedFor must be positive, got %v\", sc.ValidIfNotUsedFor)\n\t}\n\tif sc.ValidIfNotUsedFor > sc.AbsoluteLifetime {\n\t\treturn nil, fmt.Errorf(\"validIfNotUsedFor (%v) must not exceed absoluteLifetime (%v)\", sc.ValidIfNotUsedFor, sc.AbsoluteLifetime)\n\t}\n\treturn sc, nil\n}\n\nfunc buildMFAProviders(authenticators []MFAAuthenticator, logger *slog.Logger) map[string]server.MFAProvider {\n\tif len(authenticators) == 0 {\n\t\treturn nil\n\t}\n\n\tproviders := make(map[string]server.MFAProvider, len(authenticators))\n\tfor _, auth := range authenticators {\n\t\tswitch auth.Type {\n\t\tcase \"TOTP\":\n\t\t\tvar cfg TOTPConfig\n\t\t\tif err := json.Unmarshal(auth.Config, &cfg); err != nil {\n\t\t\t\tlogger.Error(\"failed to parse TOTP config\", \"id\", auth.ID, \"err\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tproviders[auth.ID] = server.NewTOTPProvider(cfg.Issuer, auth.ConnectorTypes)\n\t\t\tlogger.Info(\"MFA authenticator configured\", \"id\", auth.ID, \"type\", auth.Type)\n\t\tdefault:\n\t\t\tlogger.Error(\"unknown MFA authenticator type, skipping\", \"id\", auth.ID, \"type\", auth.Type)\n\t\t}\n\t}\n\treturn providers\n}\n"
  },
  {
    "path": "cmd/dex/serve_test.go",
    "content": "package main\n\nimport (\n\t\"log/slog\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNewLogger(t *testing.T) {\n\tt.Run(\"JSON\", func(t *testing.T) {\n\t\tlogger, err := newLogger(slog.LevelInfo, \"json\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEqual(t, (*slog.Logger)(nil), logger)\n\t})\n\n\tt.Run(\"Text\", func(t *testing.T) {\n\t\tlogger, err := newLogger(slog.LevelError, \"text\", nil)\n\t\trequire.NoError(t, err)\n\t\trequire.NotEqual(t, (*slog.Logger)(nil), logger)\n\t})\n\n\tt.Run(\"Unknown\", func(t *testing.T) {\n\t\tlogger, err := newLogger(slog.LevelError, \"gofmt\", nil)\n\t\trequire.Error(t, err)\n\t\trequire.Equal(t, \"log format is not one of the supported values (json, text): gofmt\", err.Error())\n\t\trequire.Equal(t, (*slog.Logger)(nil), logger)\n\t})\n}\n"
  },
  {
    "path": "cmd/dex/version.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar version = \"DEV\"\n\nfunc commandVersion() *cobra.Command {\n\treturn &cobra.Command{\n\t\tUse:   \"version\",\n\t\tShort: \"Print the version and exit\",\n\t\tRun: func(_ *cobra.Command, _ []string) {\n\t\t\tfmt.Printf(\n\t\t\t\t\"Dex Version: %s\\nGo Version: %s\\nGo OS/ARCH: %s %s\\n\",\n\t\t\t\tversion,\n\t\t\t\truntime.Version(),\n\t\t\t\truntime.GOOS,\n\t\t\t\truntime.GOARCH,\n\t\t\t)\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "cmd/docker-entrypoint/main.go",
    "content": "// Package main provides a utility program to launch the Dex container process with an optional\n// templating step (provided by gomplate).\n//\n// This was originally written as a shell script, but we rewrote it as a Go program so that it could\n// run as a raw binary in a distroless container.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"syscall\"\n)\n\nfunc main() {\n\t// Note that this docker-entrypoint program is args[0], and it is provided with the true process\n\t// args.\n\targs := os.Args[1:]\n\tif len(args) == 0 {\n\t\tfmt.Println(\"error: no args passed to entrypoint\")\n\t\tos.Exit(1)\n\t}\n\n\tif err := run(args, realExec, realWhich, realGomplate); err != nil {\n\t\tfmt.Println(\"error:\", err.Error())\n\t\tos.Exit(1)\n\t}\n}\n\nfunc realExec(args ...string) error {\n\targv0, err := exec.LookPath(args[0])\n\tif err != nil {\n\t\treturn fmt.Errorf(\"cannot lookup path for command %s: %w\", args[0], err)\n\t}\n\n\tif err := syscall.Exec(argv0, args, os.Environ()); err != nil {\n\t\treturn fmt.Errorf(\"cannot exec command %s (%q): %w\", args, argv0, err)\n\t}\n\n\treturn nil\n}\n\nfunc realWhich(path string) string {\n\tfullPath, err := exec.LookPath(path)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn fullPath\n}\n\nfunc realGomplate(path string) (string, error) {\n\ttmpFile, err := os.CreateTemp(\"/tmp\", \"dex.config.yaml-*\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot create temp file: %w\", err)\n\t}\n\n\tcmd := exec.Command(\"gomplate\", \"-f\", path, \"-o\", tmpFile.Name())\n\t// TODO(nabokihms): Workaround to run gomplate from a non-root directory in distroless images\n\t//   gomplate tries to access CWD on start, see: https://github.com/hairyhenderson/gomplate/pull/2202\n\tcmd.Dir = \"/etc/dex\"\n\n\toutput, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"error executing gomplate: %w, (output: %q)\", err, string(output))\n\t}\n\n\treturn tmpFile.Name(), nil\n}\n\nfunc run(args []string, execFunc func(...string) error, whichFunc func(string) string, gomplateFunc func(string) (string, error)) error {\n\tif args[0] != \"dex\" && args[0] != whichFunc(\"dex\") {\n\t\treturn execFunc(args...)\n\t}\n\n\tif args[1] != \"serve\" {\n\t\treturn execFunc(args...)\n\t}\n\n\tnewArgs := []string{}\n\tfor _, tplCandidate := range args {\n\t\tif hasSuffixes(tplCandidate, \".tpl\", \".tmpl\", \".yaml\") {\n\t\t\tfileName, err := gomplateFunc(tplCandidate)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tnewArgs = append(newArgs, fileName)\n\t\t} else {\n\t\t\tnewArgs = append(newArgs, tplCandidate)\n\t\t}\n\t}\n\n\treturn execFunc(newArgs...)\n}\n\nfunc hasSuffixes(s string, suffixes ...string) bool {\n\tfor _, suffix := range suffixes {\n\t\tif strings.HasSuffix(s, suffix) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "cmd/docker-entrypoint/main_test.go",
    "content": "package main\n\nimport (\n\t\"strings\"\n\t\"testing\"\n)\n\ntype execArgs struct {\n\tgomplate    bool\n\targPrefixes []string\n}\n\nfunc TestRun(t *testing.T) {\n\ttests := []struct {\n\t\tname         string\n\t\targs         []string\n\t\texecReturns  error\n\t\twhichReturns string\n\t\twantExecArgs execArgs\n\t\twantErr      error\n\t}{\n\t\t{\n\t\t\tname:         \"executable not dex\",\n\t\t\targs:         []string{\"tuna\", \"fish\"},\n\t\t\twantExecArgs: execArgs{gomplate: false, argPrefixes: []string{\"tuna\", \"fish\"}},\n\t\t},\n\t\t{\n\t\t\tname:         \"executable is full path to dex\",\n\t\t\targs:         []string{\"/usr/local/bin/dex\", \"marshmallow\", \"zelda\"},\n\t\t\twhichReturns: \"/usr/local/bin/dex\",\n\t\t\twantExecArgs: execArgs{gomplate: false, argPrefixes: []string{\"/usr/local/bin/dex\", \"marshmallow\", \"zelda\"}},\n\t\t},\n\t\t{\n\t\t\tname:         \"command is not serve\",\n\t\t\targs:         []string{\"dex\", \"marshmallow\", \"zelda\"},\n\t\t\twantExecArgs: execArgs{gomplate: false, argPrefixes: []string{\"dex\", \"marshmallow\", \"zelda\"}},\n\t\t},\n\t\t{\n\t\t\tname:         \"no templates\",\n\t\t\targs:         []string{\"dex\", \"serve\", \"config.yaml.not-a-template\"},\n\t\t\twantExecArgs: execArgs{gomplate: false, argPrefixes: []string{\"dex\", \"serve\", \"config.yaml.not-a-template\"}},\n\t\t},\n\t\t{\n\t\t\tname:         \"no templates\",\n\t\t\targs:         []string{\"dex\", \"serve\", \"config.yaml.not-a-template\"},\n\t\t\twantExecArgs: execArgs{gomplate: false, argPrefixes: []string{\"dex\", \"serve\", \"config.yaml.not-a-template\"}},\n\t\t},\n\t\t{\n\t\t\tname:         \".tpl template\",\n\t\t\targs:         []string{\"dex\", \"serve\", \"config.tpl\"},\n\t\t\twantExecArgs: execArgs{gomplate: true, argPrefixes: []string{\"dex\", \"serve\", \"/tmp/dex.config.yaml-\"}},\n\t\t},\n\t\t{\n\t\t\tname:         \".tmpl template\",\n\t\t\targs:         []string{\"dex\", \"serve\", \"config.tmpl\"},\n\t\t\twantExecArgs: execArgs{gomplate: true, argPrefixes: []string{\"dex\", \"serve\", \"/tmp/dex.config.yaml-\"}},\n\t\t},\n\t\t{\n\t\t\tname:         \".yaml template\",\n\t\t\targs:         []string{\"dex\", \"serve\", \"some/path/config.yaml\"},\n\t\t\twantExecArgs: execArgs{gomplate: true, argPrefixes: []string{\"dex\", \"serve\", \"/tmp/dex.config.yaml-\"}},\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tvar gotExecArgs []string\n\t\t\tvar runsGomplate bool\n\n\t\t\tfakeExec := func(args ...string) error {\n\t\t\t\tgotExecArgs = append(args, gotExecArgs...)\n\t\t\t\treturn test.execReturns\n\t\t\t}\n\n\t\t\tfakeWhich := func(_ string) string { return test.whichReturns }\n\n\t\t\tfakeGomplate := func(file string) (string, error) {\n\t\t\t\trunsGomplate = true\n\t\t\t\treturn \"/tmp/dex.config.yaml-\", nil\n\t\t\t}\n\n\t\t\tgotErr := run(test.args, fakeExec, fakeWhich, fakeGomplate)\n\t\t\tif (test.wantErr == nil) != (gotErr == nil) {\n\t\t\t\tt.Errorf(\"wanted error %s, got %s\", test.wantErr, gotErr)\n\t\t\t}\n\n\t\t\tif !execArgsMatch(test.wantExecArgs, runsGomplate, gotExecArgs) {\n\t\t\t\tt.Errorf(\"wanted exec args %+v (running gomplate: %+v), got %+v (running gomplate: %+v)\",\n\t\t\t\t\ttest.wantExecArgs.argPrefixes, test.wantExecArgs.gomplate, gotExecArgs, runsGomplate)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc execArgsMatch(wantExecArgs execArgs, gomplate bool, gotExecArgs []string) bool {\n\tif wantExecArgs.gomplate != gomplate {\n\t\treturn false\n\t}\n\tfor i := range wantExecArgs.argPrefixes {\n\t\tif !strings.HasPrefix(gotExecArgs[i], wantExecArgs.argPrefixes[i]) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "config.dev.yaml",
    "content": "issuer: http://127.0.0.1:5556/dex\n\nstorage:\n  type: sqlite3\n  config:\n    file: var/sqlite/dex.db\n\nweb:\n  http: 127.0.0.1:5556\n\ntelemetry:\n  http: 127.0.0.1:5558\n\ngrpc:\n  addr: 127.0.0.1:5557\n\nstaticClients:\n  - id: example-app\n    redirectURIs:\n      - 'http://127.0.0.1:5555/callback'\n    name: 'Example App'\n    secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n\nconnectors:\n  - type: mockCallback\n    id: mock\n    name: Example\n\nenablePasswordDB: true\n\nstaticPasswords:\n  - email: \"admin@example.com\"\n    hash: \"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W\"\n    username: \"admin\"\n    name: \"Admin User\"\n    emailVerified: true\n    preferredUsername: \"admin\"\n    groups:\n      - \"team-a\"\n      - \"team-a/admins\"\n    userID: \"08a8684b-db88-4b73-90a9-3cd1661f5466\"\n"
  },
  {
    "path": "config.docker.yaml",
    "content": "{{- /* NOTE: This configuration file is an example and exists only for development purposes. */ -}}\n{{- /* To find more about gomplate formatting, please visit its documentation site - https://docs.gomplate.ca/ */ -}}\nissuer: {{ getenv \"DEX_ISSUER\" \"http://127.0.0.1:5556/dex\" }}\n\nstorage:\n  type: sqlite3\n  config:\n    file: {{ getenv \"DEX_STORAGE_SQLITE3_CONFIG_FILE\" \"/var/dex/dex.db\" }}\n\nweb:\n{{- if getenv \"DEX_WEB_HTTPS\" \"\" }}\n  https: {{ .Env.DEX_WEB_HTTPS }}\n  tlsKey: {{ getenv \"DEX_WEB_TLS_KEY\" | required \"$DEX_WEB_TLS_KEY in case of web.https is enabled\" }}\n  tlsCert: {{ getenv \"DEX_WEB_TLS_CERT\" | required \"$DEX_WEB_TLS_CERT in case of web.https is enabled\" }}\n{{- end }}\n  http: {{ getenv \"DEX_WEB_HTTP\" \"0.0.0.0:5556\" }}\n\n{{- if getenv \"DEX_TELEMETRY_HTTP\" }}\ntelemetry:\n  http: {{ .Env.DEX_TELEMETRY_HTTP }}\n{{- end }}\n\nexpiry:\n  deviceRequests: {{ getenv \"DEX_EXPIRY_DEVICE_REQUESTS\" \"5m\" }}\n  signingKeys: {{ getenv \"DEX_EXPIRY_SIGNING_KEYS\" \"6h\" }}\n  idTokens: {{ getenv \"DEX_EXPIRY_ID_TOKENS\" \"24h\" }}\n  authRequests: {{ getenv \"DEX_EXPIRY_AUTH_REQUESTS\" \"24h\" }}\n\nlogger:\n  level: {{ getenv \"DEX_LOG_LEVEL\" \"info\" }}\n  format: {{ getenv \"DEX_LOG_FORMAT\" \"text\" }}\n\noauth2:\n  responseTypes: {{ getenv \"DEX_OAUTH2_RESPONSE_TYPES\" \"[code]\" }}\n  skipApprovalScreen: {{ getenv \"DEX_OAUTH2_SKIP_APPROVAL_SCREEN\" \"false\" }}\n  alwaysShowLoginScreen: {{ getenv \"DEX_OAUTH2_ALWAYS_SHOW_LOGIN_SCREEN\" \"false\" }}\n{{- if getenv \"DEX_OAUTH2_PASSWORD_CONNECTOR\" \"\" }}\n  passwordConnector: {{ .Env.DEX_OAUTH2_PASSWORD_CONNECTOR }}\n{{- end }}\n\nenablePasswordDB: {{ getenv \"DEX_ENABLE_PASSWORD_DB\" \"true\" }}\n\nconnectors:\n{{- if getenv \"DEX_CONNECTORS_ENABLE_MOCK\" }}\n- type: mockCallback\n  id: mock\n  name: Example\n{{- end }}\n"
  },
  {
    "path": "config.yaml.dist",
    "content": "# The base path of Dex and the external name of the OpenID Connect service.\n# This is the canonical URL that all clients MUST use to refer to Dex. If a\n# path is provided, Dex's HTTP service will listen at a non-root URL.\nissuer: http://127.0.0.1:5556/dex\n\n# The storage configuration determines where Dex stores its state.\n# Supported options include:\n#   - SQL flavors\n#   - key-value stores (eg. etcd)\n#   - Kubernetes Custom Resources\n#\n# See the documentation (https://dexidp.io/docs/storage/) for further information.\nstorage:\n  type: memory\n\n  # type: sqlite3\n  # config:\n  #   file: /var/dex/dex.db\n\n  # type: mysql\n  # config:\n  #   host: 127.0.0.1\n  #   port: 3306\n  #   database: dex\n  #   user: mysql\n  #   password: mysql\n  #   ssl:\n  #     mode: \"false\"\n\n  # type: postgres\n  # config:\n  #   host: 127.0.0.1\n  #   port: 5432\n  #   database: dex\n  #   user: postgres\n  #   password: postgres\n  #   ssl:\n  #     mode: disable\n\n  # type: etcd\n  # config:\n  #   endpoints:\n  #     - http://127.0.0.1:2379\n  #   namespace: dex/\n\n  # type: kubernetes\n  # config:\n  #   kubeConfigFile: $HOME/.kube/config\n\n# HTTP service configuration\nweb:\n  http: 127.0.0.1:5556\n\n  # Uncomment to enable HTTPS endpoint.\n  # https: 127.0.0.1:5554\n  # tlsCert: /etc/dex/tls.crt\n  # tlsKey: /etc/dex/tls.key\n  # tlsMinVersion: 1.2\n  # tlsMaxVersion: 1.3\n\n# Dex UI configuration\n# frontend:\n#   issuer: dex\n#   logoURL: theme/logo.png\n#   dir: \"\"\n#   theme: light\n\n# Telemetry configuration\n# telemetry:\n#   http: 127.0.0.1:5558\n\n# logger:\n#   level: \"debug\"\n#   format: \"text\" # can also be \"json\"\n#   # Drop these attribute keys from all log output (useful for GDPR/PII suppression).\n#   # excludeFields: [email, username, preferred_username, groups]\n\n# gRPC API configuration\n# Uncomment this block to enable the gRPC API.\n# See the documentation (https://dexidp.io/docs/api/) for further information.\n# grpc:\n#   addr: 127.0.0.1:5557\n#   tlsCert: examples/grpc-client/server.crt\n#   tlsKey: examples/grpc-client/server.key\n#   tlsClientCA: examples/grpc-client/ca.crt\n\n# Expiration configuration for tokens, signing keys, etc.\n# expiry:\n#   deviceRequests: \"5m\"\n#   signingKeys: \"6h\"\n#   idTokens: \"24h\"\n#   refreshTokens:\n#     disableRotation: false\n#     reuseInterval: \"3s\"\n#     validIfNotUsedFor: \"2160h\" # 90 days\n#     absoluteLifetime: \"3960h\" # 165 days\n\n# OAuth2 configuration\n# oauth2:\n#   # use [\"code\", \"token\", \"id_token\"] to enable implicit flow for web-only clients\n#   responseTypes: [ \"code\" ] # also allowed are \"token\" and \"id_token\"\n#\n#   # By default, Dex will ask for approval to share data with application\n#   # (approval for sharing data from connected IdP to Dex is separate process on IdP)\n#   skipApprovalScreen: false\n#\n#   # If only one authentication method is enabled, the default behavior is to\n#   # go directly to it. For connected IdPs, this redirects the browser away\n#   # from application to upstream provider such as the Google login page\n#   alwaysShowLoginScreen: false\n#\n#   # Uncomment to use a specific connector for password grants\n#   passwordConnector: local\n#\n#   # PKCE (Proof Key for Code Exchange) configuration\n#   pkce:\n#     # If true, PKCE is required for all authorization code flows (OAuth 2.1).\n#     enforce: false\n#     # Supported code challenge methods. Defaults to [\"S256\", \"plain\"].\n#     codeChallengeMethodsSupported: [\"S256\", \"plain\"]\n\n# Static clients registered in Dex by default.\n#\n# Alternatively, clients may be added through the gRPC API.\n# staticClients:\n#   - id: example-app\n#     redirectURIs:\n#       - 'http://127.0.0.1:5555/callback'\n#     name: 'Example App'\n#     secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n#\n#   # Example using environment variables\n#   # These fields are mutually exclusive with id and secret respectively.\n#   - idEnv: DEX_CLIENT_ID\n#     secretEnv: DEX_CLIENT_SECRET\n#     redirectURIs:\n#       - 'https://app.example.com/callback'\n#     name: 'Production App'\n#\n#   # Example of a public client (no secret required)\n#   - id: example-device-client\n#     redirectURIs:\n#       - /device/callback\n#     name: 'Static Client for Device Flow'\n#     public: true\n#\n#   # Example of a client restricted to specific connectors\n#   - id: restricted-client\n#     secret: restricted-client-secret\n#     redirectURIs:\n#       - 'https://app.example.com/callback'\n#     name: 'Restricted Client'\n#     allowedConnectors:\n#       - github\n#       - google\n\n# Connectors are used to authenticate users against upstream identity providers.\n#\n# See the documentation (https://dexidp.io/docs/connectors/) for further information.\n# connectors: []\n\n# Enable the password database.\n#\n# It's a \"virtual\" connector (identity provider) that stores\n# login credentials in Dex's store.\nenablePasswordDB: true\n\n# If this option isn't chosen users may be added through the gRPC API.\n# A static list of passwords for the password connector.\n#\n# Alternatively, passwords my be added/updated through the gRPC API.\n# staticPasswords:\n#  - email: \"user@example.com\"\n#    # bcrypt hash of the string \"password\"\n#    hash: \"$2a$10$examplehash...\"\n#    username: \"user-login\"\n#    # Optional. Maps to OIDC \"name\" claim. Defaults to username.\n#    name: \"User Full Name\"\n#    # Optional. Maps to OIDC \"email_verified\" claim. Defaults to true.\n#    emailVerified: true\n#    # Optional. Maps to OIDC \"preferred_username\" claim.\n#    preferredUsername: \"user-public\"\n#    # Optional. Maps to OIDC \"groups\" claim (when 'groups' scope is requested).\n#    groups:\n#      - \"team-a\"\n#      - \"team-a/admins\"\n#    userID: \"08a8684b-db88-4b73-90a9-3cd1661f5466\"\n"
  },
  {
    "path": "connector/atlassiancrowd/atlassiancrowd.go",
    "content": "// Package atlassiancrowd provides authentication strategies using Atlassian Crowd.\npackage atlassiancrowd\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/groups\"\n)\n\n// Config holds configuration options for Atlassian Crowd connector.\n// Crowd connectors require executing two queries, the first to find\n// the user based on the username and password given to the connector.\n// The second to use the user entry to search for groups.\n//\n// An example config:\n//\n//\t    type: atlassian-crowd\n//\t    config:\n//\t      baseURL: https://crowd.example.com/context\n//\t      clientID: applogin\n//\t      clientSecret: appP4$$w0rd\n//\t      # users can be restricted by a list of groups\n//\t      groups:\n//\t      - admin\n//\t      # Prompt for username field\n//\t      usernamePrompt: Login\n//\t\t\t preferredUsernameField: name\ntype Config struct {\n\tBaseURL      string   `json:\"baseURL\"`\n\tClientID     string   `json:\"clientID\"`\n\tClientSecret string   `json:\"clientSecret\"`\n\tGroups       []string `json:\"groups\"`\n\n\t// PreferredUsernameField allows users to set the field to any of the\n\t// following values: \"key\", \"name\" or \"email\".\n\t// If unset, the preferred_username field will remain empty.\n\tPreferredUsernameField string `json:\"preferredUsernameField\"`\n\n\t// UsernamePrompt allows users to override the username attribute (displayed\n\t// in the username/password prompt). If unset, the handler will use.\n\t// \"Username\".\n\tUsernamePrompt string `json:\"usernamePrompt\"`\n}\n\ntype crowdUser struct {\n\tKey    string\n\tName   string\n\tActive bool\n\tEmail  string\n}\n\ntype crowdGroups struct {\n\tGroups []struct {\n\t\tName string\n\t} `json:\"groups\"`\n}\n\ntype crowdAuthentication struct {\n\tToken string\n\tUser  struct {\n\t\tName string\n\t} `json:\"user\"`\n\tCreatedDate uint64 `json:\"created-date\"`\n\tExpiryDate  uint64 `json:\"expiry-date\"`\n}\n\ntype crowdAuthenticationError struct {\n\tReason  string\n\tMessage string\n}\n\n// Open returns a strategy for logging in through Atlassian Crowd\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tif c.BaseURL == \"\" {\n\t\treturn nil, fmt.Errorf(\"crowd: no baseURL provided for crowd connector\")\n\t}\n\treturn &crowdConnector{Config: *c, logger: logger.With(slog.Group(\"connector\", \"type\", \"atlassiancrowd\", \"id\", id))}, nil\n}\n\nvar (\n\t_ connector.PasswordConnector = (*crowdConnector)(nil)\n\t_ connector.RefreshConnector  = (*crowdConnector)(nil)\n)\n\ntype crowdConnector struct {\n\tConfig\n\tlogger *slog.Logger\n}\n\ntype refreshData struct {\n\tUsername string `json:\"username\"`\n}\n\nfunc (c *crowdConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (ident connector.Identity, validPass bool, err error) {\n\t// make this check to avoid empty passwords.\n\tif password == \"\" {\n\t\treturn connector.Identity{}, false, nil\n\t}\n\n\t// We want to return a different error if the user's password is incorrect vs\n\t// if there was an error.\n\tvar incorrectPass bool\n\tvar user crowdUser\n\n\tclient := c.crowdAPIClient()\n\n\tif incorrectPass, err = c.authenticateWithPassword(ctx, client, username, password); err != nil {\n\t\treturn connector.Identity{}, false, err\n\t}\n\n\tif incorrectPass {\n\t\treturn connector.Identity{}, false, nil\n\t}\n\n\tif user, err = c.user(ctx, client, username); err != nil {\n\t\treturn connector.Identity{}, false, err\n\t}\n\n\tident = c.identityFromCrowdUser(user)\n\tif s.Groups {\n\t\tuserGroups, err := c.getGroups(ctx, client, s.Groups, ident.Username)\n\t\tif err != nil {\n\t\t\treturn connector.Identity{}, false, fmt.Errorf(\"crowd: failed to query groups: %v\", err)\n\t\t}\n\t\tident.Groups = userGroups\n\t}\n\n\tif s.OfflineAccess {\n\t\trefresh := refreshData{Username: username}\n\t\t// Encode entry for following up requests such as the groups query and refresh attempts.\n\t\tif ident.ConnectorData, err = json.Marshal(refresh); err != nil {\n\t\t\treturn connector.Identity{}, false, fmt.Errorf(\"crowd: marshal refresh data: %v\", err)\n\t\t}\n\t}\n\n\treturn ident, true, nil\n}\n\nfunc (c *crowdConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {\n\tvar data refreshData\n\tif err := json.Unmarshal(ident.ConnectorData, &data); err != nil {\n\t\treturn ident, fmt.Errorf(\"crowd: failed to unmarshal internal data: %v\", err)\n\t}\n\n\tvar user crowdUser\n\tclient := c.crowdAPIClient()\n\n\tuser, err := c.user(ctx, client, data.Username)\n\tif err != nil {\n\t\treturn ident, fmt.Errorf(\"crowd: get user %q: %v\", data.Username, err)\n\t}\n\n\tnewIdent := c.identityFromCrowdUser(user)\n\tnewIdent.ConnectorData = ident.ConnectorData\n\n\t// If user exists, authenticate it to prolong sso session.\n\terr = c.authenticateUser(ctx, client, data.Username)\n\tif err != nil {\n\t\treturn ident, fmt.Errorf(\"crowd: authenticate user: %v\", err)\n\t}\n\n\tif s.Groups {\n\t\tuserGroups, err := c.getGroups(ctx, client, s.Groups, newIdent.Username)\n\t\tif err != nil {\n\t\t\treturn connector.Identity{}, fmt.Errorf(\"crowd: failed to query groups: %v\", err)\n\t\t}\n\t\tnewIdent.Groups = userGroups\n\t}\n\treturn newIdent, nil\n}\n\nfunc (c *crowdConnector) Prompt() string {\n\treturn c.UsernamePrompt\n}\n\nfunc (c *crowdConnector) crowdAPIClient() *http.Client {\n\treturn &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tProxy: http.ProxyFromEnvironment,\n\t\t\tDialContext: (&net.Dialer{\n\t\t\t\tTimeout:   30 * time.Second,\n\t\t\t\tKeepAlive: 30 * time.Second,\n\t\t\t}).DialContext,\n\t\t\tMaxIdleConns:          100,\n\t\t\tIdleConnTimeout:       90 * time.Second,\n\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t\tExpectContinueTimeout: 1 * time.Second,\n\t\t},\n\t}\n}\n\n// authenticateWithPassword creates a new session for user and validates a password with Crowd API\nfunc (c *crowdConnector) authenticateWithPassword(ctx context.Context, client *http.Client, username string, password string) (invalidPass bool, err error) {\n\treq, err := c.crowdUserManagementRequest(ctx,\n\t\t\"POST\",\n\t\t\"/session\",\n\t\tstruct {\n\t\t\tUsername string `json:\"username\"`\n\t\t\tPassword string `json:\"password\"`\n\t\t}{Username: username, Password: password},\n\t)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"crowd: new auth pass api request %v\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"crowd: api request %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := c.validateCrowdResponse(resp)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tif resp.StatusCode != http.StatusCreated {\n\t\tvar authError crowdAuthenticationError\n\t\tif err := json.Unmarshal(body, &authError); err != nil {\n\t\t\treturn false, fmt.Errorf(\"unmarshal auth pass response: %d %v %q\", resp.StatusCode, err, string(body))\n\t\t}\n\n\t\tif authError.Reason == \"INVALID_USER_AUTHENTICATION\" {\n\t\t\treturn true, nil\n\t\t}\n\n\t\treturn false, fmt.Errorf(\"%s: %s\", resp.Status, authError.Message)\n\t}\n\n\tvar authResponse crowdAuthentication\n\n\tif err := json.Unmarshal(body, &authResponse); err != nil {\n\t\treturn false, fmt.Errorf(\"decode auth response: %v\", err)\n\t}\n\n\treturn false, nil\n}\n\n// authenticateUser creates a new session for user without password validations with Crowd API\nfunc (c *crowdConnector) authenticateUser(ctx context.Context, client *http.Client, username string) error {\n\treq, err := c.crowdUserManagementRequest(ctx,\n\t\t\"POST\",\n\t\t\"/session?validate-password=false\",\n\t\tstruct {\n\t\t\tUsername string `json:\"username\"`\n\t\t}{Username: username},\n\t)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"crowd: new auth api request %v\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"crowd: api request %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := c.validateCrowdResponse(resp)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif resp.StatusCode != http.StatusCreated {\n\t\treturn fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tvar authResponse crowdAuthentication\n\n\tif err := json.Unmarshal(body, &authResponse); err != nil {\n\t\treturn fmt.Errorf(\"decode auth response: %v\", err)\n\t}\n\n\treturn nil\n}\n\n// user retrieves user info from Crowd API\nfunc (c *crowdConnector) user(ctx context.Context, client *http.Client, username string) (crowdUser, error) {\n\tvar user crowdUser\n\n\treq, err := c.crowdUserManagementRequest(ctx,\n\t\t\"GET\",\n\t\tfmt.Sprintf(\"/user?username=%s\", username),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn user, fmt.Errorf(\"crowd: new user api request %v\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn user, fmt.Errorf(\"crowd: api request %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := c.validateCrowdResponse(resp)\n\tif err != nil {\n\t\treturn user, err\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn user, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.Unmarshal(body, &user); err != nil {\n\t\treturn user, fmt.Errorf(\"failed to decode response: %v\", err)\n\t}\n\n\treturn user, nil\n}\n\n// groups retrieves groups from Crowd API\nfunc (c *crowdConnector) groups(ctx context.Context, client *http.Client, username string) (userGroups []string, err error) {\n\tvar crowdGroups crowdGroups\n\n\treq, err := c.crowdUserManagementRequest(ctx,\n\t\t\"GET\",\n\t\tfmt.Sprintf(\"/user/group/nested?username=%s\", username),\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"crowd: new groups api request %v\", err)\n\t}\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"crowd: api request %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := c.validateCrowdResponse(resp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.Unmarshal(body, &crowdGroups); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode response: %v\", err)\n\t}\n\n\tfor _, group := range crowdGroups.Groups {\n\t\tuserGroups = append(userGroups, group.Name)\n\t}\n\n\treturn userGroups, nil\n}\n\n// identityFromCrowdUser converts crowdUser to Identity\nfunc (c *crowdConnector) identityFromCrowdUser(user crowdUser) connector.Identity {\n\tidentity := connector.Identity{\n\t\tUsername:      user.Name,\n\t\tUserID:        user.Key,\n\t\tEmail:         user.Email,\n\t\tEmailVerified: true,\n\t}\n\n\tswitch c.PreferredUsernameField {\n\tcase \"key\":\n\t\tidentity.PreferredUsername = user.Key\n\tcase \"name\":\n\t\tidentity.PreferredUsername = user.Name\n\tcase \"email\":\n\t\tidentity.PreferredUsername = user.Email\n\tdefault:\n\t\tif c.PreferredUsernameField != \"\" {\n\t\t\tc.logger.Warn(\"preferred_username left empty. Invalid crowd field mapped to preferred_username\", \"field\", c.PreferredUsernameField)\n\t\t}\n\t}\n\n\treturn identity\n}\n\n// getGroups retrieves a list of user's groups and filters it\nfunc (c *crowdConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) {\n\tcrowdGroups, err := c.groups(ctx, client, userLogin)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(c.Groups) > 0 {\n\t\tfilteredGroups := groups.Filter(crowdGroups, c.Groups)\n\t\tif len(filteredGroups) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"crowd: user %q is not in any of the required groups\", userLogin)\n\t\t}\n\t\treturn filteredGroups, nil\n\t} else if groupScope {\n\t\treturn crowdGroups, nil\n\t}\n\n\treturn nil, nil\n}\n\n// crowdUserManagementRequest create a http.Request with basic auth, json payload and Accept header\nfunc (c *crowdConnector) crowdUserManagementRequest(ctx context.Context, method string, apiURL string, jsonPayload interface{}) (*http.Request, error) {\n\tvar body io.Reader\n\tif jsonPayload != nil {\n\t\tjsonData, err := json.Marshal(jsonPayload)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"crowd: marshal API json payload: %v\", err)\n\t\t}\n\t\tbody = bytes.NewReader(jsonData)\n\t}\n\n\treq, err := http.NewRequest(method, fmt.Sprintf(\"%s/rest/usermanagement/1%s\", c.BaseURL, apiURL), body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"new API req: %v\", err)\n\t}\n\treq = req.WithContext(ctx)\n\n\t// Crowd API requires a basic auth\n\treq.SetBasicAuth(c.ClientID, c.ClientSecret)\n\treq.Header.Set(\"Accept\", \"application/json\")\n\tif jsonPayload != nil {\n\t\treq.Header.Set(\"Content-type\", \"application/json\")\n\t}\n\treturn req, nil\n}\n\n// validateCrowdResponse validates unique not JSON responses from API\nfunc (c *crowdConnector) validateCrowdResponse(resp *http.Response) ([]byte, error) {\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"crowd: read user body: %v\", err)\n\t}\n\n\tif resp.StatusCode == http.StatusForbidden && strings.Contains(string(body), \"The server understood the request but refuses to authorize it.\") {\n\t\tc.logger.Debug(\"crowd response validation failed\", \"response\", string(body))\n\t\treturn nil, fmt.Errorf(\"dex is forbidden from making requests to the Atlassian Crowd application by URL %q\", c.BaseURL)\n\t}\n\n\tif resp.StatusCode == http.StatusUnauthorized && string(body) == \"Application failed to authenticate\" {\n\t\tc.logger.Debug(\"crowd response validation failed\", \"response\", string(body))\n\t\treturn nil, fmt.Errorf(\"dex failed to authenticate Crowd Application with ID %q\", c.ClientID)\n\t}\n\treturn body, nil\n}\n"
  },
  {
    "path": "connector/atlassiancrowd/atlassiancrowd_test.go",
    "content": "// Package atlassiancrowd provides authentication strategies using Atlassian Crowd.\npackage atlassiancrowd\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestUserGroups(t *testing.T) {\n\ts := newTestServer(map[string]TestServerResponse{\n\t\t\"/rest/usermanagement/1/user/group/nested?username=testuser\": {\n\t\t\tBody: crowdGroups{Groups: []struct{ Name string }{{Name: \"group1\"}, {Name: \"group2\"}}},\n\t\t\tCode: 200,\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := newTestCrowdConnector(s.URL)\n\tgroups, err := c.getGroups(context.Background(), newClient(), true, \"testuser\")\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\"group1\", \"group2\"})\n}\n\nfunc TestUserGroupsWithFiltering(t *testing.T) {\n\ts := newTestServer(map[string]TestServerResponse{\n\t\t\"/rest/usermanagement/1/user/group/nested?username=testuser\": {\n\t\t\tBody: crowdGroups{Groups: []struct{ Name string }{{Name: \"group1\"}, {Name: \"group2\"}}},\n\t\t\tCode: 200,\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := newTestCrowdConnector(s.URL)\n\tc.Groups = []string{\"group1\"}\n\tgroups, err := c.getGroups(context.Background(), newClient(), true, \"testuser\")\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\"group1\"})\n}\n\nfunc TestUserLoginFlow(t *testing.T) {\n\ts := newTestServer(map[string]TestServerResponse{\n\t\t\"/rest/usermanagement/1/session?validate-password=false\": {\n\t\t\tBody: crowdAuthentication{},\n\t\t\tCode: 201,\n\t\t},\n\t\t\"/rest/usermanagement/1/user?username=testuser\": {\n\t\t\tBody: crowdUser{Active: true, Name: \"testuser\", Email: \"testuser@example.com\"},\n\t\t\tCode: 200,\n\t\t},\n\t\t\"/rest/usermanagement/1/user?username=testuser2\": {\n\t\t\tBody: `<html>The server understood the request but refuses to authorize it.</html>`,\n\t\t\tCode: 403,\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := newTestCrowdConnector(s.URL)\n\tuser, err := c.user(context.Background(), newClient(), \"testuser\")\n\texpectNil(t, err)\n\texpectEquals(t, user.Name, \"testuser\")\n\texpectEquals(t, user.Email, \"testuser@example.com\")\n\n\terr = c.authenticateUser(context.Background(), newClient(), \"testuser\")\n\texpectNil(t, err)\n\n\t_, err = c.user(context.Background(), newClient(), \"testuser2\")\n\texpectEquals(t, err, fmt.Errorf(\"dex is forbidden from making requests to the Atlassian Crowd application by URL %q\", s.URL))\n}\n\nfunc TestUserPassword(t *testing.T) {\n\ts := newTestServer(map[string]TestServerResponse{\n\t\t\"/rest/usermanagement/1/session\": {\n\t\t\tBody: crowdAuthenticationError{Reason: \"INVALID_USER_AUTHENTICATION\", Message: \"test\"},\n\t\t\tCode: 401,\n\t\t},\n\t\t\"/rest/usermanagement/1/session?validate-password=false\": {\n\t\t\tBody: crowdAuthentication{},\n\t\t\tCode: 201,\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := newTestCrowdConnector(s.URL)\n\tinvalidPassword, err := c.authenticateWithPassword(context.Background(), newClient(), \"testuser\", \"testpassword\")\n\n\texpectNil(t, err)\n\texpectEquals(t, invalidPassword, true)\n\n\terr = c.authenticateUser(context.Background(), newClient(), \"testuser\")\n\texpectNil(t, err)\n}\n\nfunc TestIdentityFromCrowdUser(t *testing.T) {\n\tuser := crowdUser{\n\t\tKey:    \"12345\",\n\t\tName:   \"testuser\",\n\t\tActive: true,\n\t\tEmail:  \"testuser@example.com\",\n\t}\n\n\tc := newTestCrowdConnector(\"/\")\n\n\t// Sanity checks\n\texpectEquals(t, user.Name, \"testuser\")\n\texpectEquals(t, user.Email, \"testuser@example.com\")\n\n\t// Test unconfigured behavior\n\ti := c.identityFromCrowdUser(user)\n\texpectEquals(t, i.UserID, \"12345\")\n\texpectEquals(t, i.Username, \"testuser\")\n\texpectEquals(t, i.Email, \"testuser@example.com\")\n\texpectEquals(t, i.EmailVerified, true)\n\n\t// Test for various PreferredUsernameField settings\n\t// unset\n\texpectEquals(t, i.PreferredUsername, \"\")\n\n\tc.Config.PreferredUsernameField = \"key\"\n\ti = c.identityFromCrowdUser(user)\n\texpectEquals(t, i.PreferredUsername, \"12345\")\n\n\tc.Config.PreferredUsernameField = \"name\"\n\ti = c.identityFromCrowdUser(user)\n\texpectEquals(t, i.PreferredUsername, \"testuser\")\n\n\tc.Config.PreferredUsernameField = \"email\"\n\ti = c.identityFromCrowdUser(user)\n\texpectEquals(t, i.PreferredUsername, \"testuser@example.com\")\n\n\tc.Config.PreferredUsernameField = \"invalidstring\"\n\ti = c.identityFromCrowdUser(user)\n\texpectEquals(t, i.PreferredUsername, \"\")\n}\n\ntype TestServerResponse struct {\n\tBody interface{}\n\tCode int\n}\n\nfunc newTestCrowdConnector(baseURL string) crowdConnector {\n\tconnector := crowdConnector{}\n\tconnector.BaseURL = baseURL\n\tconnector.logger = slog.New(slog.DiscardHandler)\n\treturn connector\n}\n\nfunc newTestServer(responses map[string]TestServerResponse) *httptest.Server {\n\ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresponse := responses[r.RequestURI]\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(response.Code)\n\t\tjson.NewEncoder(w).Encode(response.Body)\n\t}))\n\treturn s\n}\n\nfunc newClient() *http.Client {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\treturn &http.Client{Transport: tr}\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Errorf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/authproxy/authproxy.go",
    "content": "// Package authproxy implements a connector which relies on external\n// authentication (e.g. mod_auth in Apache2) and returns an identity with the\n// HTTP header X-Remote-User as verified email.\npackage authproxy\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// Config holds the configuration parameters for a connector which returns an\n// identity with the HTTP header X-Remote-User as verified email,\n// X-Remote-Group and configured staticGroups as user's group.\n// Headers retrieved to fetch user's email and group can be configured\n// with userHeader and groupHeader.\ntype Config struct {\n\tUserIDHeader         string   `json:\"userIDHeader\"`\n\tUserHeader           string   `json:\"userHeader\"`\n\tUserNameHeader       string   `json:\"userNameHeader\"`\n\tEmailHeader          string   `json:\"emailHeader\"`\n\tGroupHeader          string   `json:\"groupHeader\"`\n\tGroupHeaderSeparator string   `json:\"groupHeaderSeparator\"`\n\tGroups               []string `json:\"staticGroups\"`\n}\n\n// Open returns an authentication strategy which requires no user interaction.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tuserIDHeader := c.UserIDHeader\n\tif userIDHeader == \"\" {\n\t\tuserIDHeader = \"X-Remote-User-Id\"\n\t}\n\tuserHeader := c.UserHeader\n\tif userHeader == \"\" {\n\t\tuserHeader = \"X-Remote-User\"\n\t}\n\tuserNameHeader := c.UserNameHeader\n\tif userNameHeader == \"\" {\n\t\tuserNameHeader = \"X-Remote-User-Name\"\n\t}\n\temailHeader := c.EmailHeader\n\tif emailHeader == \"\" {\n\t\temailHeader = \"X-Remote-User-Email\"\n\t}\n\tgroupHeader := c.GroupHeader\n\tif groupHeader == \"\" {\n\t\tgroupHeader = \"X-Remote-Group\"\n\t}\n\tgroupHeaderSeparator := c.GroupHeaderSeparator\n\tif groupHeaderSeparator == \"\" {\n\t\tgroupHeaderSeparator = \",\"\n\t}\n\n\treturn &callback{\n\t\tuserIDHeader:         userIDHeader,\n\t\tuserHeader:           userHeader,\n\t\tuserNameHeader:       userNameHeader,\n\t\temailHeader:          emailHeader,\n\t\tgroupHeader:          groupHeader,\n\t\tgroupHeaderSeparator: groupHeaderSeparator,\n\t\tgroups:               c.Groups,\n\t\tlogger:               logger.With(slog.Group(\"connector\", \"type\", \"authproxy\", \"id\", id)),\n\t\tpathSuffix:           \"/\" + id,\n\t}, nil\n}\n\nvar _ connector.CallbackConnector = (*callback)(nil)\n\n// Callback is a connector which returns an identity with the HTTP header\n// X-Remote-User as verified email.\ntype callback struct {\n\tuserIDHeader         string\n\tuserNameHeader       string\n\tuserHeader           string\n\temailHeader          string\n\tgroupHeader          string\n\tgroupHeaderSeparator string\n\tgroups               []string\n\tlogger               *slog.Logger\n\tpathSuffix           string\n}\n\n// LoginURL returns the URL to redirect the user to login with.\nfunc (m *callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tu, err := url.Parse(callbackURL)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"failed to parse callbackURL %q: %v\", callbackURL, err)\n\t}\n\tu.Path += m.pathSuffix\n\tv := u.Query()\n\tv.Set(\"state\", state)\n\tu.RawQuery = v.Encode()\n\treturn u.String(), nil, nil\n}\n\n// HandleCallback parses the request and returns the user's identity\nfunc (m *callback) HandleCallback(s connector.Scopes, _ []byte, r *http.Request) (connector.Identity, error) {\n\tremoteUser := r.Header.Get(m.userHeader)\n\tif remoteUser == \"\" {\n\t\treturn connector.Identity{}, fmt.Errorf(\"required HTTP header %s is not set\", m.userHeader)\n\t}\n\tremoteUserName := r.Header.Get(m.userNameHeader)\n\tif remoteUserName == \"\" {\n\t\tremoteUserName = remoteUser\n\t}\n\tremoteUserID := r.Header.Get(m.userIDHeader)\n\tif remoteUserID == \"\" {\n\t\tremoteUserID = remoteUser\n\t}\n\tremoteUserEmail := r.Header.Get(m.emailHeader)\n\tif remoteUserEmail == \"\" {\n\t\tremoteUserEmail = remoteUser\n\t}\n\tgroups := m.groups\n\theaderGroup := r.Header.Get(m.groupHeader)\n\tif headerGroup != \"\" {\n\t\tsplitheaderGroup := strings.Split(headerGroup, m.groupHeaderSeparator)\n\t\tfor i, v := range splitheaderGroup {\n\t\t\tsplitheaderGroup[i] = strings.TrimSpace(v)\n\t\t}\n\t\tgroups = append(splitheaderGroup, groups...)\n\t}\n\treturn connector.Identity{\n\t\tUserID:            remoteUserID,\n\t\tUsername:          remoteUser,\n\t\tPreferredUsername: remoteUserName,\n\t\tEmail:             remoteUserEmail,\n\t\tEmailVerified:     true,\n\t\tGroups:            groups,\n\t}, nil\n}\n"
  },
  {
    "path": "connector/authproxy/authproxy_test.go",
    "content": "package authproxy\n\nimport (\n\t\"log/slog\"\n\t\"net/http\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nconst (\n\ttestEmail             = \"testuser@example.com\"\n\ttestGroup1            = \"group1\"\n\ttestGroup2            = \"group2\"\n\ttestGroup3            = \"group 3\"\n\ttestGroup4            = \"group 4\"\n\ttestStaticGroup1      = \"static1\"\n\ttestStaticGroup2      = \"static 2\"\n\ttestUsername          = \"Test User\"\n\ttestPreferredUsername = \"testuser\"\n\ttestUserID            = \"1234567890\"\n)\n\nvar logger = slog.New(slog.DiscardHandler)\n\nfunc TestUser(t *testing.T) {\n\tconfig := Config{}\n\n\tconn, _ := config.Open(\"test\", logger)\n\tcallback := conn.(*callback)\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\texpectNil(t, err)\n\treq.Header = map[string][]string{\n\t\t\"X-Remote-User\": {testUsername},\n\t}\n\n\tident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req)\n\texpectNil(t, err)\n\n\t// If not specified, the userID and email should fall back to the remote user\n\texpectEquals(t, ident.UserID, testUsername)\n\texpectEquals(t, ident.PreferredUsername, testUsername)\n\texpectEquals(t, ident.Username, testUsername)\n\texpectEquals(t, ident.Email, testUsername)\n\texpectEquals(t, len(ident.Groups), 0)\n}\n\nfunc TestExtraHeaders(t *testing.T) {\n\tconfig := Config{}\n\n\tconn, _ := config.Open(\"test\", logger)\n\tcallback := conn.(*callback)\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\texpectNil(t, err)\n\treq.Header = map[string][]string{\n\t\t\"X-Remote-User-Id\":    {testUserID},\n\t\t\"X-Remote-User\":       {testUsername},\n\t\t\"X-Remote-User-Name\":  {testPreferredUsername},\n\t\t\"X-Remote-User-Email\": {testEmail},\n\t}\n\n\tident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req)\n\texpectNil(t, err)\n\n\texpectEquals(t, ident.UserID, testUserID)\n\texpectEquals(t, ident.PreferredUsername, testPreferredUsername)\n\texpectEquals(t, ident.Username, testUsername)\n\texpectEquals(t, ident.Email, testEmail)\n\texpectEquals(t, len(ident.Groups), 0)\n}\n\nfunc TestSingleGroup(t *testing.T) {\n\tconfig := Config{}\n\n\tconn, _ := config.Open(\"test\", logger)\n\tcallback := conn.(*callback)\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\texpectNil(t, err)\n\treq.Header = map[string][]string{\n\t\t\"X-Remote-User\":  {testEmail},\n\t\t\"X-Remote-Group\": {testGroup1},\n\t}\n\n\tident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req)\n\texpectNil(t, err)\n\n\texpectEquals(t, ident.UserID, testEmail)\n\texpectEquals(t, len(ident.Groups), 1)\n\texpectEquals(t, ident.Groups[0], testGroup1)\n}\n\nfunc TestMultipleGroup(t *testing.T) {\n\tconfig := Config{}\n\n\tconn, _ := config.Open(\"test\", logger)\n\tcallback := conn.(*callback)\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\texpectNil(t, err)\n\treq.Header = map[string][]string{\n\t\t\"X-Remote-User\":  {testEmail},\n\t\t\"X-Remote-Group\": {testGroup1 + \", \" + testGroup2 + \", \" + testGroup3 + \", \" + testGroup4},\n\t}\n\n\tident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req)\n\texpectNil(t, err)\n\n\texpectEquals(t, ident.UserID, testEmail)\n\texpectEquals(t, len(ident.Groups), 4)\n\texpectEquals(t, ident.Groups[0], testGroup1)\n\texpectEquals(t, ident.Groups[1], testGroup2)\n\texpectEquals(t, ident.Groups[2], testGroup3)\n\texpectEquals(t, ident.Groups[3], testGroup4)\n}\n\nfunc TestMultipleGroupWithCustomSeparator(t *testing.T) {\n\tconfig := Config{\n\t\tGroupHeaderSeparator: \";\",\n\t}\n\n\tconn, _ := config.Open(\"test\", logger)\n\tcallback := conn.(*callback)\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\texpectNil(t, err)\n\treq.Header = map[string][]string{\n\t\t\"X-Remote-User\":  {testEmail},\n\t\t\"X-Remote-Group\": {testGroup1 + \";\" + testGroup2 + \";\" + testGroup3 + \";\" + testGroup4},\n\t}\n\n\tident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req)\n\texpectNil(t, err)\n\n\texpectEquals(t, ident.UserID, testEmail)\n\texpectEquals(t, len(ident.Groups), 4)\n\texpectEquals(t, ident.Groups[0], testGroup1)\n\texpectEquals(t, ident.Groups[1], testGroup2)\n\texpectEquals(t, ident.Groups[2], testGroup3)\n\texpectEquals(t, ident.Groups[3], testGroup4)\n}\n\nfunc TestStaticGroup(t *testing.T) {\n\tconfig := Config{\n\t\tGroups: []string{\"static1\", \"static 2\"},\n\t}\n\n\tconn, _ := config.Open(\"test\", logger)\n\tcallback := conn.(*callback)\n\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\texpectNil(t, err)\n\treq.Header = map[string][]string{\n\t\t\"X-Remote-User\":  {testEmail},\n\t\t\"X-Remote-Group\": {testGroup1 + \", \" + testGroup2 + \", \" + testGroup3 + \", \" + testGroup4},\n\t}\n\n\tident, err := callback.HandleCallback(connector.Scopes{OfflineAccess: true, Groups: true}, nil, req)\n\texpectNil(t, err)\n\n\texpectEquals(t, ident.UserID, testEmail)\n\texpectEquals(t, len(ident.Groups), 6)\n\texpectEquals(t, ident.Groups[0], testGroup1)\n\texpectEquals(t, ident.Groups[1], testGroup2)\n\texpectEquals(t, ident.Groups[2], testGroup3)\n\texpectEquals(t, ident.Groups[3], testGroup4)\n\texpectEquals(t, ident.Groups[4], testStaticGroup1)\n\texpectEquals(t, ident.Groups[5], testStaticGroup2)\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Errorf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/bitbucketcloud/bitbucketcloud.go",
    "content": "// Package bitbucketcloud provides authentication strategies using Bitbucket Cloud.\npackage bitbucketcloud\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"sync\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/bitbucket\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/groups\"\n)\n\nconst (\n\tapiURL = \"https://api.bitbucket.org/2.0\"\n\t// Switch to API v2.0 when the Atlassian platform services are fully available in Bitbucket\n\tlegacyAPIURL = \"https://api.bitbucket.org/1.0\"\n\t// Bitbucket requires this scope to access '/user' API endpoints.\n\tscopeAccount = \"account\"\n\t// Bitbucket requires this scope to access '/user/emails' API endpoints.\n\tscopeEmail = \"email\"\n\t// Bitbucket requires this scope to access '/teams' API endpoints\n\t// which are used when a client includes the 'groups' scope.\n\tscopeTeams = \"team\"\n)\n\n// Config holds configuration options for Bitbucket logins.\ntype Config struct {\n\tClientID          string   `json:\"clientID\"`\n\tClientSecret      string   `json:\"clientSecret\"`\n\tRedirectURI       string   `json:\"redirectURI\"`\n\tTeams             []string `json:\"teams\"`\n\tIncludeTeamGroups bool     `json:\"includeTeamGroups,omitempty\"`\n}\n\n// Open returns a strategy for logging in through Bitbucket.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tb := bitbucketConnector{\n\t\tredirectURI:       c.RedirectURI,\n\t\tteams:             c.Teams,\n\t\tclientID:          c.ClientID,\n\t\tclientSecret:      c.ClientSecret,\n\t\tincludeTeamGroups: c.IncludeTeamGroups,\n\t\tapiURL:            apiURL,\n\t\tlegacyAPIURL:      legacyAPIURL,\n\t\tlogger:            logger.With(slog.Group(\"connector\", \"type\", \"bitbucketcloud\", \"id\", id)),\n\t}\n\n\treturn &b, nil\n}\n\ntype connectorData struct {\n\tAccessToken  string    `json:\"accessToken\"`\n\tRefreshToken string    `json:\"refreshToken\"`\n\tExpiry       time.Time `json:\"expiry\"`\n}\n\nvar (\n\t_ connector.CallbackConnector = (*bitbucketConnector)(nil)\n\t_ connector.RefreshConnector  = (*bitbucketConnector)(nil)\n)\n\ntype bitbucketConnector struct {\n\tredirectURI  string\n\tteams        []string\n\tclientID     string\n\tclientSecret string\n\tlogger       *slog.Logger\n\tapiURL       string\n\tlegacyAPIURL string\n\n\t// the following are used only for tests\n\thostName   string\n\thttpClient *http.Client\n\n\tincludeTeamGroups bool\n}\n\n// groupsRequired returns whether dex requires Bitbucket's 'team' scope.\nfunc (b *bitbucketConnector) groupsRequired(groupScope bool) bool {\n\treturn len(b.teams) > 0 || groupScope\n}\n\nfunc (b *bitbucketConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {\n\tbitbucketScopes := []string{scopeAccount, scopeEmail}\n\tif b.groupsRequired(scopes.Groups) {\n\t\tbitbucketScopes = append(bitbucketScopes, scopeTeams)\n\t}\n\n\tendpoint := bitbucket.Endpoint\n\tif b.hostName != \"\" {\n\t\tendpoint = oauth2.Endpoint{\n\t\t\tAuthURL:  \"https://\" + b.hostName + \"/site/oauth2/authorize\",\n\t\t\tTokenURL: \"https://\" + b.hostName + \"/site/oauth2/access_token\",\n\t\t}\n\t}\n\n\treturn &oauth2.Config{\n\t\tClientID:     b.clientID,\n\t\tClientSecret: b.clientSecret,\n\t\tEndpoint:     endpoint,\n\t\tScopes:       bitbucketScopes,\n\t}\n}\n\nfunc (b *bitbucketConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif b.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", callbackURL, b.redirectURI)\n\t}\n\n\treturn b.oauth2Config(scopes).AuthCodeURL(state), nil, nil\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n\nfunc (b *bitbucketConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\toauth2Config := b.oauth2Config(s)\n\n\tctx := r.Context()\n\tif b.httpClient != nil {\n\t\tctx = context.WithValue(r.Context(), oauth2.HTTPClient, b.httpClient)\n\t}\n\n\ttoken, err := oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"bitbucket: failed to get token: %v\", err)\n\t}\n\n\tclient := oauth2Config.Client(ctx, token)\n\n\tuser, err := b.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"bitbucket: get user: %v\", err)\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:        user.UUID,\n\t\tUsername:      user.Username,\n\t\tEmail:         user.Email,\n\t\tEmailVerified: true,\n\t}\n\n\tif b.groupsRequired(s.Groups) {\n\t\tgroups, err := b.getGroups(ctx, client, s.Groups, user.Username)\n\t\tif err != nil {\n\t\t\treturn identity, err\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\tif s.OfflineAccess {\n\t\tdata := connectorData{\n\t\t\tAccessToken:  token.AccessToken,\n\t\t\tRefreshToken: token.RefreshToken,\n\t\t\tExpiry:       token.Expiry,\n\t\t}\n\t\tconnData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"bitbucket: marshal connector data: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\n// Refreshing tokens\n// https://github.com/golang/oauth2/issues/84#issuecomment-332860871\ntype tokenNotifyFunc func(*oauth2.Token) error\n\n// notifyRefreshTokenSource is essentially `oauth2.ReuseTokenSource` with `TokenNotifyFunc` added.\ntype notifyRefreshTokenSource struct {\n\tnew oauth2.TokenSource\n\tmu  sync.Mutex // guards t\n\tt   *oauth2.Token\n\tf   tokenNotifyFunc // called when token refreshed so new refresh token can be persisted\n}\n\n// Token returns the current token if it's still valid, else will\n// refresh the current token (using r.Context for HTTP client\n// information) and return the new one.\nfunc (s *notifyRefreshTokenSource) Token() (*oauth2.Token, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif s.t.Valid() {\n\t\treturn s.t, nil\n\t}\n\tt, err := s.new.Token()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.t = t\n\treturn t, s.f(t)\n}\n\nfunc (b *bitbucketConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\tif len(identity.ConnectorData) == 0 {\n\t\treturn identity, errors.New(\"bitbucket: no upstream access token found\")\n\t}\n\n\tvar data connectorData\n\tif err := json.Unmarshal(identity.ConnectorData, &data); err != nil {\n\t\treturn identity, fmt.Errorf(\"bitbucket: unmarshal access token: %v\", err)\n\t}\n\n\ttok := &oauth2.Token{\n\t\tAccessToken:  data.AccessToken,\n\t\tRefreshToken: data.RefreshToken,\n\t\tExpiry:       data.Expiry,\n\t}\n\n\tclient := oauth2.NewClient(ctx, &notifyRefreshTokenSource{\n\t\tnew: b.oauth2Config(s).TokenSource(ctx, tok),\n\t\tt:   tok,\n\t\tf: func(tok *oauth2.Token) error {\n\t\t\tdata := connectorData{\n\t\t\t\tAccessToken:  tok.AccessToken,\n\t\t\t\tRefreshToken: tok.RefreshToken,\n\t\t\t\tExpiry:       tok.Expiry,\n\t\t\t}\n\t\t\tconnData, err := json.Marshal(data)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"bitbucket: marshal connector data: %v\", err)\n\t\t\t}\n\t\t\tidentity.ConnectorData = connData\n\t\t\treturn nil\n\t\t},\n\t})\n\n\tuser, err := b.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"bitbucket: get user: %v\", err)\n\t}\n\n\tidentity.Username = user.Username\n\tidentity.Email = user.Email\n\n\tif b.groupsRequired(s.Groups) {\n\t\tgroups, err := b.getGroups(ctx, client, s.Groups, user.Username)\n\t\tif err != nil {\n\t\t\treturn identity, err\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\treturn identity, nil\n}\n\n// Bitbucket pagination wrapper\ntype pagedResponse struct {\n\tSize     int     `json:\"size\"`\n\tPage     int     `json:\"page\"`\n\tPageLen  int     `json:\"pagelen\"`\n\tNext     *string `json:\"next\"`\n\tPrevious *string `json:\"previous\"`\n}\n\n// user holds Bitbucket user information (relevant to dex) as defined by\n// https://developer.atlassian.com/bitbucket/api/2/reference/resource/user\ntype user struct {\n\tUsername string `json:\"username\"`\n\tUUID     string `json:\"uuid\"`\n\tEmail    string `json:\"email\"`\n}\n\n// user queries the Bitbucket API for profile information using the provided client.\n//\n// The HTTP client is expected to be constructed by the golang.org/x/oauth2 package,\n// which inserts a bearer token as part of the request.\nfunc (b *bitbucketConnector) user(ctx context.Context, client *http.Client) (user, error) {\n\t// https://developer.atlassian.com/bitbucket/api/2/reference/resource/user\n\tvar (\n\t\tu   user\n\t\terr error\n\t)\n\n\tif err = get(ctx, client, b.apiURL+\"/user\", &u); err != nil {\n\t\treturn user{}, err\n\t}\n\n\tif u.Email, err = b.userEmail(ctx, client); err != nil {\n\t\treturn user{}, err\n\t}\n\n\treturn u, nil\n}\n\n// userEmail holds Bitbucket user email information as defined by\n// https://developer.atlassian.com/bitbucket/api/2/reference/resource/user/emails\ntype userEmail struct {\n\tIsPrimary   bool   `json:\"is_primary\"`\n\tIsConfirmed bool   `json:\"is_confirmed\"`\n\tEmail       string `json:\"email\"`\n}\n\ntype userEmailResponse struct {\n\tpagedResponse\n\tValues []userEmail\n}\n\n// userEmail returns the users primary, confirmed email\n//\n// The HTTP client is expected to be constructed by the golang.org/x/oauth2 package,\n// which inserts a bearer token as part of the request.\nfunc (b *bitbucketConnector) userEmail(ctx context.Context, client *http.Client) (string, error) {\n\tapiURL := b.apiURL + \"/user/emails\"\n\tfor {\n\t\t// https://developer.atlassian.com/bitbucket/api/2/reference/resource/user/emails\n\t\tvar response userEmailResponse\n\n\t\tif err := get(ctx, client, apiURL, &response); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfor _, email := range response.Values {\n\t\t\tif email.IsConfirmed && email.IsPrimary {\n\t\t\t\treturn email.Email, nil\n\t\t\t}\n\t\t}\n\n\t\tif response.Next == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn \"\", errors.New(\"bitbucket: user has no confirmed, primary email\")\n}\n\n// getGroups retrieves Bitbucket teams a user is in, if any.\nfunc (b *bitbucketConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) {\n\tbitbucketTeams, err := b.userWorkspaces(ctx, client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(b.teams) > 0 {\n\t\tfilteredTeams := groups.Filter(bitbucketTeams, b.teams)\n\t\tif len(filteredTeams) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"bitbucket: user %q is not in any of the required teams\", userLogin)\n\t\t}\n\t\treturn filteredTeams, nil\n\t} else if groupScope {\n\t\treturn bitbucketTeams, nil\n\t}\n\n\treturn nil, nil\n}\n\ntype workspaceSlug struct {\n\tSlug string `json:\"slug\"`\n}\n\ntype workspace struct {\n\tWorkspace workspaceSlug `json:\"workspace\"`\n}\n\ntype userWorkspacesResponse struct {\n\tpagedResponse\n\tValues []workspace `json:\"values\"`\n}\n\nfunc (b *bitbucketConnector) userWorkspaces(ctx context.Context, client *http.Client) ([]string, error) {\n\tvar teams []string\n\tapiURL := b.apiURL + \"/user/permissions/workspaces\"\n\n\tfor {\n\t\t// https://developer.atlassian.com/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-get\n\t\tvar response userWorkspacesResponse\n\n\t\tif err := get(ctx, client, apiURL, &response); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"bitbucket: get user teams: %v\", err)\n\t\t}\n\n\t\tfor _, value := range response.Values {\n\t\t\tteams = append(teams, value.Workspace.Slug)\n\t\t}\n\n\t\tif response.Next == nil {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif b.includeTeamGroups {\n\t\tfor _, team := range teams {\n\t\t\tteamGroups, err := b.userTeamGroups(ctx, client, team)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"bitbucket: %v\", err)\n\t\t\t}\n\t\t\tteams = append(teams, teamGroups...)\n\t\t}\n\t}\n\n\treturn teams, nil\n}\n\ntype group struct {\n\tSlug string `json:\"slug\"`\n}\n\nfunc (b *bitbucketConnector) userTeamGroups(ctx context.Context, client *http.Client, teamName string) ([]string, error) {\n\tapiURL := b.legacyAPIURL + \"/groups/\" + teamName\n\n\tvar response []group\n\tif err := get(ctx, client, apiURL, &response); err != nil {\n\t\treturn nil, fmt.Errorf(\"get user team %q groups: %v\", teamName, err)\n\t}\n\n\tteamGroups := make([]string, 0, len(response))\n\tfor _, group := range response {\n\t\tteamGroups = append(teamGroups, teamName+\"/\"+group.Slug)\n\t}\n\n\treturn teamGroups, nil\n}\n\n// get creates a \"GET `apiURL`\" request with context, sends the request using\n// the client, and decodes the resulting response body into v.\n// Any errors encountered when building requests, sending requests, and\n// reading and decoding response data are returned.\nfunc get(ctx context.Context, client *http.Client, apiURL string, v interface{}) error {\n\treq, err := http.NewRequest(\"GET\", apiURL, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"bitbucket: new req: %v\", err)\n\t}\n\treq = req.WithContext(ctx)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"bitbucket: get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"bitbucket: read body: %s: %v\", resp.Status, err)\n\t\t}\n\t\treturn fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(v); err != nil {\n\t\treturn fmt.Errorf(\"bitbucket: failed to decode response: %v\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "connector/bitbucketcloud/bitbucketcloud_test.go",
    "content": "package bitbucketcloud\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nfunc TestUserGroups(t *testing.T) {\n\tteamsResponse := userWorkspacesResponse{\n\t\tpagedResponse: pagedResponse{\n\t\t\tSize:    3,\n\t\t\tPage:    1,\n\t\t\tPageLen: 10,\n\t\t},\n\t\tValues: []workspace{\n\t\t\t{Workspace: workspaceSlug{Slug: \"team-1\"}},\n\t\t\t{Workspace: workspaceSlug{Slug: \"team-2\"}},\n\t\t\t{Workspace: workspaceSlug{Slug: \"team-3\"}},\n\t\t},\n\t}\n\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/user/permissions/workspaces\": teamsResponse,\n\t\t\"/groups/team-1\":               []group{{Slug: \"administrators\"}, {Slug: \"members\"}},\n\t\t\"/groups/team-2\":               []group{{Slug: \"everyone\"}},\n\t\t\"/groups/team-3\":               []group{},\n\t})\n\n\tconnector := bitbucketConnector{apiURL: s.URL, legacyAPIURL: s.URL}\n\tgroups, err := connector.userWorkspaces(context.Background(), newClient())\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\n\t\t\"team-1\",\n\t\t\"team-2\",\n\t\t\"team-3\",\n\t})\n\n\tconnector.includeTeamGroups = true\n\tgroups, err = connector.userWorkspaces(context.Background(), newClient())\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\n\t\t\"team-1\",\n\t\t\"team-2\",\n\t\t\"team-3\",\n\t\t\"team-1/administrators\",\n\t\t\"team-1/members\",\n\t\t\"team-2/everyone\",\n\t})\n\n\ts.Close()\n}\n\nfunc TestUserWithoutTeams(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/user/permissions/workspaces\": userWorkspacesResponse{},\n\t})\n\n\tconnector := bitbucketConnector{apiURL: s.URL}\n\tgroups, err := connector.userWorkspaces(context.Background(), newClient())\n\n\texpectNil(t, err)\n\texpectEquals(t, len(groups), 0)\n\n\ts.Close()\n}\n\nfunc TestUsernameIncludedInFederatedIdentity(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/user\": user{Username: \"some-login\"},\n\t\t\"/user/emails\": userEmailResponse{\n\t\t\tpagedResponse: pagedResponse{\n\t\t\t\tSize:    1,\n\t\t\t\tPage:    1,\n\t\t\t\tPageLen: 10,\n\t\t\t},\n\t\t\tValues: []userEmail{{\n\t\t\t\tEmail:       \"some@email.com\",\n\t\t\t\tIsConfirmed: true,\n\t\t\t\tIsPrimary:   true,\n\t\t\t}},\n\t\t},\n\t\t\"/site/oauth2/access_token\": map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t})\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tbitbucketConnector := bitbucketConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient()}\n\tidentity, err := bitbucketConnector.HandleCallback(connector.Scopes{}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some-login\")\n\n\ts.Close()\n}\n\nfunc newTestServer(responses map[string]interface{}) *httptest.Server {\n\treturn httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(responses[r.URL.String()])\n\t}))\n}\n\nfunc newClient() *http.Client {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\treturn &http.Client{Transport: tr}\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Fatalf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Fatalf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/connector.go",
    "content": "// Package connector defines interfaces for federated identity strategies.\npackage connector\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// UserNotInRequiredGroupsError is returned by a connector when a user\n// successfully authenticates but is not a member of any of the required groups.\n// The server will respond with HTTP 403 Forbidden instead of 500.\ntype UserNotInRequiredGroupsError struct {\n\tUserID string\n\tGroups []string\n}\n\nfunc (e *UserNotInRequiredGroupsError) Error() string {\n\treturn fmt.Sprintf(\"user %q is not in any of the required groups %v\", e.UserID, e.Groups)\n}\n\n// Connector is a mechanism for federating login to a remote identity service.\n//\n// Implementations are expected to implement either the PasswordConnector or\n// CallbackConnector interface.\ntype Connector interface{}\n\n// Scopes represents additional data requested by the clients about the end user.\ntype Scopes struct {\n\t// The client has requested a refresh token from the server.\n\tOfflineAccess bool\n\n\t// The client has requested group information about the end user.\n\tGroups bool\n}\n\n// Identity represents the ID Token claims supported by the server.\ntype Identity struct {\n\tUserID            string\n\tUsername          string\n\tPreferredUsername string\n\tEmail             string\n\tEmailVerified     bool\n\n\tGroups []string\n\n\t// ConnectorData holds data used by the connector for subsequent requests after initial\n\t// authentication, such as access tokens for upstream provides.\n\t//\n\t// This data is never shared with end users, OAuth clients, or through the API.\n\tConnectorData []byte\n}\n\n// PasswordConnector is an interface implemented by connectors which take a\n// username and password.\n// Prompt() is used to inform the handler what to display in the password\n// template. If this returns an empty string, it'll default to \"Username\".\ntype PasswordConnector interface {\n\tPrompt() string\n\tLogin(ctx context.Context, s Scopes, username, password string) (identity Identity, validPassword bool, err error)\n}\n\n// CallbackConnector is an interface implemented by connectors which use an OAuth\n// style redirect flow to determine user information.\ntype CallbackConnector interface {\n\t// The initial URL to redirect the user to.\n\t//\n\t// OAuth2 implementations should request different scopes from the upstream\n\t// identity provider based on the scopes requested by the downstream client.\n\t// For example, if the downstream client requests a refresh token from the\n\t// server, the connector should also request a token from the provider.\n\t//\n\t// Many identity providers have arbitrary restrictions on refresh tokens. For\n\t// example Google only allows a single refresh token per client/user/scopes\n\t// combination, and wont return a refresh token even if offline access is\n\t// requested if one has already been issues. There's no good general answer\n\t// for these kind of restrictions, and may require this package to become more\n\t// aware of the global set of user/connector interactions.\n\tLoginURL(s Scopes, callbackURL, state string) (string, []byte, error)\n\n\t// Handle the callback to the server and return an identity.\n\tHandleCallback(s Scopes, connData []byte, r *http.Request) (identity Identity, err error)\n}\n\n// SAMLConnector represents SAML connectors which implement the HTTP POST binding.\n//\n//\tRelayState is handled by the server.\n//\n// See: https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf\n// \"3.5 HTTP POST Binding\"\ntype SAMLConnector interface {\n\t// POSTData returns an encoded SAML request and SSO URL for the server to\n\t// render a POST form with.\n\t//\n\t// POSTData should encode the provided request ID in the returned serialized\n\t// SAML request.\n\tPOSTData(s Scopes, requestID string) (ssoURL, samlRequest string, err error)\n\n\t// HandlePOST decodes, verifies, and maps attributes from the SAML response.\n\t// It passes the expected value of the \"InResponseTo\" response field, which\n\t// the connector must ensure matches the response value.\n\t//\n\t// See: https://www.oasis-open.org/committees/download.php/35711/sstc-saml-core-errata-2.0-wd-06-diff.pdf\n\t// \"3.2.2 Complex Type StatusResponseType\"\n\tHandlePOST(s Scopes, samlResponse, inResponseTo string) (identity Identity, err error)\n}\n\n// RefreshConnector is a connector that can update the client claims.\ntype RefreshConnector interface {\n\t// Refresh is called when a client attempts to claim a refresh token. The\n\t// connector should attempt to update the identity object to reflect any\n\t// changes since the token was last refreshed.\n\tRefresh(ctx context.Context, s Scopes, identity Identity) (Identity, error)\n}\n\ntype TokenIdentityConnector interface {\n\tTokenIdentity(ctx context.Context, subjectTokenType, subjectToken string) (Identity, error)\n}\n"
  },
  {
    "path": "connector/gitea/gitea.go",
    "content": "// Package gitea provides authentication strategies using Gitea.\npackage gitea\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// Config holds configuration options for gitea logins.\ntype Config struct {\n\tBaseURL       string `json:\"baseURL\"`\n\tClientID      string `json:\"clientID\"`\n\tClientSecret  string `json:\"clientSecret\"`\n\tRedirectURI   string `json:\"redirectURI\"`\n\tOrgs          []Org  `json:\"orgs\"`\n\tLoadAllGroups bool   `json:\"loadAllGroups\"`\n\tUseLoginAsID  bool   `json:\"useLoginAsID\"`\n}\n\n// Org holds org-team filters, in which teams are optional.\ntype Org struct {\n\t// Organization name in gitea (not slug, full name). Only users in this gitea\n\t// organization can authenticate.\n\tName string `json:\"name\"`\n\n\t// Names of teams in a gitea organization. A user will be able to\n\t// authenticate if they are members of at least one of these teams. Users\n\t// in the organization can authenticate if this field is omitted from the\n\t// config file.\n\tTeams []string `json:\"teams,omitempty\"`\n}\n\ntype giteaUser struct {\n\tID       int    `json:\"id\"`\n\tName     string `json:\"full_name\"`\n\tUsername string `json:\"login\"`\n\tEmail    string `json:\"email\"`\n\tIsAdmin  bool   `json:\"is_admin\"`\n}\n\n// Open returns a strategy for logging in through Gitea\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tif c.BaseURL == \"\" {\n\t\tc.BaseURL = \"https://gitea.com\"\n\t}\n\treturn &giteaConnector{\n\t\tbaseURL:       c.BaseURL,\n\t\tredirectURI:   c.RedirectURI,\n\t\torgs:          c.Orgs,\n\t\tclientID:      c.ClientID,\n\t\tclientSecret:  c.ClientSecret,\n\t\tlogger:        logger.With(slog.Group(\"connector\", \"type\", \"gitea\", \"id\", id)),\n\t\tloadAllGroups: c.LoadAllGroups,\n\t\tuseLoginAsID:  c.UseLoginAsID,\n\t}, nil\n}\n\ntype connectorData struct {\n\tAccessToken  string    `json:\"accessToken\"`\n\tRefreshToken string    `json:\"refreshToken\"`\n\tExpiry       time.Time `json:\"expiry\"`\n}\n\nvar (\n\t_ connector.CallbackConnector = (*giteaConnector)(nil)\n\t_ connector.RefreshConnector  = (*giteaConnector)(nil)\n)\n\ntype giteaConnector struct {\n\tbaseURL      string\n\tredirectURI  string\n\torgs         []Org\n\tclientID     string\n\tclientSecret string\n\tlogger       *slog.Logger\n\thttpClient   *http.Client\n\t// if set to true and no orgs are configured then connector loads all user claims (all orgs and team)\n\tloadAllGroups bool\n\t// if set to true will use the user's handle rather than their numeric id as the ID\n\tuseLoginAsID bool\n}\n\nfunc (c *giteaConnector) oauth2Config(_ connector.Scopes) *oauth2.Config {\n\tgiteaEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + \"/login/oauth/authorize\", TokenURL: c.baseURL + \"/login/oauth/access_token\"}\n\treturn &oauth2.Config{\n\t\tClientID:     c.clientID,\n\t\tClientSecret: c.clientSecret,\n\t\tEndpoint:     giteaEndpoint,\n\t\tRedirectURL:  c.redirectURI,\n\t}\n}\n\nfunc (c *giteaConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", c.redirectURI, callbackURL)\n\t}\n\treturn c.oauth2Config(scopes).AuthCodeURL(state), nil, nil\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n\nfunc (c *giteaConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\toauth2Config := c.oauth2Config(s)\n\n\tctx := r.Context()\n\tif c.httpClient != nil {\n\t\tctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)\n\t}\n\n\ttoken, err := oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"gitea: failed to get token: %v\", err)\n\t}\n\n\tclient := oauth2Config.Client(ctx, token)\n\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"gitea: get user: %v\", err)\n\t}\n\n\tusername := user.Name\n\tif username == \"\" {\n\t\tusername = user.Email\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:            strconv.Itoa(user.ID),\n\t\tUsername:          username,\n\t\tPreferredUsername: user.Username,\n\t\tEmail:             user.Email,\n\t\tEmailVerified:     true,\n\t}\n\tif c.useLoginAsID {\n\t\tidentity.UserID = user.Username\n\t}\n\n\t// Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified.\n\tif c.groupsRequired() {\n\t\tgroups, err := c.getGroups(ctx, client)\n\t\tif err != nil {\n\t\t\treturn identity, err\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\tif s.OfflineAccess {\n\t\tdata := connectorData{\n\t\t\tAccessToken:  token.AccessToken,\n\t\t\tRefreshToken: token.RefreshToken,\n\t\t\tExpiry:       token.Expiry,\n\t\t}\n\t\tconnData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"gitea: marshal connector data: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\n// Refreshing tokens\n// https://github.com/golang/oauth2/issues/84#issuecomment-332860871\ntype tokenNotifyFunc func(*oauth2.Token) error\n\n// notifyRefreshTokenSource is essentially `oauth2.ReuseTokenSource` with `TokenNotifyFunc` added.\ntype notifyRefreshTokenSource struct {\n\tnew oauth2.TokenSource\n\tmu  sync.Mutex // guards t\n\tt   *oauth2.Token\n\tf   tokenNotifyFunc // called when token refreshed so new refresh token can be persisted\n}\n\n// Token returns the current token if it's still valid, else will\n// refresh the current token (using r.Context for HTTP client\n// information) and return the new one.\nfunc (s *notifyRefreshTokenSource) Token() (*oauth2.Token, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif s.t.Valid() {\n\t\treturn s.t, nil\n\t}\n\tt, err := s.new.Token()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.t = t\n\treturn t, s.f(t)\n}\n\nfunc (c *giteaConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {\n\tif len(ident.ConnectorData) == 0 {\n\t\treturn ident, errors.New(\"gitea: no upstream access token found\")\n\t}\n\n\tvar data connectorData\n\tif err := json.Unmarshal(ident.ConnectorData, &data); err != nil {\n\t\treturn ident, fmt.Errorf(\"gitea: unmarshal access token: %v\", err)\n\t}\n\n\ttok := &oauth2.Token{\n\t\tAccessToken:  data.AccessToken,\n\t\tRefreshToken: data.RefreshToken,\n\t\tExpiry:       data.Expiry,\n\t}\n\n\tclient := oauth2.NewClient(ctx, &notifyRefreshTokenSource{\n\t\tnew: c.oauth2Config(s).TokenSource(ctx, tok),\n\t\tt:   tok,\n\t\tf: func(tok *oauth2.Token) error {\n\t\t\tdata := connectorData{\n\t\t\t\tAccessToken:  tok.AccessToken,\n\t\t\t\tRefreshToken: tok.RefreshToken,\n\t\t\t\tExpiry:       tok.Expiry,\n\t\t\t}\n\t\t\tconnData, err := json.Marshal(data)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"gitea: marshal connector data: %v\", err)\n\t\t\t}\n\t\t\tident.ConnectorData = connData\n\t\t\treturn nil\n\t\t},\n\t})\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn ident, fmt.Errorf(\"gitea: get user: %v\", err)\n\t}\n\n\tusername := user.Name\n\tif username == \"\" {\n\t\tusername = user.Email\n\t}\n\tident.Username = username\n\tident.PreferredUsername = user.Username\n\tident.Email = user.Email\n\n\t// Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified.\n\tif c.groupsRequired() {\n\t\tgroups, err := c.getGroups(ctx, client)\n\t\tif err != nil {\n\t\t\treturn ident, err\n\t\t}\n\t\tident.Groups = groups\n\t}\n\n\treturn ident, nil\n}\n\n// getGroups retrieves Gitea orgs and teams a user is in, if any.\nfunc (c *giteaConnector) getGroups(ctx context.Context, client *http.Client) ([]string, error) {\n\tif len(c.orgs) > 0 {\n\t\treturn c.groupsForOrgs(ctx, client)\n\t} else if c.loadAllGroups {\n\t\treturn c.userGroups(ctx, client)\n\t}\n\treturn nil, nil\n}\n\n// formatTeamName returns unique team name.\n// Orgs might have the same team names. To make team name unique it should be prefixed with the org name.\nfunc formatTeamName(org string, team string) string {\n\treturn fmt.Sprintf(\"%s:%s\", org, team)\n}\n\n// groupsForOrgs returns list of groups that user belongs to in approved list\nfunc (c *giteaConnector) groupsForOrgs(ctx context.Context, client *http.Client) ([]string, error) {\n\tgroups, err := c.userGroups(ctx, client)\n\tif err != nil {\n\t\treturn groups, err\n\t}\n\n\tkeys := make(map[string]bool)\n\tfor _, o := range c.orgs {\n\t\tkeys[o.Name] = true\n\t\tif o.Teams != nil {\n\t\t\tfor _, t := range o.Teams {\n\t\t\t\tkeys[formatTeamName(o.Name, t)] = true\n\t\t\t}\n\t\t}\n\t}\n\tatLeastOne := false\n\tfilteredGroups := make([]string, 0)\n\tfor _, g := range groups {\n\t\tif _, value := keys[g]; value {\n\t\t\tfilteredGroups = append(filteredGroups, g)\n\t\t\tatLeastOne = true\n\t\t}\n\t}\n\n\tif !atLeastOne {\n\t\treturn []string{}, fmt.Errorf(\"gitea: User does not belong to any of the approved groups\")\n\t}\n\treturn filteredGroups, nil\n}\n\ntype organization struct {\n\tID   int64  `json:\"id\"`\n\tName string `json:\"username\"`\n}\n\ntype team struct {\n\tID           int64         `json:\"id\"`\n\tName         string        `json:\"name\"`\n\tOrganization *organization `json:\"organization\"`\n}\n\nfunc (c *giteaConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) {\n\tapiURL := c.baseURL + \"/api/v1/user/teams\"\n\tgroups := make([]string, 0)\n\tpage := 1\n\tlimit := 20\n\tfor {\n\t\tvar teams []team\n\t\treq, err := http.NewRequest(\"GET\", fmt.Sprintf(\"%s?page=%d&limit=%d\", apiURL, page, limit), nil)\n\t\tif err != nil {\n\t\t\treturn groups, fmt.Errorf(\"gitea: new req: %v\", err)\n\t\t}\n\n\t\treq = req.WithContext(ctx)\n\t\tresp, err := client.Do(req)\n\t\tif err != nil {\n\t\t\treturn groups, fmt.Errorf(\"gitea: get URL %v\", err)\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\tbody, err := io.ReadAll(resp.Body)\n\t\t\tif err != nil {\n\t\t\t\treturn groups, fmt.Errorf(\"gitea: read body: %v\", err)\n\t\t\t}\n\t\t\treturn groups, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t\t}\n\n\t\tif err := json.NewDecoder(resp.Body).Decode(&teams); err != nil {\n\t\t\treturn groups, fmt.Errorf(\"failed to decode response: %v\", err)\n\t\t}\n\n\t\tif len(teams) == 0 {\n\t\t\tbreak\n\t\t}\n\n\t\tfor _, t := range teams {\n\t\t\tgroups = append(groups, t.Organization.Name)\n\t\t\tgroups = append(groups, formatTeamName(t.Organization.Name, t.Name))\n\t\t}\n\n\t\tpage++\n\t}\n\n\t// remove duplicate slice variables\n\tkeys := make(map[string]struct{})\n\tlist := []string{}\n\tfor _, group := range groups {\n\t\tif _, exists := keys[group]; !exists {\n\t\t\tkeys[group] = struct{}{}\n\t\t\tlist = append(list, group)\n\t\t}\n\t}\n\tgroups = list\n\treturn groups, nil\n}\n\n// user queries the Gitea API for profile information using the provided client. The HTTP\n// client is expected to be constructed by the golang.org/x/oauth2 package, which inserts\n// a bearer token as part of the request.\nfunc (c *giteaConnector) user(ctx context.Context, client *http.Client) (giteaUser, error) {\n\tvar u giteaUser\n\treq, err := http.NewRequest(\"GET\", c.baseURL+\"/api/v1/user\", nil)\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"gitea: new req: %v\", err)\n\t}\n\treq = req.WithContext(ctx)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"gitea: get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn u, fmt.Errorf(\"gitea: read body: %v\", err)\n\t\t}\n\t\treturn u, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(&u); err != nil {\n\t\treturn u, fmt.Errorf(\"failed to decode response: %v\", err)\n\t}\n\treturn u, nil\n}\n\n// groupsRequired returns whether dex needs to request groups from Gitea.\nfunc (c *giteaConnector) groupsRequired() bool {\n\treturn len(c.orgs) > 0 || c.loadAllGroups\n}\n"
  },
  {
    "path": "connector/gitea/gitea_test.go",
    "content": "package gitea\n\nimport (\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// tests that the email is used as their username when they have no username set\nfunc TestUsernameIncludedInFederatedIdentity(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v1/user\": giteaUser{Email: \"some@email.com\", ID: 12345678},\n\t\t\"/login/oauth/access_token\": map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := giteaConnector{baseURL: s.URL, httpClient: newClient()}\n\tidentity, err := c.HandleCallback(connector.Scopes{}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some@email.com\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n\n\tc = giteaConnector{baseURL: s.URL, httpClient: newClient()}\n\tidentity, err = c.HandleCallback(connector.Scopes{}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some@email.com\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n}\n\nfunc newTestServer(responses map[string]interface{}) *httptest.Server {\n\treturn httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresponse := responses[r.RequestURI]\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(response)\n\t}))\n}\n\nfunc newClient() *http.Client {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\treturn &http.Client{Transport: tr}\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Errorf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/github/github.go",
    "content": "// Package github provides authentication strategies using GitHub.\npackage github\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/github\"\n\n\t\"github.com/dexidp/dex/connector\"\n\tgroups_pkg \"github.com/dexidp/dex/pkg/groups\"\n\t\"github.com/dexidp/dex/pkg/httpclient\"\n)\n\nconst (\n\tapiURL = \"https://api.github.com\"\n\t// GitHub requires this scope to access '/user' and '/user/emails' API endpoints.\n\tscopeEmail = \"user:email\"\n\t// GitHub requires this scope to access '/user/teams' and '/orgs' API endpoints\n\t// which are used when a client includes the 'groups' scope.\n\tscopeOrgs = \"read:org\"\n\t// githubAPIVersion pins the GitHub REST API version used in requests.\n\tgithubAPIVersion = \"2022-11-28\"\n)\n\n// Pagination URL patterns\n// https://developer.github.com/v3/#pagination\nvar (\n\treNext = regexp.MustCompile(\"<([^>]+)>; rel=\\\"next\\\"\")\n\treLast = regexp.MustCompile(\"<([^>]+)>; rel=\\\"last\\\"\")\n)\n\n// Config holds configuration options for github logins.\ntype Config struct {\n\tClientID             string `json:\"clientID\"`\n\tClientSecret         string `json:\"clientSecret\"`\n\tRedirectURI          string `json:\"redirectURI\"`\n\tOrg                  string `json:\"org\"`\n\tOrgs                 []Org  `json:\"orgs\"`\n\tHostName             string `json:\"hostName\"`\n\tRootCA               string `json:\"rootCA\"`\n\tTeamNameField        string `json:\"teamNameField\"`\n\tLoadAllGroups        bool   `json:\"loadAllGroups\"`\n\tUseLoginAsID         bool   `json:\"useLoginAsID\"`\n\tPreferredEmailDomain string `json:\"preferredEmailDomain\"`\n}\n\n// Org holds org-team filters, in which teams are optional.\ntype Org struct {\n\t// Organization name in github (not slug, full name). Only users in this github\n\t// organization can authenticate.\n\tName string `json:\"name\"`\n\n\t// Names of teams in a github organization. A user will be able to\n\t// authenticate if they are members of at least one of these teams. Users\n\t// in the organization can authenticate if this field is omitted from the\n\t// config file.\n\tTeams []string `json:\"teams,omitempty\"`\n}\n\n// Open returns a strategy for logging in through GitHub.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tif c.Org != \"\" {\n\t\t// Return error if both 'org' and 'orgs' fields are used.\n\t\tif len(c.Orgs) > 0 {\n\t\t\treturn nil, errors.New(\"github: cannot use both 'org' and 'orgs' fields simultaneously\")\n\t\t}\n\t\tlogger.Warn(\"github: legacy field 'org' being used. Switch to the newer 'orgs' field structure\")\n\t}\n\n\tg := githubConnector{\n\t\tredirectURI:          c.RedirectURI,\n\t\torg:                  c.Org,\n\t\torgs:                 c.Orgs,\n\t\tclientID:             c.ClientID,\n\t\tclientSecret:         c.ClientSecret,\n\t\tapiURL:               apiURL,\n\t\tlogger:               logger.With(slog.Group(\"connector\", \"type\", \"github\", \"id\", id)),\n\t\tuseLoginAsID:         c.UseLoginAsID,\n\t\tpreferredEmailDomain: c.PreferredEmailDomain,\n\t}\n\n\tif c.HostName != \"\" {\n\t\t// ensure this is a hostname and not a URL or path.\n\t\tif strings.Contains(c.HostName, \"/\") {\n\t\t\treturn nil, errors.New(\"invalid hostname: hostname cannot contain `/`\")\n\t\t}\n\n\t\tg.hostName = c.HostName\n\t\tg.apiURL = \"https://\" + c.HostName + \"/api/v3\"\n\t}\n\n\tif c.RootCA != \"\" {\n\t\tif c.HostName == \"\" {\n\t\t\treturn nil, errors.New(\"invalid connector config: Host name field required for a root certificate file\")\n\t\t}\n\t\tg.rootCA = c.RootCA\n\n\t\tvar err error\n\t\tif g.httpClient, err = httpclient.NewHTTPClient([]string{g.rootCA}, false); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to create HTTP client: %v\", err)\n\t\t}\n\t}\n\tg.loadAllGroups = c.LoadAllGroups\n\n\tswitch c.TeamNameField {\n\tcase \"name\", \"slug\", \"both\", \"\":\n\t\tg.teamNameField = c.TeamNameField\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid connector config: unsupported team name field value `%s`\", c.TeamNameField)\n\t}\n\n\tif c.PreferredEmailDomain != \"\" {\n\t\tif strings.HasSuffix(c.PreferredEmailDomain, \"*\") {\n\t\t\treturn nil, errors.New(\"invalid PreferredEmailDomain: glob pattern cannot end with \\\"*\\\"\")\n\t\t}\n\t}\n\n\treturn &g, nil\n}\n\ntype connectorData struct {\n\t// GitHub's OAuth2 tokens never expire. We don't need a refresh token.\n\tAccessToken string `json:\"accessToken\"`\n}\n\nvar (\n\t_ connector.CallbackConnector = (*githubConnector)(nil)\n\t_ connector.RefreshConnector  = (*githubConnector)(nil)\n)\n\ntype githubConnector struct {\n\tredirectURI  string\n\torg          string\n\torgs         []Org\n\tclientID     string\n\tclientSecret string\n\tlogger       *slog.Logger\n\t// apiURL defaults to \"https://api.github.com\"\n\tapiURL string\n\t// hostName of the GitHub enterprise account.\n\thostName string\n\t// Used to support untrusted/self-signed CA certs.\n\trootCA string\n\t// HTTP Client that trusts the custom declared rootCA cert.\n\thttpClient *http.Client\n\t// optional choice between 'name' (default) or 'slug'\n\tteamNameField string\n\t// if set to true and no orgs are configured then connector loads all user claims (all orgs and team)\n\tloadAllGroups bool\n\t// if set to true will use the user's handle rather than their numeric id as the ID\n\tuseLoginAsID bool\n\t// the domain to be preferred among the user's emails. e.g. \"github.com\"\n\tpreferredEmailDomain string\n}\n\n// groupsRequired returns whether dex requires GitHub's 'read:org' scope. Dex\n// needs 'read:org' if 'orgs' or 'org' fields are populated in a config file.\n// Clients can require 'groups' scope without setting 'orgs'/'org'.\nfunc (c *githubConnector) groupsRequired(groupScope bool) bool {\n\treturn len(c.orgs) > 0 || c.org != \"\" || groupScope\n}\n\nfunc (c *githubConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {\n\t// 'read:org' scope is required by the GitHub API, and thus for dex to ensure\n\t// a user is a member of orgs and teams provided in configs.\n\tgithubScopes := []string{scopeEmail}\n\tif c.groupsRequired(scopes.Groups) {\n\t\tgithubScopes = append(githubScopes, scopeOrgs)\n\t}\n\n\tendpoint := github.Endpoint\n\t// case when it is a GitHub Enterprise account.\n\tif c.hostName != \"\" {\n\t\tendpoint = oauth2.Endpoint{\n\t\t\tAuthURL:  \"https://\" + c.hostName + \"/login/oauth/authorize\",\n\t\t\tTokenURL: \"https://\" + c.hostName + \"/login/oauth/access_token\",\n\t\t}\n\t}\n\n\treturn &oauth2.Config{\n\t\tClientID:     c.clientID,\n\t\tClientSecret: c.clientSecret,\n\t\tEndpoint:     endpoint,\n\t\tScopes:       githubScopes,\n\t\tRedirectURL:  c.redirectURI,\n\t}\n}\n\nfunc (c *githubConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", callbackURL, c.redirectURI)\n\t}\n\n\treturn c.oauth2Config(scopes).AuthCodeURL(state), nil, nil\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n\nfunc (c *githubConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\toauth2Config := c.oauth2Config(s)\n\n\tctx := r.Context()\n\t// GitHub Enterprise account\n\tif c.httpClient != nil {\n\t\tctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)\n\t}\n\n\ttoken, err := oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"github: failed to get token: %v\", err)\n\t}\n\n\tclient := oauth2Config.Client(ctx, token)\n\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"github: get user: %v\", err)\n\t}\n\n\tusername := user.Name\n\tif username == \"\" {\n\t\tusername = user.Login\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:            strconv.Itoa(user.ID),\n\t\tUsername:          username,\n\t\tPreferredUsername: user.Login,\n\t\tEmail:             user.Email,\n\t\tEmailVerified:     true,\n\t}\n\tif c.useLoginAsID {\n\t\tidentity.UserID = user.Login\n\t}\n\n\t// Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified.\n\tif c.groupsRequired(s.Groups) {\n\t\tgroups, err := c.getGroups(ctx, client, s.Groups, user.Login)\n\t\tif err != nil {\n\t\t\treturn identity, err\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\tif s.OfflineAccess {\n\t\tdata := connectorData{AccessToken: token.AccessToken}\n\t\tconnData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"marshal connector data: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\nfunc (c *githubConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\tif len(identity.ConnectorData) == 0 {\n\t\treturn identity, errors.New(\"no upstream access token found\")\n\t}\n\n\tvar data connectorData\n\tif err := json.Unmarshal(identity.ConnectorData, &data); err != nil {\n\t\treturn identity, fmt.Errorf(\"github: unmarshal access token: %v\", err)\n\t}\n\n\tclient := c.oauth2Config(s).Client(ctx, &oauth2.Token{AccessToken: data.AccessToken})\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"github: get user: %v\", err)\n\t}\n\n\tusername := user.Name\n\tif username == \"\" {\n\t\tusername = user.Login\n\t}\n\tidentity.Username = username\n\tidentity.PreferredUsername = user.Login\n\tidentity.Email = user.Email\n\n\t// Only set identity.Groups if 'orgs', 'org', or 'groups' scope are specified.\n\tif c.groupsRequired(s.Groups) {\n\t\tgroups, err := c.getGroups(ctx, client, s.Groups, user.Login)\n\t\tif err != nil {\n\t\t\treturn identity, err\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\treturn identity, nil\n}\n\n// getGroups retrieves GitHub orgs and teams a user is in, if any.\nfunc (c *githubConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) {\n\tswitch {\n\tcase len(c.orgs) > 0:\n\t\treturn c.groupsForOrgs(ctx, client, userLogin)\n\tcase c.org != \"\":\n\t\treturn c.teamsForOrg(ctx, client, c.org)\n\tcase groupScope && c.loadAllGroups:\n\t\treturn c.userGroups(ctx, client)\n\t}\n\treturn nil, nil\n}\n\n// formatTeamName returns unique team name.\n// Orgs might have the same team names. To make team name unique it should be prefixed with the org name.\nfunc formatTeamName(org string, team string) string {\n\treturn fmt.Sprintf(\"%s:%s\", org, team)\n}\n\n// groupsForOrgs enforces org and team constraints on user authorization\n// Cases in which user is authorized:\n//\n//\tN orgs, no teams: user is member of at least 1 org\n//\tN orgs, M teams per org: user is member of any team from at least 1 org\n//\tN-1 orgs, M teams per org, 1 org with no teams: user is member of any team\n//\n// from at least 1 org, or member of org with no teams\nfunc (c *githubConnector) groupsForOrgs(ctx context.Context, client *http.Client, userName string) ([]string, error) {\n\tgroups := make([]string, 0)\n\tvar inOrgNoTeams bool\n\tfor _, org := range c.orgs {\n\t\tinOrg, err := c.userInOrg(ctx, client, userName, org.Name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif !inOrg {\n\t\t\tcontinue\n\t\t}\n\n\t\tteams, err := c.teamsForOrg(ctx, client, org.Name)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// User is in at least one org. User is authorized if no teams are specified\n\t\t// in config; include all teams in claim. Otherwise filter out teams not in\n\t\t// 'teams' list in config.\n\t\tif len(org.Teams) == 0 {\n\t\t\tinOrgNoTeams = true\n\t\t} else if teams = groups_pkg.Filter(teams, org.Teams); len(teams) == 0 {\n\t\t\tc.logger.Info(\"user in org but no teams\", \"user\", userName, \"org\", org.Name)\n\t\t}\n\n\t\tfor _, teamName := range teams {\n\t\t\tgroups = append(groups, formatTeamName(org.Name, teamName))\n\t\t}\n\t}\n\tif inOrgNoTeams || len(groups) > 0 {\n\t\treturn groups, nil\n\t}\n\treturn groups, fmt.Errorf(\"github: user %q not in required orgs or teams\", userName)\n}\n\nfunc (c *githubConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) {\n\torgs, err := c.userOrgs(ctx, client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\torgTeams, err := c.userOrgTeams(ctx, client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroups := make([]string, 0)\n\tfor _, o := range orgs {\n\t\tgroups = append(groups, o)\n\t\tif teams, ok := orgTeams[o]; ok {\n\t\t\tfor _, t := range teams {\n\t\t\t\tgroups = append(groups, formatTeamName(o, t))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn groups, nil\n}\n\n// userOrgs retrieves list of current user orgs\nfunc (c *githubConnector) userOrgs(ctx context.Context, client *http.Client) ([]string, error) {\n\tgroups := make([]string, 0)\n\tapiURL := c.apiURL + \"/user/orgs\"\n\tfor {\n\t\t// https://developer.github.com/v3/orgs/#list-your-organizations\n\t\tvar (\n\t\t\torgs []org\n\t\t\terr  error\n\t\t)\n\t\tif apiURL, err = get(ctx, client, apiURL, &orgs); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"github: get orgs: %v\", err)\n\t\t}\n\n\t\tfor _, o := range orgs {\n\t\t\tgroups = append(groups, o.Login)\n\t\t}\n\n\t\tif apiURL == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn groups, nil\n}\n\n// userOrgTeams retrieves teams which current user belongs to.\n// Method returns a map where key is an org name and value list of teams under the org.\nfunc (c *githubConnector) userOrgTeams(ctx context.Context, client *http.Client) (map[string][]string, error) {\n\tgroups := make(map[string][]string)\n\tapiURL := c.apiURL + \"/user/teams\"\n\tfor {\n\t\t// https://developer.github.com/v3/orgs/teams/#list-user-teams\n\t\tvar (\n\t\t\tteams []team\n\t\t\terr   error\n\t\t)\n\t\tif apiURL, err = get(ctx, client, apiURL, &teams); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"github: get teams: %v\", err)\n\t\t}\n\n\t\tfor _, t := range teams {\n\t\t\tgroups[t.Org.Login] = append(groups[t.Org.Login], c.teamGroupClaims(t)...)\n\t\t}\n\n\t\tif apiURL == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn groups, nil\n}\n\n// get creates a \"GET `apiURL`\" request with context, sends the request using\n// the client, and decodes the resulting response body into v. A pagination URL\n// is returned if one exists. Any errors encountered when building requests,\n// sending requests, and reading and decoding response data are returned.\nfunc get(ctx context.Context, client *http.Client, apiURL string, v interface{}) (string, error) {\n\treq, err := http.NewRequest(\"GET\", apiURL, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"github: new req: %v\", err)\n\t}\n\treq = req.WithContext(ctx)\n\treq.Header.Set(\"X-GitHub-Api-Version\", githubAPIVersion)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"github: get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn \"\", fmt.Errorf(\"github: read body: %v\", err)\n\t\t}\n\t\treturn \"\", fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(v); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to decode response: %v\", err)\n\t}\n\n\treturn getPagination(apiURL, resp), nil\n}\n\n// getPagination checks the \"Link\" header field for \"next\" or \"last\" pagination URLs,\n// and returns \"next\" page URL or empty string to indicate that there are no more pages.\n// Non empty next pages' URL is returned if both \"last\" and \"next\" URLs are found and next page\n// URL is not equal to last.\n//\n// https://developer.github.com/v3/#pagination\nfunc getPagination(apiURL string, resp *http.Response) string {\n\tif resp == nil {\n\t\treturn \"\"\n\t}\n\n\tlinks := resp.Header.Get(\"Link\")\n\tif len(reLast.FindStringSubmatch(links)) > 1 {\n\t\tlastPageURL := reLast.FindStringSubmatch(links)[1]\n\t\tif apiURL == lastPageURL {\n\t\t\treturn \"\"\n\t\t}\n\t} else {\n\t\treturn \"\"\n\t}\n\n\tif len(reNext.FindStringSubmatch(links)) > 1 {\n\t\treturn reNext.FindStringSubmatch(links)[1]\n\t}\n\n\treturn \"\"\n}\n\n// user holds GitHub user information (relevant to dex) as defined by\n// https://developer.github.com/v3/users/#response-with-public-profile-information\ntype user struct {\n\tName  string `json:\"name\"`\n\tLogin string `json:\"login\"`\n\tID    int    `json:\"id\"`\n\tEmail string `json:\"email\"`\n}\n\n// user queries the GitHub API for profile information using the provided client.\n//\n// The HTTP client is expected to be constructed by the golang.org/x/oauth2 package,\n// which inserts a bearer token as part of the request.\nfunc (c *githubConnector) user(ctx context.Context, client *http.Client) (user, error) {\n\t// https://developer.github.com/v3/users/#get-the-authenticated-user\n\tvar u user\n\tif _, err := get(ctx, client, c.apiURL+\"/user\", &u); err != nil {\n\t\treturn u, err\n\t}\n\n\t// Only public user emails are returned by 'GET /user'.\n\t// If a user has no public email, we must retrieve private emails explicitly.\n\t// If preferredEmailDomain is set, we always need to retrieve all emails.\n\tif u.Email == \"\" || c.preferredEmailDomain != \"\" {\n\t\tvar err error\n\t\tif u.Email, err = c.userEmail(ctx, client); err != nil {\n\t\t\treturn u, err\n\t\t}\n\t}\n\treturn u, nil\n}\n\n// userEmail holds GitHub user email information as defined by\n// https://developer.github.com/v3/users/emails/#response\ntype userEmail struct {\n\tEmail      string `json:\"email\"`\n\tVerified   bool   `json:\"verified\"`\n\tPrimary    bool   `json:\"primary\"`\n\tVisibility string `json:\"visibility\"`\n}\n\n// userEmail queries the GitHub API for a users' email information using the\n// provided client. Only returns the users' verified, primary email (private or\n// public).\n//\n// The HTTP client is expected to be constructed by the golang.org/x/oauth2 package,\n// which inserts a bearer token as part of the request.\nfunc (c *githubConnector) userEmail(ctx context.Context, client *http.Client) (string, error) {\n\tvar (\n\t\tprimaryEmail    userEmail\n\t\tpreferredEmails []userEmail\n\t)\n\n\tapiURL := c.apiURL + \"/user/emails\"\n\n\tfor {\n\t\t// https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user\n\t\tvar (\n\t\t\temails []userEmail\n\t\t\terr    error\n\t\t)\n\t\tif apiURL, err = get(ctx, client, apiURL, &emails); err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfor _, email := range emails {\n\t\t\t/*\n\t\t\t\tif GitHub Enterprise, set email.Verified to true\n\t\t\t\tThis change being made because GitHub Enterprise does not\n\t\t\t\tsupport email verification. CircleCI indicated that GitHub\n\t\t\t\tadvised them not to check for verified emails\n\t\t\t\t(https://circleci.com/enterprise/changelog/#1-47-1).\n\t\t\t\tIn addition, GitHub Enterprise support replied to a support\n\t\t\t\tticket with \"There is no way to verify an email address in\n\t\t\t\tGitHub Enterprise.\"\n\t\t\t*/\n\t\t\tif c.hostName != \"\" {\n\t\t\t\temail.Verified = true\n\t\t\t}\n\n\t\t\tif email.Verified && email.Primary {\n\t\t\t\tprimaryEmail = email\n\t\t\t}\n\n\t\t\tif c.preferredEmailDomain != \"\" {\n\t\t\t\t_, domainPart, ok := strings.Cut(email.Email, \"@\")\n\t\t\t\tif !ok {\n\t\t\t\t\treturn \"\", errors.New(\"github: invalid format email is detected\")\n\t\t\t\t}\n\t\t\t\tif email.Verified && c.isPreferredEmailDomain(domainPart) {\n\t\t\t\t\tpreferredEmails = append(preferredEmails, email)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif apiURL == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif len(preferredEmails) > 0 {\n\t\treturn preferredEmails[0].Email, nil\n\t}\n\n\tif primaryEmail.Email != \"\" {\n\t\treturn primaryEmail.Email, nil\n\t}\n\n\treturn \"\", errors.New(\"github: user has no verified, primary email or preferred-domain email\")\n}\n\n// isPreferredEmailDomain checks the domain is matching with preferredEmailDomain.\nfunc (c *githubConnector) isPreferredEmailDomain(domain string) bool {\n\tif domain == c.preferredEmailDomain {\n\t\treturn true\n\t}\n\n\tpreferredDomainParts := strings.Split(c.preferredEmailDomain, \".\")\n\tdomainParts := strings.Split(domain, \".\")\n\n\tif len(preferredDomainParts) != len(domainParts) {\n\t\treturn false\n\t}\n\n\tfor i, v := range preferredDomainParts {\n\t\tif domainParts[i] != v && v != \"*\" {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// userInOrg queries the GitHub API for a users' org membership.\n//\n// The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package,\n// which inserts a bearer token as part of the request.\nfunc (c *githubConnector) userInOrg(ctx context.Context, client *http.Client, userName, orgName string) (bool, error) {\n\t// requester == user, so GET-ing this endpoint should return 404/302 if user\n\t// is not a member\n\t//\n\t// https://developer.github.com/v3/orgs/members/#check-membership\n\tapiURL := fmt.Sprintf(\"%s/orgs/%s/members/%s\", c.apiURL, orgName, userName)\n\n\treq, err := http.NewRequest(\"GET\", apiURL, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"github: new req: %v\", err)\n\t}\n\treq = req.WithContext(ctx)\n\treq.Header.Set(\"X-GitHub-Api-Version\", githubAPIVersion)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"github: get teams: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusNoContent:\n\tcase http.StatusFound, http.StatusNotFound:\n\t\tc.logger.Info(\"user not in org or application not authorized to read org data\", \"user\", userName, \"org\", orgName)\n\tdefault:\n\t\terr = fmt.Errorf(\"github: unexpected return status: %q\", resp.Status)\n\t}\n\n\t// 204 if user is a member\n\treturn resp.StatusCode == http.StatusNoContent, err\n}\n\n// teams holds GitHub a users' team information as defined by\n// https://developer.github.com/v3/orgs/teams/#response-12\ntype team struct {\n\tName string `json:\"name\"`\n\tOrg  org    `json:\"organization\"`\n\tSlug string `json:\"slug\"`\n}\n\ntype org struct {\n\tLogin string `json:\"login\"`\n}\n\n// teamsForOrg queries the GitHub API for team membership within a specific organization.\n//\n// The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package,\n// which inserts a bearer token as part of the request.\nfunc (c *githubConnector) teamsForOrg(ctx context.Context, client *http.Client, orgName string) ([]string, error) {\n\tapiURL, groups := c.apiURL+\"/user/teams\", []string{}\n\tfor {\n\t\t// https://developer.github.com/v3/orgs/teams/#list-user-teams\n\t\tvar (\n\t\t\tteams []team\n\t\t\terr   error\n\t\t)\n\t\tif apiURL, err = get(ctx, client, apiURL, &teams); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"github: get teams: %v\", err)\n\t\t}\n\n\t\tfor _, t := range teams {\n\t\t\tif t.Org.Login == orgName {\n\t\t\t\tgroups = append(groups, c.teamGroupClaims(t)...)\n\t\t\t}\n\t\t}\n\n\t\tif apiURL == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn groups, nil\n}\n\n// teamGroupClaims returns team slug if 'teamNameField' option is set to\n// 'slug', returns the slug *and* name if set to 'both', otherwise returns team\n// name.\nfunc (c *githubConnector) teamGroupClaims(t team) []string {\n\tswitch c.teamNameField {\n\tcase \"both\":\n\t\treturn []string{t.Name, t.Slug}\n\tcase \"slug\":\n\t\treturn []string{t.Slug}\n\tdefault:\n\t\treturn []string{t.Name}\n\t}\n}\n"
  },
  {
    "path": "connector/github/github_test.go",
    "content": "package github\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\ntype testResponse struct {\n\tdata     interface{}\n\tnextLink string\n\tlastLink string\n}\n\nfunc TestUserGroups(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user/orgs\": {\n\t\t\tdata:     []org{{Login: \"org-1\"}, {Login: \"org-2\"}},\n\t\t\tnextLink: \"/user/orgs?since=2\",\n\t\t\tlastLink: \"/user/orgs?since=2\",\n\t\t},\n\t\t\"/user/orgs?since=2\": {data: []org{{Login: \"org-3\"}}},\n\t\t\"/user/teams\": {\n\t\t\tdata: []team{\n\t\t\t\t{Name: \"team-1\", Org: org{Login: \"org-1\"}},\n\t\t\t\t{Name: \"team-2\", Org: org{Login: \"org-1\"}},\n\t\t\t},\n\t\t\tnextLink: \"/user/teams?since=2\",\n\t\t\tlastLink: \"/user/teams?since=2\",\n\t\t},\n\t\t\"/user/teams?since=2\": {\n\t\t\tdata: []team{\n\t\t\t\t{Name: \"team-3\", Org: org{Login: \"org-1\"}},\n\t\t\t\t{Name: \"team-4\", Org: org{Login: \"org-2\"}},\n\t\t\t},\n\t\t\tnextLink: \"/user/teams?since=2\",\n\t\t\tlastLink: \"/user/teams?since=2\",\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := githubConnector{apiURL: s.URL}\n\tgroups, err := c.userGroups(context.Background(), newClient())\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\n\t\t\"org-1\",\n\t\t\"org-1:team-1\",\n\t\t\"org-1:team-2\",\n\t\t\"org-1:team-3\",\n\t\t\"org-2\",\n\t\t\"org-2:team-4\",\n\t\t\"org-3\",\n\t})\n}\n\nfunc TestUserGroupsWithoutOrgs(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user/orgs\":  {data: []org{}},\n\t\t\"/user/teams\": {data: []team{}},\n\t})\n\tdefer s.Close()\n\n\tc := githubConnector{apiURL: s.URL}\n\tgroups, err := c.userGroups(context.Background(), newClient())\n\n\texpectNil(t, err)\n\texpectEquals(t, len(groups), 0)\n}\n\nfunc TestUserGroupsWithTeamNameFieldConfig(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user/orgs\": {\n\t\t\tdata: []org{{Login: \"org-1\"}},\n\t\t},\n\t\t\"/user/teams\": {\n\t\t\tdata: []team{\n\t\t\t\t{Name: \"Team 1\", Slug: \"team-1\", Org: org{Login: \"org-1\"}},\n\t\t\t},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := githubConnector{apiURL: s.URL, teamNameField: \"slug\"}\n\tgroups, err := c.userGroups(context.Background(), newClient())\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\n\t\t\"org-1\",\n\t\t\"org-1:team-1\",\n\t})\n}\n\nfunc TestUserGroupsWithTeamNameAndSlugFieldConfig(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user/orgs\": {\n\t\t\tdata: []org{{Login: \"org-1\"}},\n\t\t},\n\t\t\"/user/teams\": {\n\t\t\tdata: []team{\n\t\t\t\t{Name: \"Team 1\", Slug: \"team-1\", Org: org{Login: \"org-1\"}},\n\t\t\t},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := githubConnector{apiURL: s.URL, teamNameField: \"both\"}\n\tgroups, err := c.userGroups(context.Background(), newClient())\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\n\t\t\"org-1\",\n\t\t\"org-1:Team 1\",\n\t\t\"org-1:team-1\",\n\t})\n}\n\n// tests that the users login is used as their username when they have no username set\nfunc TestUsernameIncludedInFederatedIdentity(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user\": {data: user{Login: \"some-login\", ID: 12345678}},\n\t\t\"/user/emails\": {data: []userEmail{{\n\t\t\tEmail:    \"some@email.com\",\n\t\t\tVerified: true,\n\t\t\tPrimary:  true,\n\t\t}}},\n\t\t\"/login/oauth/access_token\": {data: map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t}},\n\t\t\"/user/orgs\": {\n\t\t\tdata: []org{{Login: \"org-1\"}},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient()}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some-login\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n\texpectEquals(t, 0, len(identity.Groups))\n\n\tc = githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient(), loadAllGroups: true}\n\tidentity, err = c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some-login\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n\texpectEquals(t, identity.Groups, []string{\"org-1\"})\n}\n\nfunc TestLoginUsedAsIDWhenConfigured(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user\": {data: user{Login: \"some-login\", ID: 12345678, Name: \"Joe Bloggs\"}},\n\t\t\"/user/emails\": {data: []userEmail{{\n\t\t\tEmail:    \"some@email.com\",\n\t\t\tVerified: true,\n\t\t\tPrimary:  true,\n\t\t}}},\n\t\t\"/login/oauth/access_token\": {data: map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t}},\n\t\t\"/user/orgs\": {\n\t\t\tdata: []org{{Login: \"org-1\"}},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: newClient(), useLoginAsID: true}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.UserID, \"some-login\")\n\texpectEquals(t, identity.Username, \"Joe Bloggs\")\n}\n\nfunc TestPreferredEmailDomainConfigured(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user\": {data: user{Login: \"some-login\", ID: 12345678, Name: \"Joe Bloggs\"}},\n\t\t\"/user/emails\": {\n\t\t\tdata: []userEmail{\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"another@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@preferred-domain.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"another@preferred-domain.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\tclient := newClient()\n\tc := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: \"preferred-domain.com\"}\n\n\tu, err := c.user(ctx, client)\n\texpectNil(t, err)\n\texpectEquals(t, u.Email, \"some@preferred-domain.com\")\n}\n\nfunc TestPreferredEmailDomainConfiguredWithGlob(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user\": {data: user{Login: \"some-login\", ID: 12345678, Name: \"Joe Bloggs\"}},\n\t\t\"/user/emails\": {\n\t\t\tdata: []userEmail{\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"another@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@another.preferred-domain.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@sub-domain.preferred-domain.co\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\tclient := newClient()\n\tc := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: \"*.preferred-domain.co\"}\n\n\tu, err := c.user(ctx, client)\n\texpectNil(t, err)\n\texpectEquals(t, u.Email, \"some@sub-domain.preferred-domain.co\")\n}\n\nfunc TestPreferredEmailDomainConfigured_UserHasNoPreferredDomainEmail(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user\": {data: user{Login: \"some-login\", ID: 12345678, Name: \"Joe Bloggs\"}},\n\t\t\"/user/emails\": {\n\t\t\tdata: []userEmail{\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"another@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\tclient := newClient()\n\tc := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: \"preferred-domain.com\"}\n\n\tu, err := c.user(ctx, client)\n\texpectNil(t, err)\n\texpectEquals(t, u.Email, \"some@email.com\")\n}\n\nfunc TestPreferredEmailDomainNotConfigured(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user\": {data: user{Login: \"some-login\", ID: 12345678, Name: \"Joe Bloggs\"}},\n\t\t\"/user/emails\": {\n\t\t\tdata: []userEmail{\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"another@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@preferred-domain.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\tclient := newClient()\n\tc := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client}\n\n\tu, err := c.user(ctx, client)\n\texpectNil(t, err)\n\texpectEquals(t, u.Email, \"some@email.com\")\n}\n\nfunc TestPreferredEmailDomainConfigured_Error_BothPrimaryAndPreferredDomainEmailNotFound(t *testing.T) {\n\tctx := context.Background()\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/user\": {data: user{Login: \"some-login\", ID: 12345678, Name: \"Joe Bloggs\"}},\n\t\t\"/user/emails\": {\n\t\t\tdata: []userEmail{\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"another@email.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tEmail:    \"some@preferred-domain.com\",\n\t\t\t\t\tVerified: true,\n\t\t\t\t\tPrimary:  false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\tclient := newClient()\n\tc := githubConnector{apiURL: s.URL, hostName: hostURL.Host, httpClient: client, preferredEmailDomain: \"foo.bar\"}\n\n\t_, err = c.user(ctx, client)\n\texpectNotNil(t, err, \"Email not found error\")\n\texpectEquals(t, err.Error(), \"github: user has no verified, primary email or preferred-domain email\")\n}\n\nfunc Test_isPreferredEmailDomain(t *testing.T) {\n\tclient := newClient()\n\ttests := []struct {\n\t\tpreferredEmailDomain string\n\t\temail                string\n\t\texpected             bool\n\t}{\n\t\t{\n\t\t\tpreferredEmailDomain: \"example.com\",\n\t\t\temail:                \"test@example.com\",\n\t\t\texpected:             true,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"example.com\",\n\t\t\temail:                \"test@another.com\",\n\t\t\texpected:             false,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.example.com\",\n\t\t\temail:                \"test@my.example.com\",\n\t\t\texpected:             true,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.example.com\",\n\t\t\temail:                \"test@my.another.com\",\n\t\t\texpected:             false,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.example.com\",\n\t\t\temail:                \"test@my.domain.example.com\",\n\t\t\texpected:             false,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.example.com\",\n\t\t\temail:                \"test@sub.domain.com\",\n\t\t\texpected:             false,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.*.example.com\",\n\t\t\temail:                \"test@sub.my.example.com\",\n\t\t\texpected:             true,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.*.example.com\",\n\t\t\temail:                \"test@a.my.google.com\",\n\t\t\texpected:             false,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.preferredEmailDomain, func(t *testing.T) {\n\t\t\tc := githubConnector{apiURL: \"apiURL\", hostName: \"github.com\", httpClient: client, preferredEmailDomain: test.preferredEmailDomain}\n\t\t\t_, domainPart, _ := strings.Cut(test.email, \"@\")\n\t\t\tres := c.isPreferredEmailDomain(domainPart)\n\n\t\t\texpectEquals(t, res, test.expected)\n\t\t})\n\t}\n}\n\nfunc Test_Open_PreferredDomainConfig(t *testing.T) {\n\tlog := slog.New(slog.DiscardHandler)\n\ttests := []struct {\n\t\tpreferredEmailDomain string\n\t\temail                string\n\t\texpected             error\n\t}{\n\t\t{\n\t\t\tpreferredEmailDomain: \"example.com\",\n\t\t\texpected:             nil,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.example.com\",\n\t\t\texpected:             nil,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"*.*.example.com\",\n\t\t\texpected:             nil,\n\t\t},\n\t\t{\n\t\t\tpreferredEmailDomain: \"example.*\",\n\t\t\texpected:             errors.New(\"invalid PreferredEmailDomain: glob pattern cannot end with \\\"*\\\"\"),\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.preferredEmailDomain, func(t *testing.T) {\n\t\t\tc := Config{\n\t\t\t\tPreferredEmailDomain: test.preferredEmailDomain,\n\t\t\t}\n\t\t\t_, err := c.Open(\"id\", log)\n\n\t\t\texpectEquals(t, err, test.expected)\n\t\t})\n\t}\n}\n\nfunc TestGetSendsAPIVersionHeader(t *testing.T) {\n\tvar gotHeader string\n\ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tgotHeader = r.Header.Get(\"X-GitHub-Api-Version\")\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode([]org{})\n\t}))\n\tdefer s.Close()\n\n\tvar result []org\n\t_, err := get(context.Background(), newClient(), s.URL+\"/user/orgs\", &result)\n\texpectNil(t, err)\n\texpectEquals(t, gotHeader, githubAPIVersion)\n}\n\nfunc newTestServer(responses map[string]testResponse) *httptest.Server {\n\tvar s *httptest.Server\n\ts = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresponse := responses[r.RequestURI]\n\t\tlinkParts := make([]string, 0)\n\t\tif response.nextLink != \"\" {\n\t\t\tlinkParts = append(linkParts, fmt.Sprintf(\"<%s%s>; rel=\\\"next\\\"\", s.URL, response.nextLink))\n\t\t}\n\t\tif response.lastLink != \"\" {\n\t\t\tlinkParts = append(linkParts, fmt.Sprintf(\"<%s%s>; rel=\\\"last\\\"\", s.URL, response.lastLink))\n\t\t}\n\t\tif len(linkParts) > 0 {\n\t\t\tw.Header().Add(\"Link\", strings.Join(linkParts, \", \"))\n\t\t}\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(response.data)\n\t}))\n\treturn s\n}\n\nfunc newClient() *http.Client {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\treturn &http.Client{Transport: tr}\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Errorf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectNotNil(t *testing.T, a interface{}, msg string) {\n\tif a == nil {\n\t\tt.Errorf(\"Expected %+v to not to be nil\", msg)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/gitlab/gitlab.go",
    "content": "// Package gitlab provides authentication strategies using GitLab.\npackage gitlab\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/groups\"\n\t\"github.com/dexidp/dex/pkg/httpclient\"\n)\n\nconst (\n\t// read operations of the /api/v4/user endpoint\n\tscopeUser = \"read_user\"\n\t// used to retrieve groups from /oauth/userinfo\n\t// https://docs.gitlab.com/ee/integration/openid_connect_provider.html\n\tscopeOpenID = \"openid\"\n)\n\n// Config holds configuration options for gitlab logins.\ntype Config struct {\n\tBaseURL             string   `json:\"baseURL\"`\n\tClientID            string   `json:\"clientID\"`\n\tClientSecret        string   `json:\"clientSecret\"`\n\tRedirectURI         string   `json:\"redirectURI\"`\n\tGroups              []string `json:\"groups\"`\n\tUseLoginAsID        bool     `json:\"useLoginAsID\"`\n\tGetGroupsPermission bool     `json:\"getGroupsPermission\"`\n\tRootCAData          []byte   `json:\"rootCAData,omitempty\"`\n}\n\ntype gitlabUser struct {\n\tID       int\n\tName     string\n\tUsername string\n\tState    string\n\tEmail    string\n\tIsAdmin  bool\n}\n\n// Open returns a strategy for logging in through GitLab.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tif c.BaseURL == \"\" {\n\t\tc.BaseURL = \"https://gitlab.com\"\n\t}\n\tvar httpClient *http.Client\n\tif len(c.RootCAData) > 0 {\n\t\tvar err error\n\t\thttpClient, err = httpclient.NewHTTPClient([]string{string(c.RootCAData)}, false)\n\t\tif err != nil {\n\t\t\t// Keep backward-compatible error semantics for invalid PEM input.\n\t\t\tif strings.Contains(err.Error(), \"not in PEM format\") {\n\t\t\t\treturn nil, fmt.Errorf(\"gitlab: invalid rootCAData\")\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"gitlab: failed to create HTTP client: %v\", err)\n\t\t}\n\t\thttpClient.Timeout = 30 * time.Second\n\t}\n\treturn &gitlabConnector{\n\t\tbaseURL:             c.BaseURL,\n\t\tredirectURI:         c.RedirectURI,\n\t\tclientID:            c.ClientID,\n\t\tclientSecret:        c.ClientSecret,\n\t\tlogger:              logger.With(slog.Group(\"connector\", \"type\", \"gitlab\", \"id\", id)),\n\t\tgroups:              c.Groups,\n\t\tuseLoginAsID:        c.UseLoginAsID,\n\t\tgetGroupsPermission: c.GetGroupsPermission,\n\t\thttpClient:          httpClient,\n\t}, nil\n}\n\ntype connectorData struct {\n\t// Support GitLab's Access Tokens and Refresh tokens.\n\tAccessToken  string `json:\"accessToken\"`\n\tRefreshToken string `json:\"refreshToken\"`\n}\n\nvar (\n\t_ connector.CallbackConnector      = (*gitlabConnector)(nil)\n\t_ connector.RefreshConnector       = (*gitlabConnector)(nil)\n\t_ connector.TokenIdentityConnector = (*gitlabConnector)(nil)\n)\n\ntype gitlabConnector struct {\n\tbaseURL      string\n\tredirectURI  string\n\tgroups       []string\n\tclientID     string\n\tclientSecret string\n\tlogger       *slog.Logger\n\thttpClient   *http.Client\n\t// if set to true will use the user's handle rather than their numeric id as the ID\n\tuseLoginAsID bool\n\n\t// if set to true permissions will be added to list of groups\n\tgetGroupsPermission bool\n}\n\nfunc (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {\n\tgitlabScopes := []string{scopeUser}\n\tif c.groupsRequired(scopes.Groups) {\n\t\tgitlabScopes = []string{scopeUser, scopeOpenID}\n\t}\n\n\tgitlabEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + \"/oauth/authorize\", TokenURL: c.baseURL + \"/oauth/token\"}\n\treturn &oauth2.Config{\n\t\tClientID:     c.clientID,\n\t\tClientSecret: c.clientSecret,\n\t\tEndpoint:     gitlabEndpoint,\n\t\tScopes:       gitlabScopes,\n\t\tRedirectURL:  c.redirectURI,\n\t}\n}\n\nfunc (c *gitlabConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", c.redirectURI, callbackURL)\n\t}\n\treturn c.oauth2Config(scopes).AuthCodeURL(state), nil, nil\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n\nfunc (c *gitlabConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\toauth2Config := c.oauth2Config(s)\n\n\tctx := r.Context()\n\tif c.httpClient != nil {\n\t\tctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)\n\t}\n\n\ttoken, err := oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"gitlab: failed to get token: %v\", err)\n\t}\n\n\treturn c.identity(ctx, s, token)\n}\n\nfunc (c *gitlabConnector) identity(ctx context.Context, s connector.Scopes, token *oauth2.Token) (identity connector.Identity, err error) {\n\toauth2Config := c.oauth2Config(s)\n\tclient := oauth2Config.Client(ctx, token)\n\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"gitlab: get user: %v\", err)\n\t}\n\n\tusername := user.Name\n\tif username == \"\" {\n\t\tusername = user.Email\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:            strconv.Itoa(user.ID),\n\t\tUsername:          username,\n\t\tPreferredUsername: user.Username,\n\t\tEmail:             user.Email,\n\t\tEmailVerified:     true,\n\t}\n\tif c.useLoginAsID {\n\t\tidentity.UserID = user.Username\n\t}\n\n\tif c.groupsRequired(s.Groups) {\n\t\tgroups, err := c.getGroups(ctx, client, s.Groups, user.Username)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"gitlab: get groups: %v\", err)\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\tif s.OfflineAccess {\n\t\tdata := connectorData{RefreshToken: token.RefreshToken, AccessToken: token.AccessToken}\n\t\tconnData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"gitlab: marshal connector data: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\nfunc (c *gitlabConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {\n\tvar data connectorData\n\tif err := json.Unmarshal(ident.ConnectorData, &data); err != nil {\n\t\treturn ident, fmt.Errorf(\"gitlab: unmarshal connector data: %v\", err)\n\t}\n\toauth2Config := c.oauth2Config(s)\n\n\tif c.httpClient != nil {\n\t\tctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)\n\t}\n\n\tswitch {\n\tcase data.RefreshToken != \"\":\n\t\t{\n\t\t\tt := &oauth2.Token{\n\t\t\t\tRefreshToken: data.RefreshToken,\n\t\t\t\tExpiry:       time.Now().Add(-time.Hour),\n\t\t\t}\n\t\t\ttoken, err := oauth2Config.TokenSource(ctx, t).Token()\n\t\t\tif err != nil {\n\t\t\t\treturn ident, fmt.Errorf(\"gitlab: failed to get refresh token: %v\", err)\n\t\t\t}\n\t\t\treturn c.identity(ctx, s, token)\n\t\t}\n\tcase data.AccessToken != \"\":\n\t\t{\n\t\t\ttoken := &oauth2.Token{\n\t\t\t\tAccessToken: data.AccessToken,\n\t\t\t}\n\t\t\treturn c.identity(ctx, s, token)\n\t\t}\n\tdefault:\n\t\treturn ident, errors.New(\"no refresh or access token found\")\n\t}\n}\n\n// TokenIdentity is used for token exchange, verifying a GitLab access token\n// and returning the associated user identity. This enables direct authentication\n// with Dex using an existing GitLab token without going through the OAuth flow.\n//\n// Note: The connector decides whether to fetch groups based on its configuration\n// (groups filter, getGroupsPermission), not on the scopes from the token exchange request.\n// The server will then decide whether to include groups in the final token based on\n// the requested scopes. This matches the behavior of other connectors (e.g., OIDC).\nfunc (c *gitlabConnector) TokenIdentity(ctx context.Context, _, subjectToken string) (connector.Identity, error) {\n\tif c.httpClient != nil {\n\t\tctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)\n\t}\n\n\ttoken := &oauth2.Token{\n\t\tAccessToken: subjectToken,\n\t\tTokenType:   \"Bearer\", // GitLab tokens are typically Bearer tokens even if the type is not explicitly provided.\n\t}\n\n\t// For token exchange, we determine if groups should be fetched based on connector configuration.\n\t// If the connector has groups filter or getGroupsPermission enabled, we fetch groups.\n\tscopes := connector.Scopes{\n\t\t// Scopes are not provided in token exchange, so we request groups every time and return only if configured.\n\t\tGroups: true,\n\t}\n\n\treturn c.identity(ctx, scopes, token)\n}\n\nfunc (c *gitlabConnector) groupsRequired(groupScope bool) bool {\n\treturn len(c.groups) > 0 || groupScope\n}\n\n// user queries the GitLab API for profile information using the provided client. The HTTP\n// client is expected to be constructed by the golang.org/x/oauth2 package, which inserts\n// a bearer token as part of the request.\nfunc (c *gitlabConnector) user(ctx context.Context, client *http.Client) (gitlabUser, error) {\n\tvar u gitlabUser\n\treq, err := http.NewRequest(\"GET\", c.baseURL+\"/api/v4/user\", nil)\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"gitlab: new req: %v\", err)\n\t}\n\treq = req.WithContext(ctx)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"gitlab: get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn u, fmt.Errorf(\"gitlab: read body: %v\", err)\n\t\t}\n\t\treturn u, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(&u); err != nil {\n\t\treturn u, fmt.Errorf(\"failed to decode response: %v\", err)\n\t}\n\treturn u, nil\n}\n\ntype userInfo struct {\n\tGroups               []string `json:\"groups\"`\n\tOwnerPermission      []string `json:\"https://gitlab.org/claims/groups/owner\"`\n\tMaintainerPermission []string `json:\"https://gitlab.org/claims/groups/maintainer\"`\n\tDeveloperPermission  []string `json:\"https://gitlab.org/claims/groups/developer\"`\n}\n\n// userGroups queries the GitLab API for group membership.\n//\n// The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package,\n// which inserts a bearer token as part of the request.\nfunc (c *gitlabConnector) userGroups(ctx context.Context, client *http.Client) ([]string, error) {\n\treq, err := http.NewRequest(\"GET\", c.baseURL+\"/oauth/userinfo\", nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"gitlab: new req: %v\", err)\n\t}\n\treq = req.WithContext(ctx)\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"gitlab: get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"gitlab: read body: %v\", err)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\tvar u userInfo\n\tif err := json.NewDecoder(resp.Body).Decode(&u); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode response: %v\", err)\n\t}\n\n\tif c.getGroupsPermission {\n\t\tgroups := c.setGroupsPermission(u)\n\t\treturn groups, nil\n\t}\n\n\treturn u.Groups, nil\n}\n\nfunc (c *gitlabConnector) setGroupsPermission(u userInfo) []string {\n\tgroups := u.Groups\n\nL1:\n\tfor _, g := range groups {\n\t\tfor _, op := range u.OwnerPermission {\n\t\t\tif g == op {\n\t\t\t\tgroups = append(groups, fmt.Sprintf(\"%s:owner\", g))\n\t\t\t\tcontinue L1\n\t\t\t}\n\t\t\tif len(g) > len(op) {\n\t\t\t\tif g[0:len(op)] == op && string(g[len(op)]) == \"/\" {\n\t\t\t\t\tgroups = append(groups, fmt.Sprintf(\"%s:owner\", g))\n\t\t\t\t\tcontinue L1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor _, mp := range u.MaintainerPermission {\n\t\t\tif g == mp {\n\t\t\t\tgroups = append(groups, fmt.Sprintf(\"%s:maintainer\", g))\n\t\t\t\tcontinue L1\n\t\t\t}\n\t\t\tif len(g) > len(mp) {\n\t\t\t\tif g[0:len(mp)] == mp && string(g[len(mp)]) == \"/\" {\n\t\t\t\t\tgroups = append(groups, fmt.Sprintf(\"%s:maintainer\", g))\n\t\t\t\t\tcontinue L1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor _, dp := range u.DeveloperPermission {\n\t\t\tif g == dp {\n\t\t\t\tgroups = append(groups, fmt.Sprintf(\"%s:developer\", g))\n\t\t\t\tcontinue L1\n\t\t\t}\n\t\t\tif len(g) > len(dp) {\n\t\t\t\tif g[0:len(dp)] == dp && string(g[len(dp)]) == \"/\" {\n\t\t\t\t\tgroups = append(groups, fmt.Sprintf(\"%s:developer\", g))\n\t\t\t\t\tcontinue L1\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn groups\n}\n\nfunc (c *gitlabConnector) getGroups(ctx context.Context, client *http.Client, groupScope bool, userLogin string) ([]string, error) {\n\tgitlabGroups, err := c.userGroups(ctx, client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif len(c.groups) > 0 {\n\t\tfilteredGroups := groups.Filter(gitlabGroups, c.groups)\n\t\tif len(filteredGroups) == 0 {\n\t\t\treturn nil, fmt.Errorf(\"gitlab: user %q is not in any of the required groups\", userLogin)\n\t\t}\n\t\treturn filteredGroups, nil\n\t} else if groupScope {\n\t\treturn gitlabGroups, nil\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "connector/gitlab/gitlab_test.go",
    "content": "package gitlab\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nfunc readValidRootCAData(t *testing.T) []byte {\n\tt.Helper()\n\tb, err := os.ReadFile(\"testdata/rootCA.pem\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to read rootCA.pem testdata: %v\", err)\n\t}\n\treturn b\n}\n\nfunc newLocalHTTPSTestServer(t *testing.T, handler http.Handler) *httptest.Server {\n\tt.Helper()\n\n\tts := httptest.NewUnstartedServer(handler)\n\tcert, err := tls.LoadX509KeyPair(\"testdata/server.crt\", \"testdata/server.key\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to load TLS test cert/key: %v\", err)\n\t}\n\tts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}\n\tts.StartTLS()\n\treturn ts\n}\n\nfunc TestOpenWithRootCADataCreatesHTTPClient(t *testing.T) {\n\tlogger := slog.New(slog.NewTextHandler(io.Discard, nil))\n\n\tcfg := &Config{\n\t\tRootCAData: readValidRootCAData(t),\n\t}\n\n\tconn, err := cfg.Open(\"test\", logger)\n\tif err != nil {\n\t\tt.Fatalf(\"expected nil error, got %v\", err)\n\t}\n\n\tgc, ok := conn.(*gitlabConnector)\n\tif !ok {\n\t\tt.Fatalf(\"expected *gitlabConnector, got %T\", conn)\n\t}\n\tif gc.httpClient == nil {\n\t\tt.Fatalf(\"expected httpClient to be non-nil\")\n\t}\n\tif gc.httpClient.Timeout != 30*time.Second {\n\t\tt.Fatalf(\"expected httpClient timeout %v, got %v\", 30*time.Second, gc.httpClient.Timeout)\n\t}\n\ttr, ok := gc.httpClient.Transport.(*http.Transport)\n\tif !ok {\n\t\tt.Fatalf(\"expected transport to be *http.Transport, got %T\", gc.httpClient.Transport)\n\t}\n\t// ProxyFromEnvironment is expected to be enabled (non-nil proxy func).\n\tif tr.Proxy == nil {\n\t\tt.Fatalf(\"expected transport.Proxy to be set (ProxyFromEnvironment)\")\n\t}\n\tif tr.TLSClientConfig == nil || tr.TLSClientConfig.RootCAs == nil {\n\t\tt.Fatalf(\"expected transport TLS root CAs to be configured\")\n\t}\n}\n\nfunc TestOpenWithInvalidRootCADataReturnsError(t *testing.T) {\n\tlogger := slog.New(slog.NewTextHandler(io.Discard, nil))\n\n\tcfg := &Config{\n\t\tRootCAData: []byte(\"not a pem\"),\n\t}\n\n\t_, err := cfg.Open(\"test\", logger)\n\tif err == nil {\n\t\tt.Fatalf(\"expected error, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), \"invalid rootCAData\") {\n\t\tt.Fatalf(\"expected error to contain %q, got %q\", \"invalid rootCAData\", err.Error())\n\t}\n}\n\nfunc TestHandleCallbackCustomRootCADataEnablesTLSRequests(t *testing.T) {\n\tlogger := slog.New(slog.NewTextHandler(io.Discard, nil))\n\n\tts := newLocalHTTPSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tswitch r.URL.Path {\n\t\tcase \"/oauth/token\":\n\t\t\t// oauth2.Exchange expects an access token in response.\n\t\t\tfmt.Fprint(w, `{\"access_token\":\"abc\",\"token_type\":\"bearer\",\"expires_in\":30}`)\n\t\tcase \"/api/v4/user\":\n\t\t\tjson.NewEncoder(w).Encode(gitlabUser{Email: \"some@email.com\", ID: 12345678})\n\t\tdefault:\n\t\t\thttp.NotFound(w, r)\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &Config{\n\t\tBaseURL:      ts.URL,\n\t\tClientID:     \"client-id\",\n\t\tClientSecret: \"client-secret\",\n\t\tRedirectURI:  \"https://example.invalid/callback\",\n\t\tRootCAData:   readValidRootCAData(t),\n\t}\n\n\tconn, err := cfg.Open(\"test\", logger)\n\tif err != nil {\n\t\tt.Fatalf(\"Open() error: %v\", err)\n\t}\n\n\thostURL, err := url.Parse(ts.URL)\n\texpectNil(t, err)\n\treq, err := http.NewRequest(\"GET\", hostURL.String()+\"?code=testcode\", nil)\n\texpectNil(t, err)\n\n\tidentity, err := conn.(connector.CallbackConnector).HandleCallback(connector.Scopes{Groups: false}, nil, req)\n\tif err != nil {\n\t\tt.Fatalf(\"HandleCallback() error: %v\", err)\n\t}\n\tif identity.Email != \"some@email.com\" || identity.UserID != \"12345678\" {\n\t\tt.Fatalf(\"unexpected identity: %#v\", identity)\n\t}\n}\n\nfunc TestHandleCallbackWithoutRootCADataFailsTLS(t *testing.T) {\n\tlogger := slog.New(slog.NewTextHandler(io.Discard, nil))\n\n\tts := newLocalHTTPSTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tswitch r.URL.Path {\n\t\tcase \"/oauth/token\":\n\t\t\tfmt.Fprint(w, `{\"access_token\":\"abc\",\"token_type\":\"bearer\",\"expires_in\":30}`)\n\t\tcase \"/api/v4/user\":\n\t\t\tjson.NewEncoder(w).Encode(gitlabUser{Email: \"some@email.com\", ID: 12345678})\n\t\tdefault:\n\t\t\thttp.NotFound(w, r)\n\t\t}\n\t}))\n\tdefer ts.Close()\n\n\tcfg := &Config{\n\t\tBaseURL:      ts.URL,\n\t\tClientID:     \"client-id\",\n\t\tClientSecret: \"client-secret\",\n\t\tRedirectURI:  \"https://example.invalid/callback\",\n\t\t// RootCAData intentionally omitted: should fail TLS verification against our custom server cert.\n\t}\n\n\tconn, err := cfg.Open(\"test\", logger)\n\tif err != nil {\n\t\tt.Fatalf(\"Open() error: %v\", err)\n\t}\n\n\thostURL, err := url.Parse(ts.URL)\n\texpectNil(t, err)\n\treq, err := http.NewRequest(\"GET\", hostURL.String()+\"?code=testcode\", nil)\n\texpectNil(t, err)\n\n\t_, err = conn.(connector.CallbackConnector).HandleCallback(connector.Scopes{Groups: false}, nil, req)\n\tif err == nil {\n\t\tt.Fatalf(\"expected TLS error, got nil\")\n\t}\n}\n\nfunc TestUserGroups(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\", \"team-2\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := gitlabConnector{baseURL: s.URL}\n\tgroups, err := c.getGroups(context.Background(), newClient(), true, \"joebloggs\")\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\n\t\t\"team-1\",\n\t\t\"team-2\",\n\t})\n}\n\nfunc TestUserGroupsWithFiltering(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\", \"team-2\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := gitlabConnector{baseURL: s.URL, groups: []string{\"team-1\"}}\n\tgroups, err := c.getGroups(context.Background(), newClient(), true, \"joebloggs\")\n\n\texpectNil(t, err)\n\texpectEquals(t, groups, []string{\n\t\t\"team-1\",\n\t})\n}\n\nfunc TestUserGroupsWithoutOrgs(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\tc := gitlabConnector{baseURL: s.URL}\n\tgroups, err := c.getGroups(context.Background(), newClient(), true, \"joebloggs\")\n\n\texpectNil(t, err)\n\texpectEquals(t, len(groups), 0)\n}\n\n// tests that the email is used as their username when they have no username set\nfunc TestUsernameIncludedInFederatedIdentity(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v4/user\": gitlabUser{Email: \"some@email.com\", ID: 12345678},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := gitlabConnector{baseURL: s.URL, httpClient: newClient()}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: false}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some@email.com\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n\texpectEquals(t, 0, len(identity.Groups))\n\n\tc = gitlabConnector{baseURL: s.URL, httpClient: newClient()}\n\tidentity, err = c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some@email.com\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n\texpectEquals(t, identity.Groups, []string{\"team-1\"})\n}\n\nfunc TestLoginUsedAsIDWhenConfigured(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v4/user\": gitlabUser{Email: \"some@email.com\", ID: 12345678, Name: \"Joe Bloggs\", Username: \"joebloggs\"},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := gitlabConnector{baseURL: s.URL, httpClient: newClient(), useLoginAsID: true}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.UserID, \"joebloggs\")\n\texpectEquals(t, identity.Username, \"Joe Bloggs\")\n}\n\nfunc TestLoginWithTeamWhitelisted(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v4/user\": gitlabUser{Email: \"some@email.com\", ID: 12345678, Name: \"Joe Bloggs\"},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := gitlabConnector{baseURL: s.URL, httpClient: newClient(), groups: []string{\"team-1\"}}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.UserID, \"12345678\")\n\texpectEquals(t, identity.Username, \"Joe Bloggs\")\n}\n\nfunc TestLoginWithTeamNonWhitelisted(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v4/user\": gitlabUser{Email: \"some@email.com\", ID: 12345678, Name: \"Joe Bloggs\", Username: \"joebloggs\"},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := gitlabConnector{baseURL: s.URL, httpClient: newClient(), groups: []string{\"team-2\"}}\n\t_, err = c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNotNil(t, err, \"HandleCallback error\")\n\texpectEquals(t, err.Error(), \"gitlab: get groups: gitlab: user \\\"joebloggs\\\" is not in any of the required groups\")\n}\n\nfunc TestRefresh(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v4/user\": gitlabUser{Email: \"some@email.com\", ID: 12345678},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\":  \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"refresh_token\": \"oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC\",\n\t\t\t\"expires_in\":    \"30\",\n\t\t},\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := gitlabConnector{baseURL: s.URL, httpClient: newClient()}\n\n\texpectedConnectorData, err := json.Marshal(connectorData{\n\t\tRefreshToken: \"oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC\",\n\t\tAccessToken:  \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t})\n\texpectNil(t, err)\n\n\tidentity, err := c.HandleCallback(connector.Scopes{OfflineAccess: true}, nil, req)\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some@email.com\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n\texpectEquals(t, identity.ConnectorData, expectedConnectorData)\n\n\tidentity, err = c.Refresh(context.Background(), connector.Scopes{OfflineAccess: true}, identity)\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"some@email.com\")\n\texpectEquals(t, identity.UserID, \"12345678\")\n\texpectEquals(t, identity.ConnectorData, expectedConnectorData)\n}\n\nfunc TestRefreshWithEmptyConnectorData(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v4/user\": gitlabUser{Email: \"some@email.com\", ID: 12345678},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\":  \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"refresh_token\": \"oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC\",\n\t\t\t\"expires_in\":    \"30\",\n\t\t},\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups: []string{\"team-1\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\temptyConnectorData, err := json.Marshal(connectorData{\n\t\tRefreshToken: \"\",\n\t\tAccessToken:  \"\",\n\t})\n\texpectNil(t, err)\n\n\tc := gitlabConnector{baseURL: s.URL, httpClient: newClient()}\n\temptyIdentity := connector.Identity{ConnectorData: emptyConnectorData}\n\n\tidentity, err := c.Refresh(context.Background(), connector.Scopes{OfflineAccess: true}, emptyIdentity)\n\texpectNotNil(t, err, \"Refresh error\")\n\texpectEquals(t, emptyIdentity, identity)\n}\n\nfunc TestGroupsWithPermission(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/api/v4/user\": gitlabUser{Email: \"some@email.com\", ID: 12345678, Name: \"Joe Bloggs\", Username: \"joebloggs\"},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t\t\"/oauth/userinfo\": userInfo{\n\t\t\tGroups:               []string{\"ops\", \"dev\", \"ops-test\", \"ops/project\", \"dev/project1\", \"dev/project2\"},\n\t\t\tOwnerPermission:      []string{\"ops\"},\n\t\t\tDeveloperPermission:  []string{\"dev\"},\n\t\t\tMaintainerPermission: []string{\"dev/project1\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := gitlabConnector{baseURL: s.URL, httpClient: newClient(), getGroupsPermission: true}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\texpectNil(t, err)\n\n\texpectEquals(t, identity.Groups, []string{\n\t\t\"ops\",\n\t\t\"dev\",\n\t\t\"ops-test\",\n\t\t\"ops/project\",\n\t\t\"dev/project1\",\n\t\t\"dev/project2\",\n\t\t\"ops:owner\",\n\t\t\"dev:developer\",\n\t\t\"ops/project:owner\",\n\t\t\"dev/project1:maintainer\",\n\t\t\"dev/project2:developer\",\n\t})\n}\n\nfunc newTestServer(responses map[string]interface{}) *httptest.Server {\n\treturn httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresponse := responses[r.RequestURI]\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(response)\n\t}))\n}\n\nfunc newClient() *http.Client {\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\treturn &http.Client{Transport: tr}\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Errorf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectNotNil(t *testing.T, a interface{}, msg string) {\n\tif a == nil {\n\t\tt.Errorf(\"Expected %+v to not to be nil\", msg)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n\nfunc TestTokenIdentity(t *testing.T) {\n\t// Note: These tests verify that the connector returns groups based on its configuration.\n\t// The actual inclusion of groups in the final Dex token depends on the 'groups' scope\n\t// in the token exchange request, which is handled by the Dex server, not the connector.\n\ttests := []struct {\n\t\tname                string\n\t\tuserInfo            userInfo\n\t\tgroups              []string\n\t\tgetGroupsPermission bool\n\t\tuseLoginAsID        bool\n\t\texpectUserID        string\n\t\texpectGroups        []string\n\t}{\n\t\t{\n\t\t\tname:         \"without groups config\",\n\t\t\texpectUserID: \"12345678\",\n\t\t\texpectGroups: nil,\n\t\t},\n\t\t{\n\t\t\tname: \"with groups filter\",\n\t\t\tuserInfo: userInfo{\n\t\t\t\tGroups: []string{\"team-1\", \"team-2\"},\n\t\t\t},\n\t\t\tgroups:       []string{\"team-1\"},\n\t\t\texpectUserID: \"12345678\",\n\t\t\texpectGroups: []string{\"team-1\"},\n\t\t},\n\t\t{\n\t\t\tname: \"with groups permission\",\n\t\t\tuserInfo: userInfo{\n\t\t\t\tGroups:               []string{\"ops\", \"dev\"},\n\t\t\t\tOwnerPermission:      []string{\"ops\"},\n\t\t\t\tDeveloperPermission:  []string{\"dev\"},\n\t\t\t\tMaintainerPermission: []string{},\n\t\t\t},\n\t\t\tgetGroupsPermission: true,\n\t\t\texpectUserID:        \"12345678\",\n\t\t\texpectGroups:        []string{\"ops\", \"dev\", \"ops:owner\", \"dev:developer\"},\n\t\t},\n\t\t{\n\t\t\tname:         \"with useLoginAsID\",\n\t\t\tuseLoginAsID: true,\n\t\t\texpectUserID: \"joebloggs\",\n\t\t\texpectGroups: nil,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresponses := map[string]interface{}{\n\t\t\t\t\"/api/v4/user\": gitlabUser{\n\t\t\t\t\tEmail:    \"some@email.com\",\n\t\t\t\t\tID:       12345678,\n\t\t\t\t\tName:     \"Joe Bloggs\",\n\t\t\t\t\tUsername: \"joebloggs\",\n\t\t\t\t},\n\t\t\t\t\"/oauth/userinfo\": tc.userInfo,\n\t\t\t}\n\n\t\t\ts := newTestServer(responses)\n\t\t\tdefer s.Close()\n\n\t\t\tc := gitlabConnector{\n\t\t\t\tbaseURL:             s.URL,\n\t\t\t\thttpClient:          newClient(),\n\t\t\t\tgroups:              tc.groups,\n\t\t\t\tgetGroupsPermission: tc.getGroupsPermission,\n\t\t\t\tuseLoginAsID:        tc.useLoginAsID,\n\t\t\t}\n\n\t\t\taccessToken := \"test-access-token\"\n\t\t\tctx := context.Background()\n\t\t\tidentity, err := c.TokenIdentity(ctx, \"urn:ietf:params:oauth:token-type:access_token\", accessToken)\n\n\t\t\texpectNil(t, err)\n\t\t\texpectEquals(t, identity.UserID, tc.expectUserID)\n\t\t\texpectEquals(t, identity.Username, \"Joe Bloggs\")\n\t\t\texpectEquals(t, identity.PreferredUsername, \"joebloggs\")\n\t\t\texpectEquals(t, identity.Email, \"some@email.com\")\n\t\t\texpectEquals(t, identity.EmailVerified, true)\n\t\t\texpectEquals(t, identity.Groups, tc.expectGroups)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "connector/gitlab/testdata/rootCA.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIID1jCCAr4CCQCG4JBeSi6cDjANBgkqhkiG9w0BAQsFADCBrDELMAkGA1UEBhMC\nVVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQHDApSYW5kb21DaXR5MRsw\nGQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNVBAsMFlJhbmRvbU9yZ2Fu\naXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxvQGV4YW1wbGUuY29tMRIw\nEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIxMDA3MjIwNjQwWhcNMzIxMDA0MjIwNjQw\nWjCBrDELMAkGA1UEBhMCVVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQH\nDApSYW5kb21DaXR5MRswGQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNV\nBAsMFlJhbmRvbU9yZ2FuaXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxv\nQGV4YW1wbGUuY29tMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDh0HlpAKMKYyxbvW70XRY2bVNiNdAFninug1P4FDAJ\nz8xnbFzk17FLY7zqdtGTDmPDJ8AAxIwpGv2zYWW5VMeqKWfvyuD5dSCauY1Pdmug\nuZbpAvoJrx1sw+TL61ByVmy8x3ccB4LLKuzil/vAzUDJQkPsfTECVUPV+yiGSDuO\nEEVR9X6rZUwx2expXm8Wtb/a88FbPVI09b9eb4iWfLvGD2eNAtw8w21W0X7sQ8Hq\nzEPqquMEL4qPnNDdtk592uHvLLrd1uH8qH7c1JyA76T7H3YeUCNEi+PnLgqtsZmX\nsKY62HnLt8/LAClVsN9lFYkKEjU9V+U7IN2cL6+EwtsdAgMBAAEwDQYJKoZIhvcN\nAQELBQADggEBAN6g0qit/3R2X+KdR0LgRXF/h4qQFgcV6cxnhRAmLIDNJlxKSHqN\nIE5+bxzCbkblzGfr/jNPqW0s+yaN4CyMgKNYSzkLBPE4FF+19Uv+dyYfFms3mDJ7\n0rGjS5bCscThWhpaSw20LcwQcr/+X+/fGzJ01dVFK1UOjBKg4d4dMwxklbIkZqIq\nsiRW0GMy26mgVZ/BSjeh5kEjs6h6H3cJsGl7xYT+BI7wnxHwGeT9tkBgiyT5FwaS\nvtdZkBpQ9q8f7FwsEm3woLHdWuOnrtUtVpY/oc6WFGdROQdGzjSk0D3kHs9YhueC\nGSzZKrqX+TSIgpPrLYNHX4uxlo5TAwP/5GM=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/gitlab/testdata/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIE5TCCA82gAwIBAgIJAMGzXwBRpkG7MA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD\nVQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzARBgNVBAcMClJhbmRvbUNp\ndHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEfMB0GA1UECwwWUmFuZG9t\nT3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYRaGVsbG9AZXhhbXBsZS5j\nb20xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjEwMDcyMjA3MDhaFw0zMjEwMDQy\nMjA3MDhaMIGsMQswCQYDVQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzAR\nBgNVBAcMClJhbmRvbUNpdHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEf\nMB0GA1UECwwWUmFuZG9tT3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYR\naGVsbG9AZXhhbXBsZS5jb20xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAMuKdpXP87Q7Kg3iafXzvBuVIyV1K5UmMYiN\nkoztkC5XrCzHaQRS/CoIb7/nUqmtAxx7RL0jzhZ93zBN4HY/Zcnrd9tXoPPxi0mG\nZZWfFU6nN8nOkMHWzEbHVBmhxpfGtwmLcajQ4HrK1TZwJUn6GqclHQRy/gjxkiw5\nKPqzfVOVlA6ht4KdKstKazQkWZ5gdWT4d8yrEy/IT4oaW05xALBMQ7YGjkzWKsSF\n6ygXI7xqF9rg9jCnUsPYg4f8ut3N0c00KjsfKOOj2dF/ZyjedQ5c0u4hHmxSo3Ka\n0ZTmIrMfbVXgGjxRG2HZXLpPvQKoCf/fOX8Irdr+lahFVKASxN0CAwEAAaOCAQYw\nggECMIHLBgNVHSMEgcMwgcChgbKkga8wgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQI\nDAtSYW5kb21TdGF0ZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFu\nZG9tT3JnYW5pemF0aW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0\nMSAwHgYJKoZIhvcNAQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9j\nYWxob3N0ggkAhuCQXkounA4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwGgYDVR0R\nBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCWmh5ebpkm\nv2B1yQgarSCSSkLZ5DZSAJjrPgW2IJqCW2q2D1HworbW1Yn5jqrM9FKGnJfjCyve\nzBB5AOlGp+0bsZGgMRMCavgv4QhTThXUoJqqHcfEu4wHndcgrqSadxmV5aisSR4u\ngXnjW43o3akby+h1K40RR3vVkpzPaoC3/bgk7WVpfpPiP32E24a01gETozRb/of/\nATN3JBe0xh+e63CrPX1sago5+u3UETIoOr0fW8M/gU9GApmJiFAXwHag6j54hLCG\n23EtVDwmlarG8Pj+i0yru8s22QqzAJi5E0OwR4aB8tqicLKYBVfzyLCOielIBUrK\nOkuFKp+VjxQX\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/gitlab/testdata/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLinaVz/O0OyoN\n4mn187wblSMldSuVJjGIjZKM7ZAuV6wsx2kEUvwqCG+/51KprQMce0S9I84Wfd8w\nTeB2P2XJ63fbV6Dz8YtJhmWVnxVOpzfJzpDB1sxGx1QZocaXxrcJi3Go0OB6ytU2\ncCVJ+hqnJR0Ecv4I8ZIsOSj6s31TlZQOobeCnSrLSms0JFmeYHVk+HfMqxMvyE+K\nGltOcQCwTEO2Bo5M1irEhesoFyO8ahfa4PYwp1LD2IOH/LrdzdHNNCo7Hyjjo9nR\nf2co3nUOXNLuIR5sUqNymtGU5iKzH21V4Bo8URth2Vy6T70CqAn/3zl/CK3a/pWo\nRVSgEsTdAgMBAAECggEAU6cxu7q+54kVbKVsdThaTF/MFR4F7oPHAd9lpuQQSOuh\niLngMHXGy6OyAgYZlEDWMYN8KdwoXFgZPaoUIaVGuWk8Vnq6XOgeHfbNk2PRhwT0\nyc1K80/Lnx9XMj2p+EEkgxi7eu12BSGN5ZTLzo6rG50GQwjb3WMjd2d6rybL0GjC\nwg2arcBk3sSMYmvZOqlAsaQmtgwkJhvhVkVfEQSD3VKF7g0dh/h3LIPyM0Ff4M67\nKpLMPPwzUJ/0Z4ewAP06mMKUA86R93M+dWs2eh1oBGnRkVQdhCJLXJpuGHZ6BTiB\nRy0AeorHfnVXPbtpUeAq6m5/BBl6qX0ooB08BIFwAQKBgQDqJpTZS/ZzqL6Kcs14\nMyFu+7DungSxQ5oK9ju7EFSosanSk4UEa/lw992kM6nsIMwgSVQgba5zKcVMeSmk\nAVbpznegQD1BYCwOGwbGvkJ8jbhPy+WLbbRjWT/E6AItZgUK+fyTIcNvSehcQqsT\nfhgWsK7ueZCmLQfVhK1AxtvY3QKBgQDeiKuo8plsH/7IxDn7KVHBOHKPC2ZPzg03\ni7La6zomiRckwwPnhicRSYsjtfCCW6Ms+uzjTEItgFM+5PdrXheeku+z/sExRtZu\nemqPqDomixlXDRQ6RN3gnBSk4RU+ROB1u1uBLWXqRz8Gp2zJGRxhHfYt2zefBv4w\n/cIuPC3cAQKBgD2UsAkGJWb9tj8LOmama+CYaUwYWvuT3+uKHuNvxBQpxZQQICet\njgjb53rL66Cib4z+PBXbQsoe7jjSlNUBVS5gkq2et31+IZgEG6AhYbMIQrUZ1uD4\nlTybuF289vWhoynj3T2E37VhJq89CWky/HrbNOabKiPKLAlHv5kNs7wxAoGBANEJ\nXQbU7J2O6Iy7FyQBSlTQq3wHX1Iz4mJ9DcNrFzK/sEfOEMrZT7WDefpPm984KW3F\nP+S766ZGVuxLtMbcmh9RM23HLr8VJbSdtZ/AjO9L1r/Y/1lE+49TzmibLpNRq++r\n0WbkuEl8J44ek6fLuMbZmDi3JeZycTCgDlnUGdgBAoGAYdliovtURZCm46t1uE3F\nidCLCXCccjkt1hcNGNjck/b0trHA7wOEqICIguoWDlEBTc0PDvHEq6PfKyqptGkj\nAgaZTMF/aZiGqlT7VRpBuzxM/uV5xzCg+i2ViaW/p3xq0z2PRljVZiEfe5aWcjiM\nouTtnC3TgmcjhTgGmb48QQE=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "connector/google/google.go",
    "content": "// Package google implements logging in through Google's OpenID Connect provider.\npackage google\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"strings\"\n\t\"time\"\n\n\t\"cloud.google.com/go/compute/metadata\"\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"golang.org/x/exp/slices\"\n\t\"golang.org/x/oauth2\"\n\t\"golang.org/x/oauth2/google\"\n\tadmin \"google.golang.org/api/admin/directory/v1\"\n\t\"google.golang.org/api/impersonate\"\n\t\"google.golang.org/api/option\"\n\n\t\"github.com/dexidp/dex/connector\"\n\tpkg_groups \"github.com/dexidp/dex/pkg/groups\"\n)\n\nconst (\n\tissuerURL                  = \"https://accounts.google.com\"\n\twildcardDomainToAdminEmail = \"*\"\n)\n\n// Config holds configuration options for Google logins.\ntype Config struct {\n\tClientID     string `json:\"clientID\"`\n\tClientSecret string `json:\"clientSecret\"`\n\tRedirectURI  string `json:\"redirectURI\"`\n\n\tScopes []string `json:\"scopes\"` // defaults to \"profile\" and \"email\"\n\n\t// Optional list of whitelisted domains\n\t// If this field is nonempty, only users from a listed domain will be allowed to log in\n\tHostedDomains []string `json:\"hostedDomains\"`\n\n\t// Optional list of whitelisted groups\n\t// If this field is nonempty, only users from a listed group will be allowed to log in\n\tGroups []string `json:\"groups\"`\n\n\t// Optional path to service account json\n\t// If nonempty, and groups claim is made, will use authentication from file to\n\t// check groups with the admin directory api\n\tServiceAccountFilePath string `json:\"serviceAccountFilePath\"`\n\n\t// Deprecated: Use DomainToAdminEmail\n\tAdminEmail string\n\n\t// Required if ServiceAccountFilePath\n\t// The map workspace domain to email of a GSuite super user which the service account will impersonate\n\t// when listing groups\n\tDomainToAdminEmail map[string]string\n\n\t// If this field is true, fetch direct group membership and transitive group membership\n\tFetchTransitiveGroupMembership bool `json:\"fetchTransitiveGroupMembership\"`\n\n\t// Optional value for the prompt parameter, defaults to consent when offline_access\n\t// scope is requested\n\tPromptType *string `json:\"promptType\"`\n}\n\n// Open returns a connector which can be used to login users through Google.\nfunc (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) {\n\tlogger = logger.With(slog.Group(\"connector\", \"type\", \"google\", \"id\", id))\n\tif c.AdminEmail != \"\" {\n\t\tlogger.Warn(`use \"domainToAdminEmail.*\" option instead of \"adminEmail\"`, \"deprecated\", true)\n\t\tif c.DomainToAdminEmail == nil {\n\t\t\tc.DomainToAdminEmail = make(map[string]string)\n\t\t}\n\n\t\tc.DomainToAdminEmail[wildcardDomainToAdminEmail] = c.AdminEmail\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tprovider, err := oidc.NewProvider(ctx, issuerURL)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"failed to get provider: %v\", err)\n\t}\n\n\tscopes := []string{oidc.ScopeOpenID}\n\tif len(c.Scopes) > 0 {\n\t\tscopes = append(scopes, c.Scopes...)\n\t} else {\n\t\tscopes = append(scopes, \"profile\", \"email\")\n\t}\n\n\tadminSrv := make(map[string]*admin.Service)\n\n\t// We know impersonation is required when using a service account credential\n\t// TODO: or is it?\n\tif len(c.DomainToAdminEmail) == 0 && c.ServiceAccountFilePath != \"\" {\n\t\tcancel()\n\t\treturn nil, fmt.Errorf(\"directory service requires the domainToAdminEmail option to be configured\")\n\t}\n\n\tif (len(c.DomainToAdminEmail) > 0) || slices.Contains(scopes, \"groups\") {\n\t\tfor domain, adminEmail := range c.DomainToAdminEmail {\n\t\t\tsrv, err := createDirectoryService(c.ServiceAccountFilePath, adminEmail, logger)\n\t\t\tif err != nil {\n\t\t\t\tcancel()\n\t\t\t\treturn nil, fmt.Errorf(\"could not create directory service: %v\", err)\n\t\t\t}\n\n\t\t\tadminSrv[domain] = srv\n\t\t}\n\t}\n\n\tpromptType := \"consent\"\n\tif c.PromptType != nil {\n\t\tpromptType = *c.PromptType\n\t}\n\n\tclientID := c.ClientID\n\treturn &googleConnector{\n\t\tredirectURI: c.RedirectURI,\n\t\toauth2Config: &oauth2.Config{\n\t\t\tClientID:     clientID,\n\t\t\tClientSecret: c.ClientSecret,\n\t\t\tEndpoint:     provider.Endpoint(),\n\t\t\tScopes:       scopes,\n\t\t\tRedirectURL:  c.RedirectURI,\n\t\t},\n\t\tverifier: provider.Verifier(\n\t\t\t&oidc.Config{ClientID: clientID},\n\t\t),\n\t\tlogger:                         logger,\n\t\tcancel:                         cancel,\n\t\thostedDomains:                  c.HostedDomains,\n\t\tgroups:                         c.Groups,\n\t\tserviceAccountFilePath:         c.ServiceAccountFilePath,\n\t\tdomainToAdminEmail:             c.DomainToAdminEmail,\n\t\tfetchTransitiveGroupMembership: c.FetchTransitiveGroupMembership,\n\t\tadminSrv:                       adminSrv,\n\t\tpromptType:                     promptType,\n\t}, nil\n}\n\nvar (\n\t_ connector.CallbackConnector = (*googleConnector)(nil)\n\t_ connector.RefreshConnector  = (*googleConnector)(nil)\n)\n\ntype googleConnector struct {\n\tredirectURI                    string\n\toauth2Config                   *oauth2.Config\n\tverifier                       *oidc.IDTokenVerifier\n\tcancel                         context.CancelFunc\n\tlogger                         *slog.Logger\n\thostedDomains                  []string\n\tgroups                         []string\n\tserviceAccountFilePath         string\n\tdomainToAdminEmail             map[string]string\n\tfetchTransitiveGroupMembership bool\n\tadminSrv                       map[string]*admin.Service\n\tpromptType                     string\n}\n\nfunc (c *googleConnector) Close() error {\n\tc.cancel()\n\treturn nil\n}\n\nfunc (c *googleConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", callbackURL, c.redirectURI)\n\t}\n\n\tvar opts []oauth2.AuthCodeOption\n\tif len(c.hostedDomains) > 0 {\n\t\tpreferredDomain := c.hostedDomains[0]\n\t\tif len(c.hostedDomains) > 1 {\n\t\t\tpreferredDomain = \"*\"\n\t\t}\n\t\topts = append(opts, oauth2.SetAuthURLParam(\"hd\", preferredDomain))\n\t}\n\n\tif s.OfflineAccess {\n\t\topts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam(\"prompt\", c.promptType))\n\t}\n\n\treturn c.oauth2Config.AuthCodeURL(state, opts...), nil, nil\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n\nfunc (c *googleConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\ttoken, err := c.oauth2Config.Exchange(r.Context(), q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"google: failed to get token: %v\", err)\n\t}\n\n\treturn c.createIdentity(r.Context(), identity, s, token)\n}\n\nfunc (c *googleConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\tt := &oauth2.Token{\n\t\tRefreshToken: string(identity.ConnectorData),\n\t\tExpiry:       time.Now().Add(-time.Hour),\n\t}\n\ttoken, err := c.oauth2Config.TokenSource(ctx, t).Token()\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"google: failed to get token: %v\", err)\n\t}\n\n\treturn c.createIdentity(ctx, identity, s, token)\n}\n\nfunc (c *googleConnector) createIdentity(ctx context.Context, identity connector.Identity, s connector.Scopes, token *oauth2.Token) (connector.Identity, error) {\n\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\tif !ok {\n\t\treturn identity, errors.New(\"google: no id_token in token response\")\n\t}\n\tidToken, err := c.verifier.Verify(ctx, rawIDToken)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"google: failed to verify ID Token: %v\", err)\n\t}\n\n\tvar claims struct {\n\t\tUsername      string `json:\"name\"`\n\t\tEmail         string `json:\"email\"`\n\t\tEmailVerified bool   `json:\"email_verified\"`\n\t\tHostedDomain  string `json:\"hd\"`\n\t}\n\tif err := idToken.Claims(&claims); err != nil {\n\t\treturn identity, fmt.Errorf(\"oidc: failed to decode claims: %v\", err)\n\t}\n\n\tif len(c.hostedDomains) > 0 {\n\t\tfound := false\n\t\tfor _, domain := range c.hostedDomains {\n\t\t\tif claims.HostedDomain == domain {\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif !found {\n\t\t\treturn identity, fmt.Errorf(\"oidc: unexpected hd claim %v\", claims.HostedDomain)\n\t\t}\n\t}\n\n\tvar groups []string\n\tif s.Groups && len(c.adminSrv) > 0 {\n\t\tcheckedGroups := make(map[string]struct{})\n\t\tgroups, err = c.getGroups(claims.Email, c.fetchTransitiveGroupMembership, checkedGroups)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"google: could not retrieve groups: %v\", err)\n\t\t}\n\n\t\tif len(c.groups) > 0 {\n\t\t\tgroups = pkg_groups.Filter(groups, c.groups)\n\t\t\tif len(groups) == 0 {\n\t\t\t\treturn identity, fmt.Errorf(\"google: user %q is not in any of the required groups\", claims.Username)\n\t\t\t}\n\t\t}\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:        idToken.Subject,\n\t\tUsername:      claims.Username,\n\t\tEmail:         claims.Email,\n\t\tEmailVerified: claims.EmailVerified,\n\t\tConnectorData: []byte(token.RefreshToken),\n\t\tGroups:        groups,\n\t}\n\treturn identity, nil\n}\n\n// getGroups creates a connection to the admin directory service and lists\n// all groups the user is a member of\nfunc (c *googleConnector) getGroups(email string, fetchTransitiveGroupMembership bool, checkedGroups map[string]struct{}) ([]string, error) {\n\tvar userGroups []string\n\tvar err error\n\tgroupsList := &admin.Groups{}\n\tdomain := c.extractDomainFromEmail(email)\n\tadminSrv, err := c.findAdminService(domain)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor {\n\t\tgroupsList, err = adminSrv.Groups.List().\n\t\t\tUserKey(email).PageToken(groupsList.NextPageToken).Do()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"could not list groups: %v\", err)\n\t\t}\n\n\t\tfor _, group := range groupsList.Groups {\n\t\t\tif _, exists := checkedGroups[group.Email]; exists {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tcheckedGroups[group.Email] = struct{}{}\n\t\t\t// TODO (joelspeed): Make desired group key configurable\n\t\t\tuserGroups = append(userGroups, group.Email)\n\n\t\t\tif !fetchTransitiveGroupMembership {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// getGroups takes a user's email/alias as well as a group's email/alias\n\t\t\ttransitiveGroups, err := c.getGroups(group.Email, fetchTransitiveGroupMembership, checkedGroups)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"could not list transitive groups: %v\", err)\n\t\t\t}\n\n\t\t\tuserGroups = append(userGroups, transitiveGroups...)\n\t\t}\n\n\t\tif groupsList.NextPageToken == \"\" {\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn userGroups, nil\n}\n\nfunc (c *googleConnector) findAdminService(domain string) (*admin.Service, error) {\n\tadminSrv, ok := c.adminSrv[domain]\n\tif !ok {\n\t\tadminSrv, ok = c.adminSrv[wildcardDomainToAdminEmail]\n\t\tc.logger.Debug(\"using wildcard admin email to fetch groups\", \"admin_email\", c.domainToAdminEmail[wildcardDomainToAdminEmail])\n\t}\n\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"unable to find super admin email, domainToAdminEmail for domain: %s not set, %s is also empty\", domain, wildcardDomainToAdminEmail)\n\t}\n\n\treturn adminSrv, nil\n}\n\n// extracts the domain name from an email input. If the email is valid, it returns the domain name after the \"@\" symbol.\n// However, in the case of a broken or invalid email, it returns a wildcard symbol.\nfunc (c *googleConnector) extractDomainFromEmail(email string) string {\n\tat := strings.LastIndex(email, \"@\")\n\tif at >= 0 {\n\t\t_, domain := email[:at], email[at+1:]\n\n\t\treturn domain\n\t}\n\n\treturn wildcardDomainToAdminEmail\n}\n\n// getCredentialsFromFilePath reads and returns the service account credentials from the file at the provided path.\n// If an error occurs during the read, it is returned.\nfunc getCredentialsFromFilePath(serviceAccountFilePath string) ([]byte, error) {\n\tjsonCredentials, err := os.ReadFile(serviceAccountFilePath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error reading credentials from file: %v\", err)\n\t}\n\treturn jsonCredentials, nil\n}\n\n// getCredentialsFromDefault retrieves the application's default credentials.\n// If the default credential is empty, it attempts to create a new service with metadata credentials.\n// If successful, it returns the service and nil error.\n// If unsuccessful, it returns the error and a nil service.\nfunc getCredentialsFromDefault(ctx context.Context, email string, logger *slog.Logger) ([]byte, *admin.Service, error) {\n\tcredential, err := google.FindDefaultCredentials(ctx)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to fetch application default credentials: %w\", err)\n\t}\n\n\tif credential.JSON == nil {\n\t\tlogger.Info(\"JSON is empty, using flow for GCE\")\n\t\tservice, err := createServiceWithMetadataServer(ctx, email, logger)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\t\treturn nil, service, nil\n\t}\n\n\treturn credential.JSON, nil, nil\n}\n\n// createServiceWithMetadataServer creates a new service using metadata server.\n// If an error occurs during the process, it is returned along with a nil service.\nfunc createServiceWithMetadataServer(ctx context.Context, adminEmail string, logger *slog.Logger) (*admin.Service, error) {\n\tserviceAccountEmail, err := metadata.EmailWithContext(ctx, \"default\")\n\tlogger.Info(\"discovered serviceAccountEmail\", \"email\", serviceAccountEmail)\n\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to get service account email from metadata server: %v\", err)\n\t}\n\n\tconfig := impersonate.CredentialsConfig{\n\t\tTargetPrincipal: serviceAccountEmail,\n\t\tScopes:          []string{admin.AdminDirectoryGroupReadonlyScope},\n\t\tLifetime:        0,\n\t\tSubject:         adminEmail,\n\t}\n\n\ttokenSource, err := impersonate.CredentialsTokenSource(ctx, config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to impersonate with %s, error: %v\", adminEmail, err)\n\t}\n\n\treturn admin.NewService(ctx, option.WithHTTPClient(oauth2.NewClient(ctx, tokenSource)))\n}\n\n// createDirectoryService sets up super user impersonation and creates an admin client for calling\n// the google admin api. If no serviceAccountFilePath is defined, the application default credential\n// is used.\nfunc createDirectoryService(serviceAccountFilePath, email string, logger *slog.Logger) (service *admin.Service, err error) {\n\tvar jsonCredentials []byte\n\n\tctx := context.Background()\n\tif serviceAccountFilePath == \"\" {\n\t\tlogger.Warn(\"the application default credential is used since the service account file path is not used\")\n\t\tjsonCredentials, service, err = getCredentialsFromDefault(ctx, email, logger)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif service != nil {\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tjsonCredentials, err = getCredentialsFromFilePath(serviceAccountFilePath)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\tconfig, err := google.JWTConfigFromJSON(jsonCredentials, admin.AdminDirectoryGroupReadonlyScope)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to parse client secret file to config: %v\", err)\n\t}\n\n\t// Only attempt impersonation when there is a user configured\n\tif email != \"\" {\n\t\tconfig.Subject = email\n\t}\n\n\treturn admin.NewService(ctx, option.WithHTTPClient(config.Client(ctx)))\n}\n"
  },
  {
    "path": "connector/google/google_test.go",
    "content": "package google\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\tadmin \"google.golang.org/api/admin/directory/v1\"\n\t\"google.golang.org/api/option\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nvar (\n\t//\t\t\tgroups_0\n\t//\t\t┌───────┤\n\t//  groups_2 groups_1\n\t//\t\t│\t\t├────────┐\n\t//\t\t└──\t user_1\t  user_2\n\ttestGroups = map[string][]*admin.Group{\n\t\t\"user_1@dexidp.com\":   {{Email: \"groups_2@dexidp.com\"}, {Email: \"groups_1@dexidp.com\"}},\n\t\t\"user_2@dexidp.com\":   {{Email: \"groups_1@dexidp.com\"}},\n\t\t\"groups_1@dexidp.com\": {{Email: \"groups_0@dexidp.com\"}},\n\t\t\"groups_2@dexidp.com\": {{Email: \"groups_0@dexidp.com\"}},\n\t\t\"groups_0@dexidp.com\": {},\n\t}\n\tcallCounter = make(map[string]int)\n)\n\nfunc testSetup() *httptest.Server {\n\tmux := http.NewServeMux()\n\n\tmux.HandleFunc(\"/admin/directory/v1/groups/\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tuserKey := r.URL.Query().Get(\"userKey\")\n\t\tif groups, ok := testGroups[userKey]; ok {\n\t\t\tjson.NewEncoder(w).Encode(admin.Groups{Groups: groups})\n\t\t\tcallCounter[userKey]++\n\t\t}\n\t})\n\n\treturn httptest.NewServer(mux)\n}\n\nfunc newConnector(config *Config) (*googleConnector, error) {\n\tlog := slog.New(slog.DiscardHandler)\n\tconn, err := config.Open(\"id\", log)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgoogleConn, ok := conn.(*googleConnector)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"failed to convert to googleConnector\")\n\t}\n\treturn googleConn, nil\n}\n\nfunc tempServiceAccountKey() (string, error) {\n\tfd, err := os.CreateTemp(\"\", \"google_service_account_key\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer fd.Close()\n\terr = json.NewEncoder(fd).Encode(map[string]string{\n\t\t\"type\":                 \"service_account\",\n\t\t\"project_id\":           \"sample-project\",\n\t\t\"private_key_id\":       \"sample-key-id\",\n\t\t\"private_key\":          \"-----BEGIN PRIVATE KEY-----\\nsample-key\\n-----END PRIVATE KEY-----\\n\",\n\t\t\"client_id\":            \"sample-client-id\",\n\t\t\"client_x509_cert_url\": \"localhost\",\n\t})\n\treturn fd.Name(), err\n}\n\nfunc TestOpen(t *testing.T) {\n\tts := testSetup()\n\tdefer ts.Close()\n\n\ttype testCase struct {\n\t\tconfig      *Config\n\t\texpectedErr string\n\n\t\t// string to set in GOOGLE_APPLICATION_CREDENTIALS. As local development environments can\n\t\t// already contain ADC, test cases will be built upon this setting this env variable\n\t\tadc string\n\t}\n\n\tserviceAccountFilePath, err := tempServiceAccountKey()\n\tassert.Nil(t, err)\n\n\tfor name, reference := range map[string]testCase{\n\t\t\"missing_admin_email\": {\n\t\t\tconfig: &Config{\n\t\t\t\tClientID:               \"testClient\",\n\t\t\t\tClientSecret:           \"testSecret\",\n\t\t\t\tRedirectURI:            ts.URL + \"/callback\",\n\t\t\t\tScopes:                 []string{\"openid\", \"groups\"},\n\t\t\t\tServiceAccountFilePath: serviceAccountFilePath,\n\t\t\t},\n\t\t\texpectedErr: \"requires the domainToAdminEmail\",\n\t\t},\n\t\t\"service_account_key_not_found\": {\n\t\t\tconfig: &Config{\n\t\t\t\tClientID:               \"testClient\",\n\t\t\t\tClientSecret:           \"testSecret\",\n\t\t\t\tRedirectURI:            ts.URL + \"/callback\",\n\t\t\t\tScopes:                 []string{\"openid\", \"groups\"},\n\t\t\t\tDomainToAdminEmail:     map[string]string{\"*\": \"foo@bar.com\"},\n\t\t\t\tServiceAccountFilePath: \"not_found.json\",\n\t\t\t},\n\t\t\texpectedErr: \"error reading credentials\",\n\t\t},\n\t\t\"service_account_key_valid\": {\n\t\t\tconfig: &Config{\n\t\t\t\tClientID:               \"testClient\",\n\t\t\t\tClientSecret:           \"testSecret\",\n\t\t\t\tRedirectURI:            ts.URL + \"/callback\",\n\t\t\t\tScopes:                 []string{\"openid\", \"groups\"},\n\t\t\t\tDomainToAdminEmail:     map[string]string{\"bar.com\": \"foo@bar.com\"},\n\t\t\t\tServiceAccountFilePath: serviceAccountFilePath,\n\t\t\t},\n\t\t\texpectedErr: \"\",\n\t\t},\n\t\t\"adc\": {\n\t\t\tconfig: &Config{\n\t\t\t\tClientID:           \"testClient\",\n\t\t\t\tClientSecret:       \"testSecret\",\n\t\t\t\tRedirectURI:        ts.URL + \"/callback\",\n\t\t\t\tScopes:             []string{\"openid\", \"groups\"},\n\t\t\t\tDomainToAdminEmail: map[string]string{\"*\": \"foo@bar.com\"},\n\t\t\t},\n\t\t\tadc:         serviceAccountFilePath,\n\t\t\texpectedErr: \"\",\n\t\t},\n\t\t\"adc_priority\": {\n\t\t\tconfig: &Config{\n\t\t\t\tClientID:               \"testClient\",\n\t\t\t\tClientSecret:           \"testSecret\",\n\t\t\t\tRedirectURI:            ts.URL + \"/callback\",\n\t\t\t\tScopes:                 []string{\"openid\", \"groups\"},\n\t\t\t\tDomainToAdminEmail:     map[string]string{\"*\": \"foo@bar.com\"},\n\t\t\t\tServiceAccountFilePath: serviceAccountFilePath,\n\t\t\t},\n\t\t\tadc:         \"/dev/null\",\n\t\t\texpectedErr: \"\",\n\t\t},\n\t} {\n\t\treference := reference\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\n\t\t\tos.Setenv(\"GOOGLE_APPLICATION_CREDENTIALS\", reference.adc)\n\t\t\tconn, err := newConnector(reference.config)\n\n\t\t\tif reference.expectedErr == \"\" {\n\t\t\t\tassert.Nil(err)\n\t\t\t\tassert.NotNil(conn)\n\t\t\t} else {\n\t\t\t\tassert.ErrorContains(err, reference.expectedErr)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetGroups(t *testing.T) {\n\tts := testSetup()\n\tdefer ts.Close()\n\n\tserviceAccountFilePath, err := tempServiceAccountKey()\n\tassert.Nil(t, err)\n\n\tos.Setenv(\"GOOGLE_APPLICATION_CREDENTIALS\", serviceAccountFilePath)\n\tconn, err := newConnector(&Config{\n\t\tClientID:           \"testClient\",\n\t\tClientSecret:       \"testSecret\",\n\t\tRedirectURI:        ts.URL + \"/callback\",\n\t\tScopes:             []string{\"openid\", \"groups\"},\n\t\tDomainToAdminEmail: map[string]string{\"*\": \"admin@dexidp.com\"},\n\t})\n\tassert.Nil(t, err)\n\n\tconn.adminSrv[wildcardDomainToAdminEmail], err = admin.NewService(context.Background(), option.WithoutAuthentication(), option.WithEndpoint(ts.URL))\n\tassert.Nil(t, err)\n\ttype testCase struct {\n\t\tuserKey                        string\n\t\tfetchTransitiveGroupMembership bool\n\t\tshouldErr                      bool\n\t\texpectedGroups                 []string\n\t}\n\n\tfor name, testCase := range map[string]testCase{\n\t\t\"user1_non_transitive_lookup\": {\n\t\t\tuserKey:                        \"user_1@dexidp.com\",\n\t\t\tfetchTransitiveGroupMembership: false,\n\t\t\tshouldErr:                      false,\n\t\t\texpectedGroups:                 []string{\"groups_1@dexidp.com\", \"groups_2@dexidp.com\"},\n\t\t},\n\t\t\"user1_transitive_lookup\": {\n\t\t\tuserKey:                        \"user_1@dexidp.com\",\n\t\t\tfetchTransitiveGroupMembership: true,\n\t\t\tshouldErr:                      false,\n\t\t\texpectedGroups:                 []string{\"groups_0@dexidp.com\", \"groups_1@dexidp.com\", \"groups_2@dexidp.com\"},\n\t\t},\n\t\t\"user2_non_transitive_lookup\": {\n\t\t\tuserKey:                        \"user_2@dexidp.com\",\n\t\t\tfetchTransitiveGroupMembership: false,\n\t\t\tshouldErr:                      false,\n\t\t\texpectedGroups:                 []string{\"groups_1@dexidp.com\"},\n\t\t},\n\t\t\"user2_transitive_lookup\": {\n\t\t\tuserKey:                        \"user_2@dexidp.com\",\n\t\t\tfetchTransitiveGroupMembership: true,\n\t\t\tshouldErr:                      false,\n\t\t\texpectedGroups:                 []string{\"groups_0@dexidp.com\", \"groups_1@dexidp.com\"},\n\t\t},\n\t} {\n\t\ttestCase := testCase\n\t\tcallCounter = map[string]int{}\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\t\t\tlookup := make(map[string]struct{})\n\n\t\t\tgroups, err := conn.getGroups(testCase.userKey, testCase.fetchTransitiveGroupMembership, lookup)\n\t\t\tif testCase.shouldErr {\n\t\t\t\tassert.NotNil(err)\n\t\t\t} else {\n\t\t\t\tassert.Nil(err)\n\t\t\t}\n\t\t\tassert.ElementsMatch(testCase.expectedGroups, groups)\n\t\t\tt.Logf(\"[%s] Amount of API calls per userKey: %+v\\n\", t.Name(), callCounter)\n\t\t})\n\t}\n}\n\nfunc TestDomainToAdminEmailConfig(t *testing.T) {\n\tts := testSetup()\n\tdefer ts.Close()\n\n\tserviceAccountFilePath, err := tempServiceAccountKey()\n\tassert.Nil(t, err)\n\n\tos.Setenv(\"GOOGLE_APPLICATION_CREDENTIALS\", serviceAccountFilePath)\n\tconn, err := newConnector(&Config{\n\t\tClientID:           \"testClient\",\n\t\tClientSecret:       \"testSecret\",\n\t\tRedirectURI:        ts.URL + \"/callback\",\n\t\tScopes:             []string{\"openid\", \"groups\"},\n\t\tDomainToAdminEmail: map[string]string{\"dexidp.com\": \"admin@dexidp.com\"},\n\t})\n\tassert.Nil(t, err)\n\n\tconn.adminSrv[\"dexidp.com\"], err = admin.NewService(context.Background(), option.WithoutAuthentication(), option.WithEndpoint(ts.URL))\n\tassert.Nil(t, err)\n\ttype testCase struct {\n\t\tuserKey     string\n\t\texpectedErr string\n\t}\n\n\tfor name, testCase := range map[string]testCase{\n\t\t\"correct_user_request\": {\n\t\t\tuserKey:     \"user_1@dexidp.com\",\n\t\t\texpectedErr: \"\",\n\t\t},\n\t\t\"wrong_user_request\": {\n\t\t\tuserKey:     \"user_1@foo.bar\",\n\t\t\texpectedErr: \"unable to find super admin email\",\n\t\t},\n\t\t\"wrong_connector_response\": {\n\t\t\tuserKey:     \"user_1_foo.bar\",\n\t\t\texpectedErr: \"unable to find super admin email\",\n\t\t},\n\t} {\n\t\ttestCase := testCase\n\t\tcallCounter = map[string]int{}\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\t\t\tlookup := make(map[string]struct{})\n\n\t\t\t_, err := conn.getGroups(testCase.userKey, true, lookup)\n\t\t\tif testCase.expectedErr != \"\" {\n\t\t\t\tassert.ErrorContains(err, testCase.expectedErr)\n\t\t\t} else {\n\t\t\t\tassert.Nil(err)\n\t\t\t}\n\t\t\tt.Logf(\"[%s] Amount of API calls per userKey: %+v\\n\", t.Name(), callCounter)\n\t\t})\n\t}\n}\n\nvar gceMetadataFlags = map[string]bool{\n\t\"failOnEmailRequest\": false,\n}\n\nfunc mockGCEMetadataServer() *httptest.Server {\n\tmux := http.NewServeMux()\n\n\tmux.HandleFunc(\"/computeMetadata/v1/instance/service-accounts/default/email\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tif gceMetadataFlags[\"failOnEmailRequest\"] {\n\t\t\tw.WriteHeader(http.StatusBadRequest)\n\t\t}\n\t\tjson.NewEncoder(w).Encode(\"my-service-account@example-project.iam.gserviceaccount.com\")\n\t})\n\tmux.HandleFunc(\"/computeMetadata/v1/instance/service-accounts/default/token\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(struct {\n\t\t\tAccessToken  string `json:\"access_token\"`\n\t\t\tExpiresInSec int    `json:\"expires_in\"`\n\t\t\tTokenType    string `json:\"token_type\"`\n\t\t}{\n\t\t\tAccessToken:  \"my-example.token\",\n\t\t\tExpiresInSec: 3600,\n\t\t\tTokenType:    \"Bearer\",\n\t\t})\n\t})\n\n\treturn httptest.NewServer(mux)\n}\n\nfunc TestGCEWorkloadIdentity(t *testing.T) {\n\tts := testSetup()\n\tdefer ts.Close()\n\n\tmetadataServer := mockGCEMetadataServer()\n\tdefer metadataServer.Close()\n\tmetadataServerHost := strings.Replace(metadataServer.URL, \"http://\", \"\", 1)\n\n\tos.Setenv(\"GCE_METADATA_HOST\", metadataServerHost)\n\tos.Setenv(\"GOOGLE_APPLICATION_CREDENTIALS\", \"\")\n\tos.Setenv(\"HOME\", \"/tmp\")\n\n\tgceMetadataFlags[\"failOnEmailRequest\"] = true\n\t_, err := newConnector(&Config{\n\t\tClientID:           \"testClient\",\n\t\tClientSecret:       \"testSecret\",\n\t\tRedirectURI:        ts.URL + \"/callback\",\n\t\tScopes:             []string{\"openid\", \"groups\"},\n\t\tDomainToAdminEmail: map[string]string{\"dexidp.com\": \"admin@dexidp.com\"},\n\t})\n\tassert.Error(t, err)\n\n\tgceMetadataFlags[\"failOnEmailRequest\"] = false\n\tconn, err := newConnector(&Config{\n\t\tClientID:           \"testClient\",\n\t\tClientSecret:       \"testSecret\",\n\t\tRedirectURI:        ts.URL + \"/callback\",\n\t\tScopes:             []string{\"openid\", \"groups\"},\n\t\tDomainToAdminEmail: map[string]string{\"dexidp.com\": \"admin@dexidp.com\"},\n\t})\n\tassert.Nil(t, err)\n\n\tconn.adminSrv[\"dexidp.com\"], err = admin.NewService(context.Background(), option.WithoutAuthentication(), option.WithEndpoint(ts.URL))\n\tassert.Nil(t, err)\n\ttype testCase struct {\n\t\tuserKey     string\n\t\texpectedErr string\n\t}\n\n\tfor name, testCase := range map[string]testCase{\n\t\t\"correct_user_request\": {\n\t\t\tuserKey:     \"user_1@dexidp.com\",\n\t\t\texpectedErr: \"\",\n\t\t},\n\t\t\"wrong_user_request\": {\n\t\t\tuserKey:     \"user_1@foo.bar\",\n\t\t\texpectedErr: \"unable to find super admin email\",\n\t\t},\n\t\t\"wrong_connector_response\": {\n\t\t\tuserKey:     \"user_1_foo.bar\",\n\t\t\texpectedErr: \"unable to find super admin email\",\n\t\t},\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\t\t\tlookup := make(map[string]struct{})\n\n\t\t\t_, err := conn.getGroups(testCase.userKey, true, lookup)\n\t\t\tif testCase.expectedErr != \"\" {\n\t\t\t\tassert.ErrorContains(err, testCase.expectedErr)\n\t\t\t} else {\n\t\t\t\tassert.Nil(err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPromptTypeConfig(t *testing.T) {\n\tpromptTypeLogin := \"login\"\n\tcases := []struct {\n\t\tname                    string\n\t\tpromptType              *string\n\t\texpectedPromptTypeValue string\n\t}{\n\t\t{\n\t\t\tname:                    \"prompt type is nil\",\n\t\t\tpromptType:              nil,\n\t\t\texpectedPromptTypeValue: \"consent\",\n\t\t},\n\t\t{\n\t\t\tname:                    \"prompt type is empty\",\n\t\t\tpromptType:              new(string),\n\t\t\texpectedPromptTypeValue: \"\",\n\t\t},\n\t\t{\n\t\t\tname:                    \"prompt type is set\",\n\t\t\tpromptType:              &promptTypeLogin,\n\t\t\texpectedPromptTypeValue: \"login\",\n\t\t},\n\t}\n\n\tts := testSetup()\n\tdefer ts.Close()\n\n\tserviceAccountFilePath, err := tempServiceAccountKey()\n\tassert.Nil(t, err)\n\n\tos.Setenv(\"GOOGLE_APPLICATION_CREDENTIALS\", serviceAccountFilePath)\n\n\tfor _, test := range cases {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tconn, err := newConnector(&Config{\n\t\t\t\tClientID:           \"testClient\",\n\t\t\t\tClientSecret:       \"testSecret\",\n\t\t\t\tRedirectURI:        ts.URL + \"/callback\",\n\t\t\t\tScopes:             []string{\"openid\", \"groups\", \"offline_access\"},\n\t\t\t\tDomainToAdminEmail: map[string]string{\"dexidp.com\": \"admin@dexidp.com\"},\n\t\t\t\tPromptType:         test.promptType,\n\t\t\t})\n\n\t\t\tassert.Nil(t, err)\n\t\t\tassert.Equal(t, test.expectedPromptTypeValue, conn.promptType)\n\n\t\t\tloginURL, _, err := conn.LoginURL(connector.Scopes{OfflineAccess: true}, ts.URL+\"/callback\", \"state\")\n\t\t\tassert.Nil(t, err)\n\n\t\t\turlp, err := url.Parse(loginURL)\n\t\t\tassert.Nil(t, err)\n\n\t\t\tassert.Equal(t, test.expectedPromptTypeValue, urlp.Query().Get(\"prompt\"))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "connector/keystone/keystone.go",
    "content": "// Package keystone provides authentication strategy using Keystone.\npackage keystone\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\n\t\"github.com/google/uuid\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nvar (\n\t_ connector.PasswordConnector = (*conn)(nil)\n\t_ connector.RefreshConnector  = (*conn)(nil)\n)\n\ntype conn struct {\n\tDomain        domainKeystone\n\tHost          string\n\tAdminUsername string\n\tAdminPassword string\n\tclient        *http.Client\n\tLogger        *slog.Logger\n}\n\ntype userKeystone struct {\n\tDomain domainKeystone `json:\"domain\"`\n\tID     string         `json:\"id\"`\n\tName   string         `json:\"name\"`\n}\n\ntype domainKeystone struct {\n\tID   string `json:\"id,omitempty\"`\n\tName string `json:\"name,omitempty\"`\n}\n\n// Config holds the configuration parameters for Keystone connector.\n// Keystone should expose API v3\n// An example config:\n//\n//\tconnectors:\n//\t\ttype: keystone\n//\t\tid: keystone\n//\t\tname: Keystone\n//\t\tconfig:\n//\t\t\tkeystoneHost: http://example:5000\n//\t\t\tdomain: default\n//\t\t\tkeystoneUsername: demo\n//\t\t\tkeystonePassword: DEMO_PASS\ntype Config struct {\n\tDomain        string `json:\"domain\"`\n\tHost          string `json:\"keystoneHost\"`\n\tAdminUsername string `json:\"keystoneUsername\"`\n\tAdminPassword string `json:\"keystonePassword\"`\n}\n\ntype loginRequestData struct {\n\tauth `json:\"auth\"`\n}\n\ntype auth struct {\n\tIdentity identity `json:\"identity\"`\n}\n\ntype identity struct {\n\tMethods  []string `json:\"methods\"`\n\tPassword password `json:\"password\"`\n}\n\ntype password struct {\n\tUser user `json:\"user\"`\n}\n\ntype user struct {\n\tName     string         `json:\"name\"`\n\tDomain   domainKeystone `json:\"domain\"`\n\tPassword string         `json:\"password\"`\n}\n\ntype token struct {\n\tUser userKeystone `json:\"user\"`\n}\n\ntype tokenResponse struct {\n\tToken token `json:\"token\"`\n}\n\ntype group struct {\n\tID   string `json:\"id\"`\n\tName string `json:\"name\"`\n}\n\ntype groupsResponse struct {\n\tGroups []group `json:\"groups\"`\n}\n\ntype userResponse struct {\n\tUser struct {\n\t\tName  string `json:\"name\"`\n\t\tEmail string `json:\"email\"`\n\t\tID    string `json:\"id\"`\n\t} `json:\"user\"`\n}\n\n// Open returns an authentication strategy using Keystone.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\t_, err := uuid.Parse(c.Domain)\n\tvar domain domainKeystone\n\t// check if the supplied domain is a UUID or the special \"default\" value\n\t// which is treated as an ID and not a name\n\tif err == nil || c.Domain == \"default\" {\n\t\tdomain = domainKeystone{\n\t\t\tID: c.Domain,\n\t\t}\n\t} else {\n\t\tdomain = domainKeystone{\n\t\t\tName: c.Domain,\n\t\t}\n\t}\n\n\treturn &conn{\n\t\tDomain:        domain,\n\t\tHost:          c.Host,\n\t\tAdminUsername: c.AdminUsername,\n\t\tAdminPassword: c.AdminPassword,\n\t\tLogger:        logger.With(slog.Group(\"connector\", \"type\", \"keystone\", \"id\", id)),\n\t\tclient:        http.DefaultClient,\n\t}, nil\n}\n\nfunc (p *conn) Close() error { return nil }\n\nfunc (p *conn) Login(ctx context.Context, scopes connector.Scopes, username, password string) (identity connector.Identity, validPassword bool, err error) {\n\tresp, err := p.getTokenResponse(ctx, username, password)\n\tif err != nil {\n\t\treturn identity, false, fmt.Errorf(\"keystone: error %v\", err)\n\t}\n\tif resp.StatusCode/100 != 2 {\n\t\treturn identity, false, fmt.Errorf(\"keystone login: error %v\", resp.StatusCode)\n\t}\n\tif resp.StatusCode != 201 {\n\t\treturn identity, false, nil\n\t}\n\ttoken := resp.Header.Get(\"X-Subject-Token\")\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn identity, false, err\n\t}\n\tdefer resp.Body.Close()\n\ttokenResp := new(tokenResponse)\n\terr = json.Unmarshal(data, &tokenResp)\n\tif err != nil {\n\t\treturn identity, false, fmt.Errorf(\"keystone: invalid token response: %v\", err)\n\t}\n\tif scopes.Groups {\n\t\tgroups, err := p.getUserGroups(ctx, tokenResp.Token.User.ID, token)\n\t\tif err != nil {\n\t\t\treturn identity, false, err\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\tidentity.Username = username\n\tidentity.UserID = tokenResp.Token.User.ID\n\n\tuser, err := p.getUser(ctx, tokenResp.Token.User.ID, token)\n\tif err != nil {\n\t\treturn identity, false, err\n\t}\n\tif user.User.Email != \"\" {\n\t\tidentity.Email = user.User.Email\n\t\tidentity.EmailVerified = true\n\t}\n\n\treturn identity, true, nil\n}\n\nfunc (p *conn) Prompt() string { return \"username\" }\n\nfunc (p *conn) Refresh(\n\tctx context.Context, scopes connector.Scopes, identity connector.Identity,\n) (connector.Identity, error) {\n\ttoken, err := p.getAdminToken(ctx)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"keystone: failed to obtain admin token: %v\", err)\n\t}\n\tok, err := p.checkIfUserExists(ctx, identity.UserID, token)\n\tif err != nil {\n\t\treturn identity, err\n\t}\n\tif !ok {\n\t\treturn identity, fmt.Errorf(\"keystone: user %q does not exist\", identity.UserID)\n\t}\n\tif scopes.Groups {\n\t\tgroups, err := p.getUserGroups(ctx, identity.UserID, token)\n\t\tif err != nil {\n\t\t\treturn identity, err\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\treturn identity, nil\n}\n\nfunc (p *conn) getTokenResponse(ctx context.Context, username, pass string) (response *http.Response, err error) {\n\tjsonData := loginRequestData{\n\t\tauth: auth{\n\t\t\tIdentity: identity{\n\t\t\t\tMethods: []string{\"password\"},\n\t\t\t\tPassword: password{\n\t\t\t\t\tUser: user{\n\t\t\t\t\t\tName:     username,\n\t\t\t\t\t\tDomain:   p.Domain,\n\t\t\t\t\t\tPassword: pass,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\tjsonValue, err := json.Marshal(jsonData)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// https://developer.openstack.org/api-ref/identity/v3/#password-authentication-with-unscoped-authorization\n\tauthTokenURL := p.Host + \"/v3/auth/tokens/\"\n\treq, err := http.NewRequest(\"POST\", authTokenURL, bytes.NewBuffer(jsonValue))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq = req.WithContext(ctx)\n\n\treturn p.client.Do(req)\n}\n\nfunc (p *conn) getAdminToken(ctx context.Context) (string, error) {\n\tresp, err := p.getTokenResponse(ctx, p.AdminUsername, p.AdminPassword)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer resp.Body.Close()\n\n\ttoken := resp.Header.Get(\"X-Subject-Token\")\n\treturn token, nil\n}\n\nfunc (p *conn) checkIfUserExists(ctx context.Context, userID string, token string) (bool, error) {\n\tuser, err := p.getUser(ctx, userID, token)\n\treturn user != nil, err\n}\n\nfunc (p *conn) getUser(ctx context.Context, userID string, token string) (*userResponse, error) {\n\t// https://developer.openstack.org/api-ref/identity/v3/#show-user-details\n\tuserURL := p.Host + \"/v3/users/\" + userID\n\treq, err := http.NewRequest(\"GET\", userURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treq.Header.Set(\"X-Auth-Token\", token)\n\treq = req.WithContext(ctx)\n\tresp, err := p.client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != 200 {\n\t\treturn nil, err\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuser := userResponse{}\n\terr = json.Unmarshal(data, &user)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &user, nil\n}\n\nfunc (p *conn) getUserGroups(ctx context.Context, userID string, token string) ([]string, error) {\n\t// https://developer.openstack.org/api-ref/identity/v3/#list-groups-to-which-a-user-belongs\n\tgroupsURL := p.Host + \"/v3/users/\" + userID + \"/groups\"\n\treq, err := http.NewRequest(\"GET\", groupsURL, nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"X-Auth-Token\", token)\n\treq = req.WithContext(ctx)\n\tresp, err := p.client.Do(req)\n\tif err != nil {\n\t\tp.Logger.Error(\"error while fetching user groups\", \"user_id\", userID, \"err\", err)\n\t\treturn nil, err\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\n\tgroupsResp := new(groupsResponse)\n\n\terr = json.Unmarshal(data, &groupsResp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgroups := make([]string, len(groupsResp.Groups))\n\tfor i, group := range groupsResp.Groups {\n\t\tgroups[i] = group.Name\n\t}\n\treturn groups, nil\n}\n"
  },
  {
    "path": "connector/keystone/keystone_test.go",
    "content": "package keystone\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nconst (\n\tinvalidPass = \"WRONG_PASS\"\n\n\ttestUser          = \"test_user\"\n\ttestPass          = \"test_pass\"\n\ttestEmail         = \"test@example.com\"\n\ttestGroup         = \"test_group\"\n\ttestDomainAltName = \"altdomain\"\n\ttestDomainID      = \"default\"\n\ttestDomainName    = \"Default\"\n)\n\nvar (\n\tkeystoneURL      = \"\"\n\tkeystoneAdminURL = \"\"\n\tadminUser        = \"\"\n\tadminPass        = \"\"\n\tauthTokenURL     = \"\"\n\tusersURL         = \"\"\n\tgroupsURL        = \"\"\n\tdomainsURL       = \"\"\n)\n\ntype userReq struct {\n\tName     string   `json:\"name\"`\n\tEmail    string   `json:\"email\"`\n\tEnabled  bool     `json:\"enabled\"`\n\tPassword string   `json:\"password\"`\n\tRoles    []string `json:\"roles\"`\n\tDomainID string   `json:\"domain_id,omitempty\"`\n}\n\ntype domainResponse struct {\n\tDomain domainKeystone `json:\"domain\"`\n}\n\ntype domainsResponse struct {\n\tDomains []domainKeystone `json:\"domains\"`\n}\n\ntype groupResponse struct {\n\tGroup struct {\n\t\tID string `json:\"id\"`\n\t} `json:\"group\"`\n}\n\nfunc getAdminToken(t *testing.T, adminName, adminPass string) (token, id string) {\n\tt.Helper()\n\tjsonData := loginRequestData{\n\t\tauth: auth{\n\t\t\tIdentity: identity{\n\t\t\t\tMethods: []string{\"password\"},\n\t\t\t\tPassword: password{\n\t\t\t\t\tUser: user{\n\t\t\t\t\t\tName:     adminName,\n\t\t\t\t\t\tDomain:   domainKeystone{ID: testDomainID},\n\t\t\t\t\t\tPassword: adminPass,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tbody, err := json.Marshal(jsonData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", authTokenURL, bytes.NewBuffer(body))\n\tif err != nil {\n\t\tt.Fatalf(\"keystone: failed to obtain admin token: %v\\n\", err)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttoken = resp.Header.Get(\"X-Subject-Token\")\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\ttokenResp := new(tokenResponse)\n\terr = json.Unmarshal(data, &tokenResp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn token, tokenResp.Token.User.ID\n}\n\nfunc getOrCreateDomain(t *testing.T, token, domainName string) string {\n\tt.Helper()\n\n\tdomainSearchURL := domainsURL + \"?name=\" + domainName\n\treqGet, err := http.NewRequest(\"GET\", domainSearchURL, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treqGet.Header.Set(\"X-Auth-Token\", token)\n\treqGet.Header.Add(\"Content-Type\", \"application/json\")\n\trespGet, err := http.DefaultClient.Do(reqGet)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdataGet, err := io.ReadAll(respGet.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer respGet.Body.Close()\n\n\tdomainsResp := new(domainsResponse)\n\terr = json.Unmarshal(dataGet, &domainsResp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif len(domainsResp.Domains) >= 1 {\n\t\treturn domainsResp.Domains[0].ID\n\t}\n\n\tcreateDomainData := map[string]interface{}{\n\t\t\"domain\": map[string]interface{}{\n\t\t\t\"name\":    domainName,\n\t\t\t\"enabled\": true,\n\t\t},\n\t}\n\n\tbody, err := json.Marshal(createDomainData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", domainsURL, bytes.NewBuffer(body))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"X-Auth-Token\", token)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif resp.StatusCode != 201 {\n\t\tt.Fatalf(\"failed to create domain %s\", domainName)\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tdomainResp := new(domainResponse)\n\terr = json.Unmarshal(data, &domainResp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn domainResp.Domain.ID\n}\n\nfunc createUser(t *testing.T, token, domainID, userName, userEmail, userPass string) string {\n\tt.Helper()\n\n\tcreateUserData := map[string]interface{}{\n\t\t\"user\": userReq{\n\t\t\tDomainID: domainID,\n\t\t\tName:     userName,\n\t\t\tEmail:    userEmail,\n\t\t\tEnabled:  true,\n\t\t\tPassword: userPass,\n\t\t\tRoles:    []string{\"admin\"},\n\t\t},\n\t}\n\n\tbody, err := json.Marshal(createUserData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", usersURL, bytes.NewBuffer(body))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"X-Auth-Token\", token)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tuserResp := new(userResponse)\n\terr = json.Unmarshal(data, &userResp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn userResp.User.ID\n}\n\n// delete group or user\nfunc deleteResource(t *testing.T, token, id, uri string) {\n\tt.Helper()\n\n\tdeleteURI := uri + id\n\treq, err := http.NewRequest(\"DELETE\", deleteURI, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"error: %v\", err)\n\t}\n\treq.Header.Set(\"X-Auth-Token\", token)\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatalf(\"error: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n}\n\nfunc createGroup(t *testing.T, token, description, name string) string {\n\tt.Helper()\n\n\tcreateGroupData := map[string]interface{}{\n\t\t\"group\": map[string]interface{}{\n\t\t\t\"name\":        name,\n\t\t\t\"description\": description,\n\t\t},\n\t}\n\n\tbody, err := json.Marshal(createGroupData)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", groupsURL, bytes.NewBuffer(body))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treq.Header.Set(\"X-Auth-Token\", token)\n\treq.Header.Add(\"Content-Type\", \"application/json\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer resp.Body.Close()\n\n\tgroupResp := new(groupResponse)\n\terr = json.Unmarshal(data, &groupResp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn groupResp.Group.ID\n}\n\nfunc addUserToGroup(t *testing.T, token, groupID, userID string) error {\n\tt.Helper()\n\turi := groupsURL + groupID + \"/users/\" + userID\n\treq, err := http.NewRequest(\"PUT\", uri, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Header.Set(\"X-Auth-Token\", token)\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\tt.Fatalf(\"error: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\treturn nil\n}\n\nfunc TestIncorrectCredentialsLogin(t *testing.T) {\n\tsetupVariables(t)\n\tc := conn{\n\t\tclient: http.DefaultClient,\n\t\tHost:   keystoneURL, Domain: domainKeystone{ID: testDomainID},\n\t\tAdminUsername: adminUser, AdminPassword: adminPass,\n\t}\n\ts := connector.Scopes{OfflineAccess: true, Groups: true}\n\t_, validPW, err := c.Login(context.Background(), s, adminUser, invalidPass)\n\n\tif validPW {\n\t\tt.Fatal(\"Incorrect password check\")\n\t}\n\n\tif err == nil {\n\t\tt.Fatal(\"Error should be returned when invalid password is provided\")\n\t}\n\n\tif !strings.Contains(err.Error(), \"401\") {\n\t\tt.Fatal(\"Unrecognized error, expecting 401\")\n\t}\n}\n\nfunc TestValidUserLogin(t *testing.T) {\n\tsetupVariables(t)\n\ttoken, _ := getAdminToken(t, adminUser, adminPass)\n\n\ttype tUser struct {\n\t\tcreateDomain bool\n\t\tdomain       domainKeystone\n\t\tusername     string\n\t\temail        string\n\t\tpassword     string\n\t}\n\n\ttype expect struct {\n\t\tusername      string\n\t\temail         string\n\t\tverifiedEmail bool\n\t}\n\n\ttests := []struct {\n\t\tname     string\n\t\tinput    tUser\n\t\texpected expect\n\t}{\n\t\t{\n\t\t\tname: \"test with email address\",\n\t\t\tinput: tUser{\n\t\t\t\tcreateDomain: false,\n\t\t\t\tdomain:       domainKeystone{ID: testDomainID},\n\t\t\t\tusername:     testUser,\n\t\t\t\temail:        testEmail,\n\t\t\t\tpassword:     testPass,\n\t\t\t},\n\t\t\texpected: expect{\n\t\t\t\tusername:      testUser,\n\t\t\t\temail:         testEmail,\n\t\t\t\tverifiedEmail: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"test without email address\",\n\t\t\tinput: tUser{\n\t\t\t\tcreateDomain: false,\n\t\t\t\tdomain:       domainKeystone{ID: testDomainID},\n\t\t\t\tusername:     testUser,\n\t\t\t\temail:        \"\",\n\t\t\t\tpassword:     testPass,\n\t\t\t},\n\t\t\texpected: expect{\n\t\t\t\tusername:      testUser,\n\t\t\t\temail:         \"\",\n\t\t\t\tverifiedEmail: false,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"test with default domain Name\",\n\t\t\tinput: tUser{\n\t\t\t\tcreateDomain: false,\n\t\t\t\tdomain:       domainKeystone{Name: testDomainName},\n\t\t\t\tusername:     testUser,\n\t\t\t\temail:        testEmail,\n\t\t\t\tpassword:     testPass,\n\t\t\t},\n\t\t\texpected: expect{\n\t\t\t\tusername:      testUser,\n\t\t\t\temail:         testEmail,\n\t\t\t\tverifiedEmail: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"test with custom domain Name\",\n\t\t\tinput: tUser{\n\t\t\t\tcreateDomain: true,\n\t\t\t\tdomain:       domainKeystone{Name: testDomainAltName},\n\t\t\t\tusername:     testUser,\n\t\t\t\temail:        testEmail,\n\t\t\t\tpassword:     testPass,\n\t\t\t},\n\t\t\texpected: expect{\n\t\t\t\tusername:      testUser,\n\t\t\t\temail:         testEmail,\n\t\t\t\tverifiedEmail: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"test with custom domain ID\",\n\t\t\tinput: tUser{\n\t\t\t\tcreateDomain: true,\n\t\t\t\tdomain:       domainKeystone{},\n\t\t\t\tusername:     testUser,\n\t\t\t\temail:        testEmail,\n\t\t\t\tpassword:     testPass,\n\t\t\t},\n\t\t\texpected: expect{\n\t\t\t\tusername:      testUser,\n\t\t\t\temail:         testEmail,\n\t\t\t\tverifiedEmail: true,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdomainID := \"\"\n\t\t\tif tt.input.createDomain == true {\n\t\t\t\tdomainID = getOrCreateDomain(t, token, testDomainAltName)\n\t\t\t\tt.Logf(\"getOrCreateDomain ID: %s\\n\", domainID)\n\n\t\t\t\t// if there was nothing set then use the dynamically generated domain ID\n\t\t\t\tif tt.input.domain.ID == \"\" && tt.input.domain.Name == \"\" {\n\t\t\t\t\ttt.input.domain.ID = domainID\n\t\t\t\t}\n\t\t\t}\n\t\t\tuserID := createUser(t, token, domainID, tt.input.username, tt.input.email, tt.input.password)\n\t\t\tdefer deleteResource(t, token, userID, usersURL)\n\n\t\t\tc := conn{\n\t\t\t\tclient: http.DefaultClient,\n\t\t\t\tHost:   keystoneURL, Domain: tt.input.domain,\n\t\t\t\tAdminUsername: adminUser, AdminPassword: adminPass,\n\t\t\t}\n\t\t\ts := connector.Scopes{OfflineAccess: true, Groups: true}\n\t\t\tidentity, validPW, err := c.Login(context.Background(), s, tt.input.username, tt.input.password)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Login failed for user %s: %v\", tt.input.username, err.Error())\n\t\t\t}\n\t\t\tt.Log(identity)\n\t\t\tif identity.Username != tt.expected.username {\n\t\t\t\tt.Fatalf(\"Invalid user. Got: %v. Wanted: %v\", identity.Username, tt.expected.username)\n\t\t\t}\n\t\t\tif identity.UserID == \"\" {\n\t\t\t\tt.Fatalf(\"Didn't get any UserID back\")\n\t\t\t}\n\t\t\tif identity.Email != tt.expected.email {\n\t\t\t\tt.Fatalf(\"Invalid email. Got: %v. Wanted: %v\", identity.Email, tt.expected.email)\n\t\t\t}\n\t\t\tif identity.EmailVerified != tt.expected.verifiedEmail {\n\t\t\t\tt.Fatalf(\"Invalid verifiedEmail. Got: %v. Wanted: %v\", identity.EmailVerified, tt.expected.verifiedEmail)\n\t\t\t}\n\n\t\t\tif !validPW {\n\t\t\t\tt.Fatal(\"Valid password was not accepted\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUseRefreshToken(t *testing.T) {\n\tsetupVariables(t)\n\ttoken, adminID := getAdminToken(t, adminUser, adminPass)\n\tgroupID := createGroup(t, token, \"Test group description\", testGroup)\n\taddUserToGroup(t, token, groupID, adminID)\n\tdefer deleteResource(t, token, groupID, groupsURL)\n\n\tc := conn{\n\t\tclient: http.DefaultClient,\n\t\tHost:   keystoneURL, Domain: domainKeystone{ID: testDomainID},\n\t\tAdminUsername: adminUser, AdminPassword: adminPass,\n\t}\n\ts := connector.Scopes{OfflineAccess: true, Groups: true}\n\n\tidentityLogin, _, err := c.Login(context.Background(), s, adminUser, adminPass)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tidentityRefresh, err := c.Refresh(context.Background(), s, identityLogin)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\texpectEquals(t, 1, len(identityRefresh.Groups))\n\texpectEquals(t, testGroup, identityRefresh.Groups[0])\n}\n\nfunc TestUseRefreshTokenUserDeleted(t *testing.T) {\n\tsetupVariables(t)\n\ttoken, _ := getAdminToken(t, adminUser, adminPass)\n\tuserID := createUser(t, token, \"\", testUser, testEmail, testPass)\n\n\tc := conn{\n\t\tclient: http.DefaultClient,\n\t\tHost:   keystoneURL, Domain: domainKeystone{ID: testDomainID},\n\t\tAdminUsername: adminUser, AdminPassword: adminPass,\n\t}\n\ts := connector.Scopes{OfflineAccess: true, Groups: true}\n\n\tidentityLogin, _, err := c.Login(context.Background(), s, testUser, testPass)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\t_, err = c.Refresh(context.Background(), s, identityLogin)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tdeleteResource(t, token, userID, usersURL)\n\t_, err = c.Refresh(context.Background(), s, identityLogin)\n\n\tif !strings.Contains(err.Error(), \"does not exist\") {\n\t\tt.Errorf(\"unexpected error: %s\", err.Error())\n\t}\n}\n\nfunc TestUseRefreshTokenGroupsChanged(t *testing.T) {\n\tsetupVariables(t)\n\ttoken, _ := getAdminToken(t, adminUser, adminPass)\n\tuserID := createUser(t, token, \"\", testUser, testEmail, testPass)\n\tdefer deleteResource(t, token, userID, usersURL)\n\n\tc := conn{\n\t\tclient: http.DefaultClient,\n\t\tHost:   keystoneURL, Domain: domainKeystone{ID: testDomainID},\n\t\tAdminUsername: adminUser, AdminPassword: adminPass,\n\t}\n\ts := connector.Scopes{OfflineAccess: true, Groups: true}\n\n\tidentityLogin, _, err := c.Login(context.Background(), s, testUser, testPass)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\tidentityRefresh, err := c.Refresh(context.Background(), s, identityLogin)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\texpectEquals(t, 0, len(identityRefresh.Groups))\n\n\tgroupID := createGroup(t, token, \"Test group\", testGroup)\n\taddUserToGroup(t, token, groupID, userID)\n\tdefer deleteResource(t, token, groupID, groupsURL)\n\n\tidentityRefresh, err = c.Refresh(context.Background(), s, identityLogin)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\n\texpectEquals(t, 1, len(identityRefresh.Groups))\n}\n\nfunc TestNoGroupsInScope(t *testing.T) {\n\tsetupVariables(t)\n\ttoken, _ := getAdminToken(t, adminUser, adminPass)\n\tuserID := createUser(t, token, \"\", testUser, testEmail, testPass)\n\tdefer deleteResource(t, token, userID, usersURL)\n\n\tc := conn{\n\t\tclient: http.DefaultClient,\n\t\tHost:   keystoneURL, Domain: domainKeystone{ID: testDomainID},\n\t\tAdminUsername: adminUser, AdminPassword: adminPass,\n\t}\n\ts := connector.Scopes{OfflineAccess: true, Groups: false}\n\n\tgroupID := createGroup(t, token, \"Test group\", testGroup)\n\taddUserToGroup(t, token, groupID, userID)\n\tdefer deleteResource(t, token, groupID, groupsURL)\n\n\tidentityLogin, _, err := c.Login(context.Background(), s, testUser, testPass)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\texpectEquals(t, 0, len(identityLogin.Groups))\n\n\tidentityRefresh, err := c.Refresh(context.Background(), s, identityLogin)\n\tif err != nil {\n\t\tt.Fatal(err.Error())\n\t}\n\texpectEquals(t, 0, len(identityRefresh.Groups))\n}\n\nfunc setupVariables(t *testing.T) {\n\tkeystoneURLEnv := \"DEX_KEYSTONE_URL\"\n\tkeystoneAdminURLEnv := \"DEX_KEYSTONE_ADMIN_URL\"\n\tkeystoneAdminUserEnv := \"DEX_KEYSTONE_ADMIN_USER\"\n\tkeystoneAdminPassEnv := \"DEX_KEYSTONE_ADMIN_PASS\"\n\tkeystoneURL = os.Getenv(keystoneURLEnv)\n\tif keystoneURL == \"\" {\n\t\tt.Skipf(\"variable %q not set, skipping keystone connector tests\\n\", keystoneURLEnv)\n\t\treturn\n\t}\n\tkeystoneAdminURL = os.Getenv(keystoneAdminURLEnv)\n\tif keystoneAdminURL == \"\" {\n\t\tt.Skipf(\"variable %q not set, skipping keystone connector tests\\n\", keystoneAdminURLEnv)\n\t\treturn\n\t}\n\tadminUser = os.Getenv(keystoneAdminUserEnv)\n\tif adminUser == \"\" {\n\t\tt.Skipf(\"variable %q not set, skipping keystone connector tests\\n\", keystoneAdminUserEnv)\n\t\treturn\n\t}\n\tadminPass = os.Getenv(keystoneAdminPassEnv)\n\tif adminPass == \"\" {\n\t\tt.Skipf(\"variable %q not set, skipping keystone connector tests\\n\", keystoneAdminPassEnv)\n\t\treturn\n\t}\n\tauthTokenURL = keystoneURL + \"/v3/auth/tokens/\"\n\tusersURL = keystoneAdminURL + \"/v3/users/\"\n\tgroupsURL = keystoneAdminURL + \"/v3/groups/\"\n\tdomainsURL = keystoneAdminURL + \"/v3/domains/\"\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %v to be equal %v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/ldap/gen-certs.sh",
    "content": "#!/bin/bash -e\n\n# Stolen from the coreos/matchbox repo.\n\necho \"\n[req]\nreq_extensions = v3_req\ndistinguished_name = req_distinguished_name\n\n[req_distinguished_name]\n\n[ v3_req ]\nbasicConstraints = CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.101 = localhost\n\" > openssl.config\n\nopenssl genrsa -out testdata/ca.key 2048\nopenssl genrsa -out testdata/server.key 2048\n\nopenssl req \\\n    -x509 -new -nodes \\\n    -key testdata/ca.key \\\n    -days 10000 -out testdata/ca.crt \\\n    -subj \"/CN=ldap-tests\"\n\nopenssl req \\\n\t-new \\\n\t-key testdata/server.key \\\n\t-out testdata/server.csr \\\n\t-subj \"/CN=localhost\" \\\n\t-config openssl.config\n\nopenssl x509 -req \\\n\t-in testdata/server.csr \\\n\t-CA testdata/ca.crt \\\n\t-CAkey testdata/ca.key \\\n\t-CAcreateserial \\\n\t-out testdata/server.crt \\\n\t-days 10000 \\\n\t-extensions v3_req \\\n\t-extfile openssl.config\n\nrm testdata/server.csr\nrm testdata/ca.srl\nrm openssl.config\n"
  },
  {
    "path": "connector/ldap/ldap.go",
    "content": "// Package ldap implements strategies for authenticating using the LDAP protocol.\npackage ldap\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/url\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/go-ldap/ldap/v3\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// Config holds the configuration parameters for the LDAP connector. The LDAP\n// connectors require executing two queries, the first to find the user based on\n// the username and password given to the connector. The second to use the user\n// entry to search for groups.\n//\n// An example config:\n//\n//     type: ldap\n//     config:\n//       host: ldap.example.com:636\n//       # The following field is required if using port 389.\n//       # insecureNoSSL: true\n//       rootCA: /etc/dex/ldap.ca\n//       bindDN: uid=serviceaccount,cn=users,dc=example,dc=com\n//       bindPW: password\n//       userSearch:\n//         # Would translate to the query \"(&(objectClass=person)(|(uid=<username>)(mail=<username>)))\"\n//         baseDN: cn=users,dc=example,dc=com\n//         filter: \"(objectClass=person)\"\n//         username:\n//         - uid\n//         - mail\n//         idAttr: uid\n//         emailAttr: mail\n//         nameAttr: name\n//         preferredUsernameAttr: uid\n//       groupSearch:\n//         # Would translate to the separate query per user matcher pair and aggregate results into a single group list:\n//         #  \"(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(memberUid=<user uid>))\"\n//         #  \"(&(|(objectClass=posixGroup)(objectClass=groupOfNames))(member=<user DN>))\"\n//         baseDN: cn=groups,dc=example,dc=com\n//         filter: \"(|(objectClass=posixGroup)(objectClass=groupOfNames))\"\n//         userMatchers:\n//         - userAttr: uid\n//           groupAttr: memberUid\n//           # Use if full DN is needed and not available as any other attribute\n//           # Will only work if \"DN\" attribute does not exist in the record:\n//         - userAttr: DN\n//           groupAttr: member\n//         nameAttr: name\n//\n\n// UsernameAttributes represents one or more LDAP attributes to match against\n// the username input. It supports unmarshaling from both a single string\n// (e.g. \"uid\") and a list of strings (e.g. [\"uid\", \"mail\"]).\ntype UsernameAttributes []string\n\nfunc (u *UsernameAttributes) UnmarshalJSON(data []byte) error {\n\tvar arr []string\n\tif err := json.Unmarshal(data, &arr); err == nil {\n\t\t*u = arr\n\t\treturn nil\n\t}\n\tvar s string\n\tif err := json.Unmarshal(data, &s); err != nil {\n\t\treturn fmt.Errorf(\"username must be a string or list of strings\")\n\t}\n\tif s != \"\" {\n\t\t*u = UsernameAttributes{s}\n\t}\n\treturn nil\n}\n\n// UserMatcher holds information about user and group matching.\ntype UserMatcher struct {\n\tUserAttr  string `json:\"userAttr\"`\n\tGroupAttr string `json:\"groupAttr\"`\n\t// Look for parent groups\n\tRecursionGroupAttr string `json:\"recursionGroupAttr\"`\n}\n\n// Config holds configuration options for LDAP logins.\ntype Config struct {\n\t// The host and optional port of the LDAP server. If port isn't supplied, it will be\n\t// guessed based on the TLS configuration. 389 or 636.\n\tHost string `json:\"host\"`\n\n\t// Required if LDAP host does not use TLS.\n\tInsecureNoSSL bool `json:\"insecureNoSSL\"`\n\n\t// Don't verify the CA.\n\tInsecureSkipVerify bool `json:\"insecureSkipVerify\"`\n\n\t// Connect to the insecure port then issue a StartTLS command to negotiate a\n\t// secure connection. If unsupplied secure connections will use the LDAPS\n\t// protocol.\n\tStartTLS bool `json:\"startTLS\"`\n\n\t// Path to a trusted root certificate file.\n\tRootCA string `json:\"rootCA\"`\n\t// Path to a client cert file generated by rootCA.\n\tClientCert string `json:\"clientCert\"`\n\t// Path to a client private key file generated by rootCA.\n\tClientKey string `json:\"clientKey\"`\n\t// Base64 encoded PEM data containing root CAs.\n\tRootCAData []byte `json:\"rootCAData\"`\n\n\t// BindDN and BindPW for an application service account. The connector uses these\n\t// credentials to search for users and groups.\n\tBindDN string `json:\"bindDN\"`\n\tBindPW string `json:\"bindPW\"`\n\n\t// UsernamePrompt allows users to override the username attribute (displayed\n\t// in the username/password prompt). If unset, the handler will use\n\t// \"Username\".\n\tUsernamePrompt string `json:\"usernamePrompt\"`\n\n\t// User entry search configuration.\n\tUserSearch struct {\n\t\t// BaseDN to start the search from. For example \"cn=users,dc=example,dc=com\"\n\t\tBaseDN string `json:\"baseDN\"`\n\n\t\t// Optional filter to apply when searching the directory. For example \"(objectClass=person)\"\n\t\tFilter string `json:\"filter\"`\n\n\t\t// Attribute(s) to match against the inputted username. Accepts a single string\n\t\t// or a list of strings. When multiple attributes are specified, an OR filter is\n\t\t// constructed: \"(|(<attr1>=<username>)(<attr2>=<username>))\".\n\t\tUsername UsernameAttributes `json:\"username\"`\n\n\t\t// Can either be:\n\t\t// * \"sub\" - search the whole sub tree\n\t\t// * \"one\" - only search one level\n\t\tScope string `json:\"scope\"`\n\n\t\t// A mapping of attributes on the user entry to claims.\n\t\tIDAttr                    string `json:\"idAttr\"`                // Defaults to \"uid\"\n\t\tEmailAttr                 string `json:\"emailAttr\"`             // Defaults to \"mail\"\n\t\tNameAttr                  string `json:\"nameAttr\"`              // No default.\n\t\tPreferredUsernameAttrAttr string `json:\"preferredUsernameAttr\"` // No default.\n\n\t\t// If this is set, the email claim of the id token will be constructed from the idAttr and\n\t\t// value of emailSuffix. This should not include the @ character.\n\t\tEmailSuffix string `json:\"emailSuffix\"` // No default.\n\t} `json:\"userSearch\"`\n\n\t// Group search configuration.\n\tGroupSearch struct {\n\t\t// BaseDN to start the search from. For example \"cn=groups,dc=example,dc=com\"\n\t\tBaseDN string `json:\"baseDN\"`\n\n\t\t// Optional filter to apply when searching the directory. For example \"(objectClass=posixGroup)\"\n\t\tFilter string `json:\"filter\"`\n\n\t\tScope string `json:\"scope\"` // Defaults to \"sub\"\n\n\t\t// DEPRECATED config options. Those are left for backward compatibility.\n\t\t// See \"UserMatchers\" below for the current group to user matching implementation\n\t\t// TODO: should be eventually removed from the code\n\t\tUserAttr  string `json:\"userAttr\"`\n\t\tGroupAttr string `json:\"groupAttr\"`\n\n\t\tRecursionGroupAttr string `json:\"recursionGroupAttr\"`\n\n\t\t// Array of the field pairs used to match a user to a group.\n\t\t// See the \"UserMatcher\" struct for the exact field names\n\t\t//\n\t\t// Each pair adds an additional requirement to the filter that an attribute in the group\n\t\t// match the user's attribute value. For example that the \"members\" attribute of\n\t\t// a group matches the \"uid\" of the user. The exact filter being added is:\n\t\t//\n\t\t//   (userMatchers[n].<groupAttr>=userMatchers[n].<userAttr value>)\n\t\t//\n\t\tUserMatchers []UserMatcher `json:\"userMatchers\"`\n\n\t\t// The attribute of the group that represents its name.\n\t\tNameAttr string `json:\"nameAttr\"`\n\t} `json:\"groupSearch\"`\n}\n\nfunc scopeString(i int) string {\n\tswitch i {\n\tcase ldap.ScopeBaseObject:\n\t\treturn \"base\"\n\tcase ldap.ScopeSingleLevel:\n\t\treturn \"one\"\n\tcase ldap.ScopeWholeSubtree:\n\t\treturn \"sub\"\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\nfunc parseScope(s string) (int, bool) {\n\t// NOTE(ericchiang): ScopeBaseObject doesn't really make sense for us because we\n\t// never know the user's or group's DN.\n\tswitch s {\n\tcase \"\", \"sub\":\n\t\treturn ldap.ScopeWholeSubtree, true\n\tcase \"one\":\n\t\treturn ldap.ScopeSingleLevel, true\n\t}\n\treturn 0, false\n}\n\n// Build a list of group attr name to user attr value matchers.\n// Function exists here to allow backward compatibility between old and new\n// group to user matching implementations.\n// See \"Config.GroupSearch.UserMatchers\" comments for the details\nfunc userMatchers(c *Config, logger *slog.Logger) []UserMatcher {\n\tif len(c.GroupSearch.UserMatchers) > 0 && c.GroupSearch.UserMatchers[0].UserAttr != \"\" {\n\t\treturn c.GroupSearch.UserMatchers\n\t}\n\n\tif c.GroupSearch.UserAttr != \"\" || c.GroupSearch.GroupAttr != \"\" {\n\t\tlogger.Warn(`use \"groupSearch.userMatchers\" option instead of \"userAttr/groupAttr\" fields`, \"deprecated\", true)\n\t}\n\treturn []UserMatcher{\n\t\t{\n\t\t\tUserAttr:           c.GroupSearch.UserAttr,\n\t\t\tGroupAttr:          c.GroupSearch.GroupAttr,\n\t\t\tRecursionGroupAttr: c.GroupSearch.RecursionGroupAttr,\n\t\t},\n\t}\n}\n\n// Open returns an authentication strategy using LDAP.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tlogger = logger.With(slog.Group(\"connector\", \"type\", \"ldap\", \"id\", id))\n\tconn, err := c.OpenConnector(logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn connector.Connector(conn), nil\n}\n\ntype refreshData struct {\n\tUsername string     `json:\"username\"`\n\tEntry    ldap.Entry `json:\"entry\"`\n}\n\n// OpenConnector is the same as Open but returns a type with all implemented connector interfaces.\nfunc (c *Config) OpenConnector(logger *slog.Logger) (interface {\n\tconnector.Connector\n\tconnector.PasswordConnector\n\tconnector.RefreshConnector\n}, error,\n) {\n\treturn c.openConnector(logger)\n}\n\nfunc (c *Config) openConnector(logger *slog.Logger) (*ldapConnector, error) {\n\trequiredFields := []struct {\n\t\tname string\n\t\tval  string\n\t}{\n\t\t{\"host\", c.Host},\n\t\t{\"userSearch.baseDN\", c.UserSearch.BaseDN},\n\t}\n\n\tfor _, field := range requiredFields {\n\t\tif field.val == \"\" {\n\t\t\treturn nil, fmt.Errorf(\"ldap: missing required field %q\", field.name)\n\t\t}\n\t}\n\n\tif len(c.UserSearch.Username) == 0 {\n\t\treturn nil, fmt.Errorf(\"ldap: missing required field %q\", \"userSearch.username\")\n\t}\n\n\tvar (\n\t\thost string\n\t\terr  error\n\t)\n\tif host, _, err = net.SplitHostPort(c.Host); err != nil {\n\t\thost = c.Host\n\t\tif c.InsecureNoSSL {\n\t\t\tc.Host += \":389\"\n\t\t} else {\n\t\t\tc.Host += \":636\"\n\t\t}\n\t}\n\n\ttlsConfig := &tls.Config{ServerName: host, InsecureSkipVerify: c.InsecureSkipVerify}\n\tif c.RootCA != \"\" || len(c.RootCAData) != 0 {\n\t\tdata := c.RootCAData\n\t\tif len(data) == 0 {\n\t\t\tvar err error\n\t\t\tif data, err = os.ReadFile(c.RootCA); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"ldap: read ca file: %v\", err)\n\t\t\t}\n\t\t}\n\t\trootCAs := x509.NewCertPool()\n\t\tif !rootCAs.AppendCertsFromPEM(data) {\n\t\t\treturn nil, fmt.Errorf(\"ldap: no certs found in ca file\")\n\t\t}\n\t\ttlsConfig.RootCAs = rootCAs\n\t}\n\n\tif c.ClientKey != \"\" && c.ClientCert != \"\" {\n\t\tcert, err := tls.LoadX509KeyPair(c.ClientCert, c.ClientKey)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"ldap: load client cert failed: %v\", err)\n\t\t}\n\t\ttlsConfig.Certificates = append(tlsConfig.Certificates, cert)\n\t}\n\tuserSearchScope, ok := parseScope(c.UserSearch.Scope)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"userSearch.Scope unknown value %q\", c.UserSearch.Scope)\n\t}\n\tgroupSearchScope, ok := parseScope(c.GroupSearch.Scope)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"groupSearch.Scope unknown value %q\", c.GroupSearch.Scope)\n\t}\n\n\t// TODO(nabokihms): remove it after deleting deprecated groupSearch options\n\tc.GroupSearch.UserMatchers = userMatchers(c, logger)\n\treturn &ldapConnector{*c, userSearchScope, groupSearchScope, tlsConfig, c.UserSearch.Username, logger}, nil\n}\n\nvar (\n\t_ connector.PasswordConnector = (*ldapConnector)(nil)\n\t_ connector.RefreshConnector  = (*ldapConnector)(nil)\n)\n\ntype ldapConnector struct {\n\tConfig\n\n\tuserSearchScope  int\n\tgroupSearchScope int\n\n\ttlsConfig *tls.Config\n\n\tusernameAttrs []string\n\n\tlogger *slog.Logger\n}\n\n// do initializes a connection to the LDAP directory and passes it to the\n// provided function. It then performs appropriate teardown or reuse before\n// returning.\nfunc (c *ldapConnector) do(_ context.Context, f func(c *ldap.Conn) error) error {\n\t// TODO(ericchiang): support context here\n\tvar (\n\t\tconn *ldap.Conn\n\t\terr  error\n\t)\n\n\tswitch {\n\tcase c.InsecureNoSSL:\n\t\tu := url.URL{Scheme: \"ldap\", Host: c.Host}\n\t\tconn, err = ldap.DialURL(u.String())\n\tcase c.StartTLS:\n\t\tu := url.URL{Scheme: \"ldap\", Host: c.Host}\n\t\tconn, err = ldap.DialURL(u.String())\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to connect: %v\", err)\n\t\t}\n\t\tif err := conn.StartTLS(c.tlsConfig); err != nil {\n\t\t\treturn fmt.Errorf(\"start TLS failed: %v\", err)\n\t\t}\n\tdefault:\n\t\tu := url.URL{Scheme: \"ldaps\", Host: c.Host}\n\t\tconn, err = ldap.DialURL(u.String(), ldap.DialWithTLSConfig(c.tlsConfig))\n\t}\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to connect: %v\", err)\n\t}\n\tdefer conn.Close()\n\n\t// If bindDN and bindPW are empty this will default to an anonymous bind.\n\tif c.BindDN == \"\" && c.BindPW == \"\" {\n\t\tif err := conn.UnauthenticatedBind(\"\"); err != nil {\n\t\t\treturn fmt.Errorf(\"ldap: initial anonymous bind failed: %v\", err)\n\t\t}\n\t} else if err := conn.Bind(c.BindDN, c.BindPW); err != nil {\n\t\treturn fmt.Errorf(\"ldap: initial bind for user %q failed: %v\", c.BindDN, err)\n\t}\n\n\treturn f(conn)\n}\n\nfunc (c *ldapConnector) getAttrs(e ldap.Entry, name string) []string {\n\tfor _, a := range e.Attributes {\n\t\tif a.Name != name {\n\t\t\tcontinue\n\t\t}\n\t\treturn a.Values\n\t}\n\tif strings.ToLower(name) == \"dn\" {\n\t\treturn []string{e.DN}\n\t}\n\n\tc.logger.Debug(\"attribute is not fround in entry\", \"attribute\", name)\n\treturn nil\n}\n\nfunc (c *ldapConnector) getAttr(e ldap.Entry, name string) string {\n\tif a := c.getAttrs(e, name); len(a) > 0 {\n\t\treturn a[0]\n\t}\n\treturn \"\"\n}\n\nfunc (c *ldapConnector) identityFromEntry(user ldap.Entry) (ident connector.Identity, err error) {\n\t// If we're missing any attributes, such as email or ID, we want to report\n\t// an error rather than continuing.\n\tmissing := []string{}\n\n\t// Fill the identity struct using the attributes from the user entry.\n\tif ident.UserID = c.getAttr(user, c.UserSearch.IDAttr); ident.UserID == \"\" {\n\t\tmissing = append(missing, c.UserSearch.IDAttr)\n\t}\n\n\tif c.UserSearch.NameAttr != \"\" {\n\t\tif ident.Username = c.getAttr(user, c.UserSearch.NameAttr); ident.Username == \"\" {\n\t\t\tmissing = append(missing, c.UserSearch.NameAttr)\n\t\t}\n\t}\n\n\tif c.UserSearch.PreferredUsernameAttrAttr != \"\" {\n\t\tif ident.PreferredUsername = c.getAttr(user, c.UserSearch.PreferredUsernameAttrAttr); ident.PreferredUsername == \"\" {\n\t\t\tmissing = append(missing, c.UserSearch.PreferredUsernameAttrAttr)\n\t\t}\n\t}\n\n\tif c.UserSearch.EmailSuffix != \"\" {\n\t\tident.Email = ident.Username + \"@\" + c.UserSearch.EmailSuffix\n\t} else if ident.Email = c.getAttr(user, c.UserSearch.EmailAttr); ident.Email == \"\" {\n\t\tmissing = append(missing, c.UserSearch.EmailAttr)\n\t}\n\t// TODO(ericchiang): Let this value be set from an attribute.\n\tident.EmailVerified = true\n\n\tif len(missing) != 0 {\n\t\terr := fmt.Errorf(\"ldap: entry %q missing following required attribute(s): %q\", user.DN, missing)\n\t\treturn connector.Identity{}, err\n\t}\n\treturn ident, nil\n}\n\nfunc (c *ldapConnector) userEntry(conn *ldap.Conn, username string) (user ldap.Entry, found bool, err error) {\n\tvar filter string\n\tescapedUsername := ldap.EscapeFilter(username)\n\n\tattrFilters := make([]string, 0, len(c.usernameAttrs))\n\tfor _, attr := range c.usernameAttrs {\n\t\tattrFilters = append(attrFilters, fmt.Sprintf(\"(%s=%s)\", attr, escapedUsername))\n\t}\n\tif len(attrFilters) == 1 {\n\t\tfilter = attrFilters[0] // Skip OR wrapper for single attribute\n\t} else {\n\t\tfilter = fmt.Sprintf(\"(|%s)\", strings.Join(attrFilters, \"\"))\n\t}\n\n\tif c.UserSearch.Filter != \"\" {\n\t\tfilter = fmt.Sprintf(\"(&%s%s)\", c.UserSearch.Filter, filter)\n\t}\n\n\t// Initial search.\n\treq := &ldap.SearchRequest{\n\t\tBaseDN: c.UserSearch.BaseDN,\n\t\tFilter: filter,\n\t\tScope:  c.userSearchScope,\n\t\t// We only need to search for these specific requests.\n\t\tAttributes: []string{\n\t\t\tc.UserSearch.IDAttr,\n\t\t\tc.UserSearch.EmailAttr,\n\t\t\t// TODO(ericchiang): what if this contains duplicate values?\n\t\t},\n\t}\n\n\treq.Attributes = append(req.Attributes, c.usernameAttrs...)\n\n\tfor _, matcher := range c.GroupSearch.UserMatchers {\n\t\treq.Attributes = append(req.Attributes, matcher.UserAttr)\n\t}\n\n\tif c.UserSearch.NameAttr != \"\" {\n\t\treq.Attributes = append(req.Attributes, c.UserSearch.NameAttr)\n\t}\n\n\tif c.UserSearch.PreferredUsernameAttrAttr != \"\" {\n\t\treq.Attributes = append(req.Attributes, c.UserSearch.PreferredUsernameAttrAttr)\n\t}\n\n\tc.logger.Info(\"performing ldap search\",\n\t\t\"base_dn\", req.BaseDN, \"scope\", scopeString(req.Scope), \"filter\", req.Filter)\n\tresp, err := conn.Search(req)\n\tif err != nil {\n\t\treturn ldap.Entry{}, false, fmt.Errorf(\"ldap: search with filter %q failed: %v\", req.Filter, err)\n\t}\n\n\tswitch n := len(resp.Entries); n {\n\tcase 0:\n\t\tc.logger.Error(\"no results returned for filter\", \"filter\", filter)\n\t\treturn ldap.Entry{}, false, nil\n\tcase 1:\n\t\tuser = *resp.Entries[0]\n\t\tc.logger.Info(\"username mapped to entry\", \"username\", username, \"user_dn\", user.DN)\n\t\treturn user, true, nil\n\tdefault:\n\t\treturn ldap.Entry{}, false, fmt.Errorf(\"ldap: filter returned multiple (%d) results: %q\", n, filter)\n\t}\n}\n\nfunc (c *ldapConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (ident connector.Identity, validPass bool, err error) {\n\t// make this check to avoid unauthenticated bind to the LDAP server.\n\n\tif password == \"\" {\n\t\treturn connector.Identity{}, false, nil\n\t}\n\n\tvar (\n\t\t// We want to return a different error if the user's password is incorrect vs\n\t\t// if there was an error.\n\t\tincorrectPass = false\n\t\tuser          ldap.Entry\n\t)\n\n\tusername = ldap.EscapeFilter(username)\n\n\terr = c.do(ctx, func(conn *ldap.Conn) error {\n\t\tentry, found, err := c.userEntry(conn, username)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !found {\n\t\t\tincorrectPass = true\n\t\t\treturn nil\n\t\t}\n\t\tuser = entry\n\n\t\t// Try to authenticate as the distinguished name.\n\t\tif err := conn.Bind(user.DN, password); err != nil {\n\t\t\t// Detect a bad password through the LDAP error code.\n\t\t\tif ldapErr, ok := err.(*ldap.Error); ok {\n\t\t\t\tswitch ldapErr.ResultCode {\n\t\t\t\tcase ldap.LDAPResultInvalidCredentials:\n\t\t\t\t\tc.logger.Error(\"invalid password for user\", \"user_dn\", user.DN)\n\t\t\t\t\tincorrectPass = true\n\t\t\t\t\treturn nil\n\t\t\t\tcase ldap.LDAPResultConstraintViolation:\n\t\t\t\t\tc.logger.Error(\"constraint violation for user\", \"user_dn\", user.DN, \"err\", ldapErr.Error())\n\t\t\t\t\tincorrectPass = true\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t} // will also catch all ldap.Error without a case statement above\n\t\t\treturn fmt.Errorf(\"ldap: failed to bind as dn %q: %v\", user.DN, err)\n\t\t}\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn connector.Identity{}, false, err\n\t}\n\tif incorrectPass {\n\t\treturn connector.Identity{}, false, nil\n\t}\n\n\tif ident, err = c.identityFromEntry(user); err != nil {\n\t\treturn connector.Identity{}, false, err\n\t}\n\n\tif s.Groups {\n\t\tgroups, err := c.groups(ctx, user)\n\t\tif err != nil {\n\t\t\treturn connector.Identity{}, false, fmt.Errorf(\"ldap: failed to query groups: %v\", err)\n\t\t}\n\t\tident.Groups = groups\n\t}\n\n\tif s.OfflineAccess {\n\t\trefresh := refreshData{\n\t\t\tUsername: username,\n\t\t\tEntry:    user,\n\t\t}\n\t\t// Encode entry for follow up requests such as the groups query and\n\t\t// refresh attempts.\n\t\tif ident.ConnectorData, err = json.Marshal(refresh); err != nil {\n\t\t\treturn connector.Identity{}, false, fmt.Errorf(\"ldap: marshal entry: %v\", err)\n\t\t}\n\t}\n\n\treturn ident, true, nil\n}\n\nfunc (c *ldapConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {\n\tvar data refreshData\n\tif err := json.Unmarshal(ident.ConnectorData, &data); err != nil {\n\t\treturn ident, fmt.Errorf(\"ldap: failed to unmarshal internal data: %v\", err)\n\t}\n\n\tvar user ldap.Entry\n\terr := c.do(ctx, func(conn *ldap.Conn) error {\n\t\tentry, found, err := c.userEntry(conn, data.Username)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !found {\n\t\t\treturn fmt.Errorf(\"ldap: user not found %q\", data.Username)\n\t\t}\n\t\tuser = entry\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn ident, err\n\t}\n\tif user.DN != data.Entry.DN {\n\t\treturn ident, fmt.Errorf(\"ldap: refresh for username %q expected DN %q got %q\", data.Username, data.Entry.DN, user.DN)\n\t}\n\n\tnewIdent, err := c.identityFromEntry(user)\n\tif err != nil {\n\t\treturn ident, err\n\t}\n\tnewIdent.ConnectorData = ident.ConnectorData\n\n\tif s.Groups {\n\t\tgroups, err := c.groups(ctx, user)\n\t\tif err != nil {\n\t\t\treturn connector.Identity{}, fmt.Errorf(\"ldap: failed to query groups: %v\", err)\n\t\t}\n\t\tnewIdent.Groups = groups\n\t}\n\treturn newIdent, nil\n}\n\nfunc (c *ldapConnector) groups(ctx context.Context, user ldap.Entry) ([]string, error) {\n\tif c.GroupSearch.BaseDN == \"\" {\n\t\tc.logger.Debug(\"No groups returned because no groups baseDN has been configured.\", \"base_dn\", c.getAttr(user, c.UserSearch.NameAttr))\n\t\treturn nil, nil\n\t}\n\n\tvar groupNames []string\n\n\tfor _, matcher := range c.GroupSearch.UserMatchers {\n\t\t// Initial Search\n\t\tvar groups []*ldap.Entry\n\t\tfor _, attr := range c.getAttrs(user, matcher.UserAttr) {\n\t\t\tobtained, filter, err := c.queryGroups(ctx, matcher.GroupAttr, attr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tgotGroups := len(obtained) != 0\n\t\t\tif !gotGroups {\n\t\t\t\t// TODO(ericchiang): Is this going to spam the logs?\n\t\t\t\tc.logger.Error(\"ldap: groups search returned no groups\", \"filter\", filter)\n\t\t\t}\n\t\t\tgroups = append(groups, obtained...)\n\t\t}\n\n\t\t// If RecursionGroupAttr is not set, convert direct groups into names and return\n\t\tif matcher.RecursionGroupAttr == \"\" {\n\t\t\tfor _, group := range groups {\n\t\t\t\tname := c.getAttr(*group, c.GroupSearch.NameAttr)\n\t\t\t\tif name == \"\" {\n\t\t\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\t\t\"ldap: group entity %q missing required attribute %q\",\n\t\t\t\t\t\tgroup.DN, c.GroupSearch.NameAttr,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\tgroupNames = append(groupNames, name)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Recursive Search\n\t\tc.logger.Info(\"Recursive group search enabled\", \"groupAttr\", matcher.GroupAttr, \"recursionAttr\", matcher.RecursionGroupAttr)\n\t\tfor {\n\t\t\tvar nextLevel []*ldap.Entry\n\t\t\tfor _, group := range groups {\n\t\t\t\tname := c.getAttr(*group, c.GroupSearch.NameAttr)\n\t\t\t\tif name == \"\" {\n\t\t\t\t\treturn nil, fmt.Errorf(\"ldap: group entity %q missing required attribute %q\",\n\t\t\t\t\t\tgroup.DN, c.GroupSearch.NameAttr)\n\t\t\t\t}\n\n\t\t\t\t// Prevent duplicates and circular references.\n\t\t\t\tduplicate := false\n\t\t\t\tfor _, existingName := range groupNames {\n\t\t\t\t\tif name == existingName {\n\t\t\t\t\t\tc.logger.Debug(\"Found duplicate group\", \"name\", name)\n\t\t\t\t\t\tduplicate = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif duplicate {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tgroupNames = append(groupNames, name)\n\n\t\t\t\t// Search for parent groups using the group's DN.\n\t\t\t\tparents, filter, err := c.queryGroups(ctx, matcher.RecursionGroupAttr, group.DN)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tif len(parents) == 0 {\n\t\t\t\t\tc.logger.Debug(\"No parent groups found\", \"filter\", filter)\n\t\t\t\t} else {\n\t\t\t\t\tnextLevel = append(nextLevel, parents...)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(nextLevel) == 0 {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tgroups = nextLevel\n\t\t}\n\t}\n\treturn groupNames, nil\n}\n\nfunc (c *ldapConnector) queryGroups(ctx context.Context, memberAttr, dn string) ([]*ldap.Entry, string, error) {\n\tfilter := fmt.Sprintf(\"(%s=%s)\", memberAttr, ldap.EscapeFilter(dn))\n\tif c.GroupSearch.Filter != \"\" {\n\t\tfilter = fmt.Sprintf(\"(&%s%s)\", c.GroupSearch.Filter, filter)\n\t}\n\n\treq := &ldap.SearchRequest{\n\t\tBaseDN:     c.GroupSearch.BaseDN,\n\t\tFilter:     filter,\n\t\tScope:      c.groupSearchScope,\n\t\tAttributes: []string{c.GroupSearch.NameAttr},\n\t}\n\n\tvar entries []*ldap.Entry\n\tif err := c.do(ctx, func(conn *ldap.Conn) error {\n\t\tc.logger.Info(\n\t\t\t\"performing ldap search\",\n\t\t\t\"base_dn\", req.BaseDN,\n\t\t\t\"scope\", scopeString(req.Scope),\n\t\t\t\"filter\", req.Filter,\n\t\t)\n\t\tresp, err := conn.Search(req)\n\t\tif err != nil {\n\t\t\tif ldapErr, ok := err.(*ldap.Error); ok && ldapErr.ResultCode == ldap.LDAPResultNoSuchObject {\n\t\t\t\tc.logger.Info(\"LDAP search returned no groups\", \"filter\", filter)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"ldap: search failed: %v\", err)\n\t\t}\n\t\tentries = append(entries, resp.Entries...)\n\t\treturn nil\n\t}); err != nil {\n\t\treturn nil, filter, err\n\t}\n\treturn entries, filter, nil\n}\n\nfunc (c *ldapConnector) Prompt() string {\n\treturn c.UsernamePrompt\n}\n"
  },
  {
    "path": "connector/ldap/ldap_test.go",
    "content": "package ldap\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// connectionMethod indicates how the test should connect to the LDAP server.\ntype connectionMethod int32\n\nconst (\n\tconnectStartTLS connectionMethod = iota\n\tconnectLDAPS\n\tconnectLDAP\n\tconnectInsecureSkipVerify\n)\n\n// subtest is a login test against a given schema.\ntype subtest struct {\n\t// Name of the sub-test.\n\tname string\n\n\t// Password credentials, and if the connector should request\n\t// groups as well.\n\tusername string\n\tpassword string\n\tgroups   bool\n\n\t// Expected result of the login.\n\twantErr   bool\n\twantBadPW bool\n\twant      connector.Identity\n}\n\nfunc TestQuery(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestQuery,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestQuery,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"validpassword2\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestQuery,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"invalidpassword\",\n\t\t\tusername:  \"jane\",\n\t\t\tpassword:  \"badpassword\",\n\t\t\twantBadPW: true,\n\t\t},\n\t\t{\n\t\t\tname:      \"invaliduser\",\n\t\t\tusername:  \"idontexist\",\n\t\t\tpassword:  \"foo\",\n\t\t\twantBadPW: true, // Want invalid password, not a query error.\n\t\t},\n\t\t{\n\t\t\tname:      \"invalid wildcard username\",\n\t\t\tusername:  \"a*\", // wildcard query is not allowed\n\t\t\tpassword:  \"foo\",\n\t\t\twantBadPW: true, // Want invalid password, not a query error.\n\t\t},\n\t\t{\n\t\t\tname:      \"invalid wildcard password\",\n\t\t\tusername:  \"john\",\n\t\t\tpassword:  \"*\",  // wildcard password is not allowed\n\t\t\twantBadPW: true, // Want invalid password, not a query error.\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestQueryWithEmailSuffix(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailSuffix = \"test.example.com\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"ignoremailattr\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"jane@test.example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"nomailattr\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"john@test.example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestUserFilter(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=TestUserFilter,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\tc.UserSearch.Filter = \"(ou:dn:=Seattle)\"\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"validpassword2\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:      \"invalidpassword\",\n\t\t\tusername:  \"jane\",\n\t\t\tpassword:  \"badpassword\",\n\t\t\twantBadPW: true,\n\t\t},\n\t\t{\n\t\t\tname:      \"invaliduser\",\n\t\t\tusername:  \"idontexist\",\n\t\t\tpassword:  \"foo\",\n\t\t\twantBadPW: true, // Want invalid password, not a query error.\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestUsernameWithMultipleAttributes(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=TestUsernameWithMultipleAttributes,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\", \"mail\"}\n\tc.UserSearch.Filter = \"(ou:dn:=Seattle)\"\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"cn\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"mail\",\n\t\t\tusername: \"janedoe@example.com\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestGroupQuery(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestGroupQuery,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\tc.GroupSearch.BaseDN = \"ou=Groups,ou=TestGroupQuery,dc=example,dc=org\"\n\tc.GroupSearch.UserMatchers = []UserMatcher{\n\t\t{\n\t\t\tUserAttr:  \"DN\",\n\t\t\tGroupAttr: \"member\",\n\t\t},\n\t}\n\tc.GroupSearch.NameAttr = \"cn\"\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\", \"developers\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"validpassword2\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestGroupQuery,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\"},\n\t\t\t},\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestGroupsOnUserEntity(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\tc.GroupSearch.BaseDN = \"ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org\"\n\tc.GroupSearch.UserMatchers = []UserMatcher{\n\t\t{\n\t\t\tUserAttr:  \"departmentNumber\",\n\t\t\tGroupAttr: \"gidNumber\",\n\t\t},\n\t}\n\tc.GroupSearch.NameAttr = \"cn\"\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\", \"developers\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"validpassword2\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\", \"designers\"},\n\t\t\t},\n\t\t},\n\t}\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestGroupFilter(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestGroupFilter,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\tc.GroupSearch.BaseDN = \"ou=TestGroupFilter,dc=example,dc=org\"\n\tc.GroupSearch.UserMatchers = []UserMatcher{\n\t\t{\n\t\t\tUserAttr:  \"dn\",\n\t\t\tGroupAttr: \"member\",\n\t\t},\n\t}\n\tc.GroupSearch.NameAttr = \"cn\"\n\tc.GroupSearch.Filter = \"(ou:dn:=Seattle)\" // ignore other groups\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\", \"developers\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"validpassword2\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\"},\n\t\t\t},\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestGroupToUserMatchers(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\tc.GroupSearch.BaseDN = \"ou=TestGroupToUserMatchers,dc=example,dc=org\"\n\tc.GroupSearch.UserMatchers = []UserMatcher{\n\t\t{\n\t\t\tUserAttr:  \"DN\",\n\t\t\tGroupAttr: \"member\",\n\t\t},\n\t\t{\n\t\t\tUserAttr:  \"uid\",\n\t\t\tGroupAttr: \"memberUid\",\n\t\t},\n\t}\n\tc.GroupSearch.NameAttr = \"cn\"\n\tc.GroupSearch.Filter = \"(|(objectClass=posixGroup)(objectClass=groupOfNames))\" // search all group types\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\", \"developers\", \"frontend\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"validpassword2\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\", \"qa\", \"logger\"},\n\t\t\t},\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\n// Test deprecated group to user matching implementation\n// which was left for backward compatibility.\n// See \"Config.GroupSearch.UserMatchers\" comments for the details\nfunc TestDeprecatedGroupToUserMatcher(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\tc.GroupSearch.BaseDN = \"ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\"\n\tc.GroupSearch.UserAttr = \"DN\"\n\tc.GroupSearch.GroupAttr = \"member\"\n\tc.GroupSearch.NameAttr = \"cn\"\n\tc.GroupSearch.Filter = \"(ou:dn:=Seattle)\" // ignore other groups\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\", \"developers\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"validpassword2\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"admins\"},\n\t\t\t},\n\t\t},\n\t}\n\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc TestStartTLS(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestStartTLS,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestStartTLS,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t}\n\trunTests(t, connectStartTLS, c, tests)\n}\n\nfunc TestInsecureSkipVerify(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t}\n\trunTests(t, connectInsecureSkipVerify, c, tests)\n}\n\nfunc TestLDAPS(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestLDAPS,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"validpassword\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestLDAPS,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t},\n\t\t},\n\t}\n\trunTests(t, connectLDAPS, c, tests)\n}\n\nfunc TestUsernamePrompt(t *testing.T) {\n\ttests := map[string]struct {\n\t\tconfig   Config\n\t\texpected string\n\t}{\n\t\t\"with usernamePrompt unset it returns \\\"\\\"\": {\n\t\t\tconfig:   Config{},\n\t\t\texpected: \"\",\n\t\t},\n\t\t\"with usernamePrompt set it returns that\": {\n\t\t\tconfig:   Config{UsernamePrompt: \"Email address\"},\n\t\t\texpected: \"Email address\",\n\t\t},\n\t}\n\n\tfor n, d := range tests {\n\t\tt.Run(n, func(t *testing.T) {\n\t\t\tconn := &ldapConnector{Config: d.config}\n\t\t\tif actual := conn.Prompt(); actual != d.expected {\n\t\t\t\tt.Errorf(\"expected %v, got %v\", d.expected, actual)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestUsernameAttributesUnmarshal(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tjson    string\n\t\twant    UsernameAttributes\n\t\twantErr bool\n\t}{\n\t\t{name: \"single string\", json: `\"uid\"`, want: UsernameAttributes{\"uid\"}},\n\t\t{name: \"array of strings\", json: `[\"uid\",\"mail\"]`, want: UsernameAttributes{\"uid\", \"mail\"}},\n\t\t{name: \"single element array\", json: `[\"cn\"]`, want: UsernameAttributes{\"cn\"}},\n\t\t{name: \"empty string\", json: `\"\"`, want: nil},\n\t\t{name: \"invalid type\", json: `123`, wantErr: true},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar got UsernameAttributes\n\t\t\terr := got.UnmarshalJSON([]byte(tt.json))\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Fatalf(\"UnmarshalJSON() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t\tif !tt.wantErr {\n\t\t\t\tif diff := pretty.Compare(tt.want, got); diff != \"\" {\n\t\t\t\t\tt.Errorf(\"unexpected result: %s\", diff)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNestedGroups(t *testing.T) {\n\tc := &Config{}\n\tc.UserSearch.BaseDN = \"ou=People,ou=TestNestedGroups,dc=example,dc=org\"\n\tc.UserSearch.NameAttr = \"cn\"\n\tc.UserSearch.EmailAttr = \"mail\"\n\tc.UserSearch.IDAttr = \"DN\"\n\tc.UserSearch.Username = UsernameAttributes{\"cn\"}\n\n\tc.GroupSearch.BaseDN = \"ou=TestNestedGroups,dc=example,dc=org\"\n\tc.GroupSearch.UserMatchers = []UserMatcher{\n\t\t{\n\t\t\tUserAttr:  \"DN\",\n\t\t\tGroupAttr: \"member\",\n\t\t\t// Enable Recursive Search\n\t\t\tRecursionGroupAttr: \"member\",\n\t\t},\n\t}\n\tc.GroupSearch.NameAttr = \"cn\"\n\n\ttests := []subtest{\n\t\t{\n\t\t\tname:     \"nestedgroups_jane\",\n\t\t\tusername: \"jane\",\n\t\t\tpassword: \"foo\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org\",\n\t\t\t\tUsername:      \"jane\",\n\t\t\t\tEmail:         \"janedoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"childGroup\", \"circularGroup1\", \"intermediateGroup\", \"circularGroup2\", \"parentGroup\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:     \"nestedgroups_john\",\n\t\t\tusername: \"john\",\n\t\t\tpassword: \"bar\",\n\t\t\tgroups:   true,\n\t\t\twant: connector.Identity{\n\t\t\t\tUserID:        \"cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org\",\n\t\t\t\tUsername:      \"john\",\n\t\t\t\tEmail:         \"johndoe@example.com\",\n\t\t\t\tEmailVerified: true,\n\t\t\t\tGroups:        []string{\"circularGroup2\", \"intermediateGroup\", \"circularGroup1\", \"parentGroup\"},\n\t\t\t},\n\t\t},\n\t}\n\trunTests(t, connectLDAP, c, tests)\n}\n\nfunc getenv(key, defaultVal string) string {\n\tif val := os.Getenv(key); val != \"\" {\n\t\treturn val\n\t}\n\treturn defaultVal\n}\n\n// runTests runs a set of tests against an LDAP schema.\n//\n// The tests require LDAP to be running.\n// You can use the provided docker-compose file to setup an LDAP server.\nfunc runTests(t *testing.T, connMethod connectionMethod, config *Config, tests []subtest) {\n\tldapHost := os.Getenv(\"DEX_LDAP_HOST\")\n\tif ldapHost == \"\" {\n\t\tt.Skipf(`test environment variable \"DEX_LDAP_HOST\" not set, skipping`)\n\t}\n\n\t// Shallow copy.\n\tc := *config\n\n\t// We need to configure host parameters but don't want to overwrite user or\n\t// group search configuration.\n\tswitch connMethod {\n\tcase connectStartTLS:\n\t\tc.Host = fmt.Sprintf(\"%s:%s\", ldapHost, getenv(\"DEX_LDAP_PORT\", \"389\"))\n\t\tc.RootCA = \"testdata/certs/ca.crt\"\n\t\tc.StartTLS = true\n\tcase connectLDAPS:\n\t\tc.Host = fmt.Sprintf(\"%s:%s\", ldapHost, getenv(\"DEX_LDAP_TLS_PORT\", \"636\"))\n\t\tc.RootCA = \"testdata/certs/ca.crt\"\n\tcase connectInsecureSkipVerify:\n\t\tc.Host = fmt.Sprintf(\"%s:%s\", ldapHost, getenv(\"DEX_LDAP_TLS_PORT\", \"636\"))\n\t\tc.InsecureSkipVerify = true\n\tcase connectLDAP:\n\t\tc.Host = fmt.Sprintf(\"%s:%s\", ldapHost, getenv(\"DEX_LDAP_PORT\", \"389\"))\n\t\tc.InsecureNoSSL = true\n\t}\n\n\tc.BindDN = \"cn=admin,dc=example,dc=org\"\n\tc.BindPW = \"admin\"\n\n\tl := slog.New(slog.DiscardHandler)\n\n\tconn, err := c.openConnector(l)\n\tif err != nil {\n\t\tt.Errorf(\"open connector: %v\", err)\n\t}\n\n\tfor _, test := range tests {\n\t\tif test.name == \"\" {\n\t\t\tt.Fatal(\"go a subtest with no name\")\n\t\t}\n\n\t\t// Run the subtest.\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := connector.Scopes{OfflineAccess: true, Groups: test.groups}\n\t\t\tident, validPW, err := conn.Login(context.Background(), s, test.username, test.password)\n\t\t\tif err != nil {\n\t\t\t\tif !test.wantErr {\n\t\t\t\t\tt.Fatalf(\"query failed: %v\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif test.wantErr {\n\t\t\t\tt.Fatalf(\"wanted query to fail\")\n\t\t\t}\n\n\t\t\tif !validPW {\n\t\t\t\tif !test.wantBadPW {\n\t\t\t\t\tt.Fatalf(\"invalid password: %v\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif test.wantBadPW {\n\t\t\t\tt.Fatalf(\"wanted invalid password\")\n\t\t\t}\n\t\t\tgot := ident\n\t\t\tgot.ConnectorData = nil\n\n\t\t\tif diff := pretty.Compare(test.want, got); diff != \"\" {\n\t\t\t\tt.Error(diff)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Verify that refresh tokens work.\n\t\t\tident, err = conn.Refresh(context.Background(), s, ident)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"refresh failed: %v\", err)\n\t\t\t}\n\n\t\t\tgot = ident\n\t\t\tgot.ConnectorData = nil\n\n\t\t\tif diff := pretty.Compare(test.want, got); diff != \"\" {\n\t\t\t\tt.Errorf(\"after refresh: %s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "connector/ldap/testdata/certs/ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC/TCCAeWgAwIBAgIJAIrt+AlVUsXKMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV\nBAMMCmxkYXAtdGVzdHMwHhcNMTcwNDEyMjAxNzI5WhcNNDQwODI4MjAxNzI5WjAV\nMRMwEQYDVQQDDApsZGFwLXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\nCgKCAQEAzKJkt2WsALUDA3tQsedx7UJKIxis05+dU5FbBxf/BMSch8gCNh/cWErH\nIDljWGwLKbc9UefIz3BzbcNBPLgLGMp7t9Pf9HCBNf7lShLZB2BEGpgpCpd0urox\nxTqMEfchssJj75HOZRweHfBDDHk8LMHQYUBn5qTiuMYvBUbPVq69argE/kt5yAEW\nCOZzzx38a11iY0gtPjY4Tc9vICsLHhTssNn/1wf+GFNzSTHqijC7NKW0txUneFQJ\nh6LAmKV/uZC84W1tqMDZKKpABiTpB+JbDvwsb9eXJ6YG6TgbKcrXjLy4ogbIrIRA\ns2DqMih792mxusIl6lRf3hTtCdyodwIDAQABo1AwTjAdBgNVHQ4EFgQUnfj9sAq4\n2xBbV4rf5FNvYaE2Bg0wHwYDVR0jBBgwFoAUnfj9sAq42xBbV4rf5FNvYaE2Bg0w\nDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFGnBH1qpLJLvrLWKNI5w\nu8pFYO3RGqmfJ3BGf60MQxdUaTIUNQxPfPATbth7t8GRJwpWESRDlaXWq9fM9rkt\nfbmuqjAMGTFloNd9ra6e2F0CKjwZWcn/3eG/mVw/5d1Ku9Ow8luKrZuzNzVJd13r\nhoNc1wYXN0pHWkNiRUuR/E4fE/sn+tYOpJ4XYQvKAcSrNrq8m5O9VG5gLvlTeNno\n6q9hBy+5XKYUdHlzbAGm9QL0e1R45Mu4qxcFluKEmzS1rXlLsLs4/pqHgreXlYgL\nf7K0cFvaJGnFRKaxa6Bpf1EPNtqSc/pQZh01Ww8CUu1xh2+5KufgJQjAHVG3a1ow\ndQ==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/ldap/testdata/certs/ca.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAzKJkt2WsALUDA3tQsedx7UJKIxis05+dU5FbBxf/BMSch8gC\nNh/cWErHIDljWGwLKbc9UefIz3BzbcNBPLgLGMp7t9Pf9HCBNf7lShLZB2BEGpgp\nCpd0uroxxTqMEfchssJj75HOZRweHfBDDHk8LMHQYUBn5qTiuMYvBUbPVq69argE\n/kt5yAEWCOZzzx38a11iY0gtPjY4Tc9vICsLHhTssNn/1wf+GFNzSTHqijC7NKW0\ntxUneFQJh6LAmKV/uZC84W1tqMDZKKpABiTpB+JbDvwsb9eXJ6YG6TgbKcrXjLy4\nogbIrIRAs2DqMih792mxusIl6lRf3hTtCdyodwIDAQABAoIBAHQpEucQbe0Q058c\nVxhF+2PlJ1R441JV3ubbMkL6mibIvNpO7QJwX5I3EIX4Ta6Z1lRd0g82dcVbXgrG\ntbeT+aie+E/Hk++cFZzjDqFXxZ7sRHycN1/tzbNZknsU2wIvuQ9STYxmxjSbG3V/\nN3BTOZdmhbVO7Cv/GTwuM+7Y3UWkc74HaXfAgo1UIO9MtqgqP3H1Tv6ZIeKzl+mP\nwrvei0eQe6jI4W6+vUOX3SlrlrMxMTLK/Ce2MP1pJx++m8Ga23+vtna+lkOWnwcD\nNmhYl4dL31sDcE6Hz/T6Wwfdlfyugw8vi3a3GEYGMIwy27CFf/ccYnWPOI3oIHDe\nRwlXLCECgYEA595xJmfUpwqgYY80pT3JG3+64NWJ7f/gH0Ey9fivZfnTegjkI2Kc\nUf7+odCq9I1TFtx10M72N4pXT1uLzJtINYty4ZIfOLG7jSraVbOuf9AvMNCYw+cT\nFcf/HGUJEE95TKYDrGfklOYFNs3ZCcKOCYJOWCuwki8Vm2vtJpV6gnkCgYEA4e5b\nDI+YworLjokY8eP4aOF5BMuiFdGkYDjVQZG45RwjJdLwBjaf+HA4pAuJAr2LWiLX\ncdKpk+3AlJ8UMLIM+hBP4hBqnrPaRTkEhTXpbUA1lvL9o0mVDFgNh90guu5TeJza\nsW7JLaStmAyCxYGxbW4LTjR8GX9DPOPmLs5ZRm8CgYAyFW5DaXIZksYJzLEGcE4c\nTn7DSdy9N+PlXGPxlYHteQUg+wKsUgSKAZZmxXfn0w77hSs9qzar0IoDbjbIP1Jd\nnn12E+YCjQGCAJugn2s12HYZCTW2Oxd4QPbt3zUR/NiqocFxYA+TygueRuB2pzue\n+jKKAQXmzZzRMYLMLsWDoQKBgAnrCcoyX5VivG7ka9jqlhQcmdBxFAt7KYkj1ZDM\nUd6U7qIRcYIEUd95JbNl4jzhj0WEtAqGIfWhgUvE9ADzQAiWQLt+1v9ii9lwGFe0\ntyuZnwCiaCoL5+Qj1Ww6c95g6f8oe51AbMp5KTm8it0axWw1YX+sZCpGYPBCXO9/\nFYI3AoGBAMacjjbPjjfOXxBRhRz1rEDTrIStDj5KM4fgslKVGysqpH/mw7gSC8SK\nqn0anL2s3SAe9PQpOzM3pFFRZx4XMOk4ojYRZtp3FjPFDRnYuYzkfkbU7eV04awO\n6nrua8KNLNK+ir9iCi46tP6Zr3F81zWGUoVArVUgCRDbA9e0swB0\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "connector/ldap/testdata/certs/dhparam.pem",
    "content": "-----BEGIN DH PARAMETERS-----\nMIIBCAKCAQEAx5y2viJKOAAcDYSj55odZsbA7dkSQ9afEPd9uaCLOvRYKLJY1S1V\nC4m1eVfna8JndSLdsBGDQe4BlBTkEYMYR8CJHtUuBxeAucOH8KlF8rIHXXi71oex\nT7kPtJEDINQKOn06bHqNcn0a7ZMWP8jiQ708OYr5P+1T/N82QTAFpDuqK42ZnBqf\n8qzQkkTN0UCktY2EWnFTbNIXcMKWQnYP8zt/CG3Q31b2bnQt2iLEa/DIF7RLNjfx\n9wPQBBAqgWbLmWfdPpHsAPtQxtItb+GRbPs3aLm06CFKlQuteDoP+suo0EtglHcV\nV9Ynvdz0cdJCJ7EPyET6CtLMzc/Puup/AwIBAg==\n-----END DH PARAMETERS-----\n"
  },
  {
    "path": "connector/ldap/testdata/certs/ldap.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIC3DCCAcSgAwIBAgIJANsmsx7hUWnHMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV\nBAMMCmxkYXAtdGVzdHMwHhcNMTcwNDEyMjAxNzI5WhcNNDQwODI4MjAxNzI5WjAU\nMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQDlWGC5X/TWgysEimM7n0hSkXRCITwAFxKG0C4EeppmL42DBcjQa0xrElRF\nh57EBZltbSfvTMDBZAyhx5oZKoETDfwy5jFzf4L4PazSkvfn4qWmCnrq4HNO5Vl7\nGBsW93bljsh2nfvoKDX2vBpEUe0qrZzJtRHq0ytfd6zXZ9+WFMsmhD9poADrH4hB\n/UOV3uCJPybOoy/WsANQpSgJPD886zakmF+54XQ3tExKzFA1rR4HJbU26h99U5kH\n346sV7/xKJLENQVIH1qsqyA1UPDZRWusABjdIPc9Racy0/MxTVE0k5lQbBvz9QSe\nHZvW+ct/aZX5tjxr9JlSY7tK2I9FAgMBAAGjMDAuMAkGA1UdEwQCMAAwCwYDVR0P\nBAQDAgXgMBQGA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEA\nRZp/fNjoQNaO6KW0Ay0aaPW6jPrcqGjzFgeIXaw/0UaWm5jhptWtjOAILV+afIrd\n4cKDg65o4xRdQYYbqmutFMAO/DeyDyMi3IL60qk0osipPDIORx5Ai2ZBQvUsGtwV\nnp9UwQGNO5AGeR9N5kndyldbpxaIJFhsKOV8uRSi+4PRbMH3G0kJIX6wwZU4Ri/k\n3lWJQfqULH0vtMQCWSJuaYHxWYFq4AM+H/zpLwg1WG2eKVgSMWotxMRi5LOFSBbG\nXuOxAb0SNBcXl6kjRYbQyHBxIJMsB1lk64g7dTJqXuYFUwmIGL/vTr6PL6EKYk65\n/aWO8cvwXOrYaf9umgcqvg==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/ldap/testdata/certs/ldap.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA5VhguV/01oMrBIpjO59IUpF0QiE8ABcShtAuBHqaZi+NgwXI\n0GtMaxJURYeexAWZbW0n70zAwWQMoceaGSqBEw38MuYxc3+C+D2s0pL35+Klpgp6\n6uBzTuVZexgbFvd25Y7Idp376Cg19rwaRFHtKq2cybUR6tMrX3es12fflhTLJoQ/\naaAA6x+IQf1Dld7giT8mzqMv1rADUKUoCTw/POs2pJhfueF0N7RMSsxQNa0eByW1\nNuoffVOZB9+OrFe/8SiSxDUFSB9arKsgNVDw2UVrrAAY3SD3PUWnMtPzMU1RNJOZ\nUGwb8/UEnh2b1vnLf2mV+bY8a/SZUmO7StiPRQIDAQABAoIBAQDHBbKqK4MkxB8I\nia8jhk4UmPTyjjSrP1pscyv75wkltA5xrQtfEj32jKlkzRQRt2o1c4w8NbbwHAp6\nOeSYAjKQfoplAS3YtMbK9XqMIc3QBPcK5/1S5gQqaw0DrR+VBpq/CvEbPm3kQUDT\nJNkGgLH3X0G4KNGrniT9a7UqGJIGgdBAr7bPESiDi9wuOwfhm/9TB8LOG8wB9cn4\nNcUipvjOcRxMFkyYtq056ZfGeoK2ooFe0lHi4j8sWXfII789OqN0plecAg8NGZsl\nklSncpTObE6eTXo9Jncio3pftvszEctKssK7vuL6opajtppT6C5FnKLb6NIAOo7j\nCPk1BRPhAoGBAPf8TMTr+l8MHRuVXEx52E1dBH46ZB8bMfvwb7cZ31Fn0EEmygCj\nwP9eKZ8MKmHVBbU6CbxYQMICTTwRrw9H0tNoaZBwzWMz/JDHcACfsPKtfrX8T4UQ\nwmVwbLctdC1Cbaxn1jYeSLoLfSe8IGPDnLpsMCzpRcQIgPS+gO69zr8vAoGBAOzB\n254TKd2OQPnvUvmAVYGRYyTu/+ShH9fZyDJYtjhQbuxt6eqh3poneWJOW+KPlqDd\nJ0a8yv1pDXmCy5k1Oo8Nubt7cPI0y2z0nm5LvAaqPaFdUJs9nq9umH3svJh6du6Z\n+TZ6MDU/eyJRq7Mc5SQrssziJidS3cU21b560xvLAoGBAPYpZY9Ia7Uz0iUSY5eq\nj7Nj9VTT45UZKsnbRxnrvckSEyDJP1XZN3iG4Sv3KI8KpWrbHNTwif/Lxx0stKin\ndDjU+Y0e3FJwRXL19lE4M68B17kQp2MAWufU7KX8oclXmoS8YmBAOZMsWmU6ErDV\neVt4j23VdaJ9inzoKhZTJcqTAoGAH9znJZsGo16lt/1ReWqgF1Ptt+bCYY6drnsM\nylnODD4m74LLXFx0jOKLH4PUMeWJLBUXWBnIZ9pfid7kb7YOL3p1aJnwVWhtiDhT\nqhxfLbZznOfmFT5xwMJtm2Tk7NBueSYXuBExs7jbZX8AUJau7/NBmPlGkTxBxGzg\nz0XQa4kCgYBxYBXwFpLLjBO+bMMkoVOlMDj7feCOWP9CsnKQSHYqPbmmb+8mA7pN\nmIWfjSVynVe+Ncn0I5Uijbs9QDYqcfApJQ+iXeb+VGrg4QkLHHGd/5kIY28Evc6A\nKVyRIuiYNmgOXGpaFpMXSw718N4U7jWW7lqUxK2rvEupFhaL52oJFQ==\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "connector/ldap/testdata/schema.ldif",
    "content": "dn: ou=TestQuery,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestQuery\n\ndn: ou=People,ou=TestQuery,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestQuery,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,ou=TestQuery,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\n\n########################################################################\n\ndn: ou=TestQueryWithEmailSuffix,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestQueryWithEmailSuffix\n\ndn: ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,ou=TestQueryWithEmailSuffix,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nuserpassword: bar\n\n########################################################################\n\ndn: ou=TestUserFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestUserFilter\n\ndn: ou=Seattle,ou=TestUserFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Seattle\n\ndn: ou=Portland,ou=TestUserFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Portland\n\ndn: ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: ou=People,ou=Portland,ou=TestUserFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=jane,ou=People,ou=Portland,ou=TestUserFilter,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoefromportland@example.com\nuserpassword: baz\n\ndn: cn=john,ou=People,ou=Seattle,ou=TestUserFilter,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\n\n########################################################################\n\ndn: ou=TestGroupQuery,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestGroupQuery\n\ndn: ou=People,ou=TestGroupQuery,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,ou=TestGroupQuery,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\n\n# Group definitions.\n\ndn: ou=Groups,ou=TestGroupQuery,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: cn=admins,ou=Groups,ou=TestGroupQuery,dc=example,dc=org\nobjectClass: groupOfNames\ncn: admins\nmember: cn=john,ou=People,ou=TestGroupQuery,dc=example,dc=org\nmember: cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org\n\ndn: cn=developers,ou=Groups,ou=TestGroupQuery,dc=example,dc=org\nobjectClass: groupOfNames\ncn: developers\nmember: cn=jane,ou=People,ou=TestGroupQuery,dc=example,dc=org\n\n########################################################################\n\ndn: ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestGroupsOnUserEntity\n\ndn: ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\n# Groups are enumerated as part of the user entity instead of the members being\n# a list on the group entity.\n\ndn: cn=jane,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\ndepartmentNumber: 1000\ndepartmentNumber: 1001\n\ndn: cn=john,ou=People,ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\ndepartmentNumber: 1000\ndepartmentNumber: 1002\n\n# Group definitions. Notice that they don't have any \"member\" field.\n\ndn: ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: cn=admins,ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: posixGroup\ncn: admins\ngidNumber: 1000\n\ndn: cn=developers,ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: posixGroup\ncn: developers\ngidNumber: 1001\n\ndn: cn=designers,ou=Groups,ou=TestGroupsOnUserEntity,dc=example,dc=org\nobjectClass: posixGroup\ncn: designers\ngidNumber: 1002\n\n########################################################################\n\ndn: ou=TestGroupFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestGroupFilter\n\ndn: ou=People,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\n\n# Group definitions.\n\ndn: ou=Seattle,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Seattle\n\ndn: ou=Portland,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Portland\n\ndn: ou=Groups,ou=Seattle,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: ou=Groups,ou=Portland,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: cn=qa,ou=Groups,ou=Portland,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: groupOfNames\ncn: qa\nmember: cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org\n\ndn: cn=admins,ou=Groups,ou=Seattle,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: groupOfNames\ncn: admins\nmember: cn=john,ou=People,ou=TestGroupFilter,dc=example,dc=org\nmember: cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org\n\ndn: cn=developers,ou=Groups,ou=Seattle,ou=TestGroupFilter,dc=example,dc=org\nobjectClass: groupOfNames\ncn: developers\nmember: cn=jane,ou=People,ou=TestGroupFilter,dc=example,dc=org\n\n########################################################################\n\ndn: ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestGroupToUserMatchers\n\ndn: ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nuid: janedoe\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nuid: johndoe\nmail: johndoe@example.com\nuserpassword: bar\n\n# Group definitions.\n\ndn: ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Seattle\n\ndn: ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Portland\n\ndn: ou=Groups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: ou=UnixGroups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: UnixGroups\n\ndn: ou=Groups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: ou=UnixGroups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: organizationalUnit\nou: UnixGroups\n\ndn: cn=qa,ou=Groups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: groupOfNames\ncn: qa\nmember: cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\n\ndn: cn=logger,ou=UnixGroups,ou=Portland,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: posixGroup\ngidNumber: 1000\ncn: logger\nmemberUid: johndoe\n\ndn: cn=admins,ou=Groups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: groupOfNames\ncn: admins\nmember: cn=john,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\nmember: cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\n\ndn: cn=developers,ou=Groups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: groupOfNames\ncn: developers\nmember: cn=jane,ou=People,ou=TestGroupToUserMatchers,dc=example,dc=org\n\ndn: cn=frontend,ou=UnixGroups,ou=Seattle,ou=TestGroupToUserMatchers,dc=example,dc=org\nobjectClass: posixGroup\ngidNumber: 1001\ncn: frontend\nmemberUid: janedoe\n\n########################################################################\n\ndn: ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestDeprecatedGroupToUserMatcher\n\ndn: ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\n\n# Group definitions.\n\ndn: ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Seattle\n\ndn: ou=Portland,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Portland\n\ndn: ou=Groups,ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: ou=Groups,ou=Portland,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: cn=qa,ou=Groups,ou=Portland,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: groupOfNames\ncn: qa\nmember: cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\n\ndn: cn=admins,ou=Groups,ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: groupOfNames\ncn: admins\nmember: cn=john,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nmember: cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\n\ndn: cn=developers,ou=Groups,ou=Seattle,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\nobjectClass: groupOfNames\ncn: developers\nmember: cn=jane,ou=People,ou=TestDeprecatedGroupToUserMatcher,dc=example,dc=org\n\n########################################################################\n\ndn: ou=TestStartTLS,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestStartTLS\n\ndn: ou=People,ou=TestStartTLS,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestStartTLS,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\n########################################################################\n\ndn: ou=TestInsecureSkipVerify,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestInsecureSkipVerify\n\ndn: ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestInsecureSkipVerify,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\n########################################################################\n\ndn: ou=TestLDAPS,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestLDAPS\n\ndn: ou=People,ou=TestLDAPS,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestLDAPS,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\n########################################################################\n\ndn: ou=TestNestedGroups,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestNestedGroups\n\ndn: ou=People,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\n\n# Group definitions.\n\ndn: ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: cn=childGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: groupOfNames\ncn: childGroup\nmember: cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org\n\ndn: cn=intermediateGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: groupOfNames\ncn: intermediateGroup\nmember: cn=childGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nmember: cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org\n\ndn: cn=parentGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: groupOfNames\ncn: parentGroup\nmember: cn=intermediateGroup,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\n\ndn: cn=circularGroup1,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: groupOfNames\ncn: circularGroup1\nmember: cn=circularGroup2,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nmember: cn=jane,ou=People,ou=TestNestedGroups,dc=example,dc=org\n\ndn: cn=circularGroup2,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nobjectClass: groupOfNames\ncn: circularGroup2\nmember: cn=circularGroup1,ou=Groups,ou=TestNestedGroups,dc=example,dc=org\nmember: cn=john,ou=People,ou=TestNestedGroups,dc=example,dc=org\n\n########################################################################\n\ndn: ou=TestUsernameWithMultipleAttributes,dc=example,dc=org\nobjectClass: organizationalUnit\nou: TestUsernameWithMultipleAttributes\n\ndn: ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Seattle\n\ndn: ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,ou=Seattle,ou=TestUsernameWithMultipleAttributes,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo"
  },
  {
    "path": "connector/linkedin/linkedin.go",
    "content": "// Package linkedin provides authentication strategies using LinkedIn\npackage linkedin\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nconst (\n\tapiURL   = \"https://api.linkedin.com/v2\"\n\tauthURL  = \"https://www.linkedin.com/oauth/v2/authorization\"\n\ttokenURL = \"https://www.linkedin.com/oauth/v2/accessToken\"\n)\n\n// Config holds configuration options for LinkedIn logins.\ntype Config struct {\n\tClientID     string `json:\"clientID\"`\n\tClientSecret string `json:\"clientSecret\"`\n\tRedirectURI  string `json:\"redirectURI\"`\n}\n\n// Open returns a strategy for logging in through LinkedIn\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\treturn &linkedInConnector{\n\t\toauth2Config: &oauth2.Config{\n\t\t\tClientID:     c.ClientID,\n\t\t\tClientSecret: c.ClientSecret,\n\t\t\tEndpoint: oauth2.Endpoint{\n\t\t\t\tAuthURL:  authURL,\n\t\t\t\tTokenURL: tokenURL,\n\t\t\t},\n\t\t\tScopes:      []string{\"r_liteprofile\", \"r_emailaddress\"},\n\t\t\tRedirectURL: c.RedirectURI,\n\t\t},\n\t\tlogger: logger.With(slog.Group(\"connector\", \"type\", \"linkedin\", \"id\", id)),\n\t}, nil\n}\n\ntype connectorData struct {\n\tAccessToken string `json:\"accessToken\"`\n}\n\nvar (\n\t_ connector.CallbackConnector = (*linkedInConnector)(nil)\n\t_ connector.RefreshConnector  = (*linkedInConnector)(nil)\n)\n\ntype linkedInConnector struct {\n\toauth2Config *oauth2.Config\n\tlogger       *slog.Logger\n}\n\n// LoginURL returns an access token request URL\nfunc (c *linkedInConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.oauth2Config.RedirectURL != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\",\n\t\t\tcallbackURL, c.oauth2Config.RedirectURL)\n\t}\n\n\treturn c.oauth2Config.AuthCodeURL(state), nil, nil\n}\n\n// HandleCallback handles HTTP redirect from LinkedIn\nfunc (c *linkedInConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\tctx := r.Context()\n\ttoken, err := c.oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"linkedin: get token: %v\", err)\n\t}\n\n\tclient := c.oauth2Config.Client(ctx, token)\n\tprofile, err := c.profile(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"linkedin: get profile: %v\", err)\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:        profile.ID,\n\t\tUsername:      profile.fullname(),\n\t\tEmail:         profile.Email,\n\t\tEmailVerified: true,\n\t}\n\n\tif s.OfflineAccess {\n\t\tdata := connectorData{AccessToken: token.AccessToken}\n\t\tconnData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"linkedin: marshal connector data: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\nfunc (c *linkedInConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {\n\tif len(ident.ConnectorData) == 0 {\n\t\treturn ident, fmt.Errorf(\"linkedin: no upstream access token found\")\n\t}\n\n\tvar data connectorData\n\tif err := json.Unmarshal(ident.ConnectorData, &data); err != nil {\n\t\treturn ident, fmt.Errorf(\"linkedin: unmarshal access token: %v\", err)\n\t}\n\n\tclient := c.oauth2Config.Client(ctx, &oauth2.Token{AccessToken: data.AccessToken})\n\tprofile, err := c.profile(ctx, client)\n\tif err != nil {\n\t\treturn ident, fmt.Errorf(\"linkedin: get profile: %v\", err)\n\t}\n\n\tident.Username = profile.fullname()\n\tident.Email = profile.Email\n\n\treturn ident, nil\n}\n\ntype profile struct {\n\tID        string `json:\"id\"`\n\tFirstName string `json:\"localizedFirstName\"`\n\tLastName  string `json:\"localizedLastName\"`\n\tEmail     string `json:\"emailAddress\"`\n}\n\ntype emailresp struct {\n\tElements []struct {\n\t\tHandle struct {\n\t\t\tEmailAddress string `json:\"emailAddress\"`\n\t\t} `json:\"handle~\"`\n\t} `json:\"elements\"`\n}\n\n// fullname returns a full name of a person, or email if the resulting name is\n// empty\nfunc (p profile) fullname() string {\n\tfname := strings.TrimSpace(p.FirstName + \" \" + p.LastName)\n\tif fname == \"\" {\n\t\treturn p.Email\n\t}\n\n\treturn fname\n}\n\nfunc (c *linkedInConnector) primaryEmail(ctx context.Context, client *http.Client) (email string, err error) {\n\treq, err := http.NewRequest(\"GET\", apiURL+\"/emailAddress?q=members&projection=(elements*(handle~))\", nil)\n\tif err != nil {\n\t\treturn email, fmt.Errorf(\"new req: %v\", err)\n\t}\n\n\tresp, err := client.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn email, fmt.Errorf(\"get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn email, fmt.Errorf(\"read body: %v\", err)\n\t}\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn email, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tvar parsedResp emailresp\n\terr = json.Unmarshal(body, &parsedResp)\n\tif err == nil {\n\t\tfor _, elem := range parsedResp.Elements {\n\t\t\temail = elem.Handle.EmailAddress\n\t\t}\n\t}\n\n\tif email == \"\" {\n\t\terr = fmt.Errorf(\"email is not set\")\n\t}\n\n\treturn email, err\n}\n\nfunc (c *linkedInConnector) profile(ctx context.Context, client *http.Client) (p profile, err error) {\n\t// https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api\n\t// https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/primary-contact-api\n\t// https://docs.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/migration-faq#how-do-i-retrieve-the-members-email-address\n\treq, err := http.NewRequest(\"GET\", apiURL+\"/me\", nil)\n\tif err != nil {\n\t\treturn p, fmt.Errorf(\"new req: %v\", err)\n\t}\n\n\tresp, err := client.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn p, fmt.Errorf(\"get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn p, fmt.Errorf(\"read body: %v\", err)\n\t\t}\n\t\treturn p, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(&p); err != nil {\n\t\treturn p, fmt.Errorf(\"JSON decode: %v\", err)\n\t}\n\n\temail, err := c.primaryEmail(ctx, client)\n\tif err != nil {\n\t\treturn p, fmt.Errorf(\"fetching email: %v\", err)\n\t}\n\tp.Email = email\n\n\treturn p, err\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n"
  },
  {
    "path": "connector/microsoft/microsoft.go",
    "content": "// Package microsoft provides authentication strategies using Microsoft.\npackage microsoft\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\tgroups_pkg \"github.com/dexidp/dex/pkg/groups\"\n)\n\n// GroupNameFormat represents the format of the group identifier\n// we use type of string instead of int because it's easier to\n// marshall/unmarshall\ntype GroupNameFormat string\n\n// Possible values for GroupNameFormat\nconst (\n\tGroupID   GroupNameFormat = \"id\"\n\tGroupName GroupNameFormat = \"name\"\n)\n\nconst (\n\t// Microsoft requires this scope to access user's profile\n\tscopeUser = \"user.read\"\n\t// Microsoft requires this scope to list groups the user is a member of\n\t// and resolve their ids to groups names.\n\tscopeGroups = \"directory.read.all\"\n\t// Microsoft requires this scope to return a refresh token\n\t// see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#offline_access\n\tscopeOfflineAccess = \"offline_access\"\n)\n\n// Config holds configuration options for microsoft logins.\ntype Config struct {\n\tClientID             string          `json:\"clientID\"`\n\tClientSecret         string          `json:\"clientSecret\"`\n\tRedirectURI          string          `json:\"redirectURI\"`\n\tTenant               string          `json:\"tenant\"`\n\tOnlySecurityGroups   bool            `json:\"onlySecurityGroups\"`\n\tGroups               []string        `json:\"groups\"`\n\tGroupNameFormat      GroupNameFormat `json:\"groupNameFormat\"`\n\tUseGroupsAsWhitelist bool            `json:\"useGroupsAsWhitelist\"`\n\tEmailToLowercase     bool            `json:\"emailToLowercase\"`\n\n\tAPIURL   string `json:\"apiURL\"`\n\tGraphURL string `json:\"graphURL\"`\n\n\t// PromptType is used for the prompt query parameter.\n\t// For valid values, see https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow#request-an-authorization-code.\n\tPromptType string `json:\"promptType\"`\n\tDomainHint string `json:\"domainHint\"`\n\n\tScopes []string `json:\"scopes\"` // defaults to scopeUser (user.read)\n}\n\n// Open returns a strategy for logging in through Microsoft.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tm := microsoftConnector{\n\t\tapiURL:               strings.TrimSuffix(c.APIURL, \"/\"),\n\t\tgraphURL:             strings.TrimSuffix(c.GraphURL, \"/\"),\n\t\tredirectURI:          c.RedirectURI,\n\t\tclientID:             c.ClientID,\n\t\tclientSecret:         c.ClientSecret,\n\t\ttenant:               c.Tenant,\n\t\tonlySecurityGroups:   c.OnlySecurityGroups,\n\t\tgroups:               c.Groups,\n\t\tgroupNameFormat:      c.GroupNameFormat,\n\t\tuseGroupsAsWhitelist: c.UseGroupsAsWhitelist,\n\t\tlogger:               logger.With(slog.Group(\"connector\", \"type\", \"microsoft\", \"id\", id)),\n\t\temailToLowercase:     c.EmailToLowercase,\n\t\tpromptType:           c.PromptType,\n\t\tdomainHint:           c.DomainHint,\n\t\tscopes:               c.Scopes,\n\t}\n\n\tif m.apiURL == \"\" {\n\t\tm.apiURL = \"https://login.microsoftonline.com\"\n\t}\n\n\tif m.graphURL == \"\" {\n\t\tm.graphURL = \"https://graph.microsoft.com\"\n\t}\n\n\t// By default allow logins from both personal and business/school\n\t// accounts.\n\tif m.tenant == \"\" {\n\t\tm.tenant = \"common\"\n\t}\n\n\t// By default, use group names\n\tswitch m.groupNameFormat {\n\tcase \"\":\n\t\tm.groupNameFormat = GroupName\n\tcase GroupID, GroupName:\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"invalid groupNameFormat: %s\", m.groupNameFormat)\n\t}\n\n\treturn &m, nil\n}\n\ntype connectorData struct {\n\tAccessToken  string    `json:\"accessToken\"`\n\tRefreshToken string    `json:\"refreshToken\"`\n\tExpiry       time.Time `json:\"expiry\"`\n}\n\nvar (\n\t_ connector.CallbackConnector = (*microsoftConnector)(nil)\n\t_ connector.RefreshConnector  = (*microsoftConnector)(nil)\n)\n\ntype microsoftConnector struct {\n\tapiURL               string\n\tgraphURL             string\n\tredirectURI          string\n\tclientID             string\n\tclientSecret         string\n\ttenant               string\n\tonlySecurityGroups   bool\n\tgroupNameFormat      GroupNameFormat\n\tgroups               []string\n\tuseGroupsAsWhitelist bool\n\tlogger               *slog.Logger\n\temailToLowercase     bool\n\tpromptType           string\n\tdomainHint           string\n\tscopes               []string\n}\n\nfunc (c *microsoftConnector) isOrgTenant() bool {\n\treturn c.tenant != \"common\" && c.tenant != \"consumers\" && c.tenant != \"organizations\"\n}\n\nfunc (c *microsoftConnector) groupsRequired(groupScope bool) bool {\n\treturn (len(c.groups) > 0 || groupScope) && c.isOrgTenant()\n}\n\nfunc (c *microsoftConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {\n\tvar microsoftScopes []string\n\tif len(c.scopes) > 0 {\n\t\tmicrosoftScopes = c.scopes\n\t} else {\n\t\tmicrosoftScopes = append(microsoftScopes, scopeUser)\n\t}\n\tif c.groupsRequired(scopes.Groups) {\n\t\tmicrosoftScopes = append(microsoftScopes, scopeGroups)\n\t}\n\n\tif scopes.OfflineAccess {\n\t\tmicrosoftScopes = append(microsoftScopes, scopeOfflineAccess)\n\t}\n\n\treturn &oauth2.Config{\n\t\tClientID:     c.clientID,\n\t\tClientSecret: c.clientSecret,\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL:  c.apiURL + \"/\" + c.tenant + \"/oauth2/v2.0/authorize\",\n\t\t\tTokenURL: c.apiURL + \"/\" + c.tenant + \"/oauth2/v2.0/token\",\n\t\t},\n\t\tScopes:      microsoftScopes,\n\t\tRedirectURL: c.redirectURI,\n\t}\n}\n\nfunc (c *microsoftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", callbackURL, c.redirectURI)\n\t}\n\n\tvar options []oauth2.AuthCodeOption\n\tif c.promptType != \"\" {\n\t\toptions = append(options, oauth2.SetAuthURLParam(\"prompt\", c.promptType))\n\t}\n\tif c.domainHint != \"\" {\n\t\toptions = append(options, oauth2.SetAuthURLParam(\"domain_hint\", c.domainHint))\n\t}\n\n\treturn c.oauth2Config(scopes).AuthCodeURL(state, options...), nil, nil\n}\n\nfunc (c *microsoftConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\toauth2Config := c.oauth2Config(s)\n\n\tctx := r.Context()\n\n\ttoken, err := oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"microsoft: failed to get token: %v\", err)\n\t}\n\n\tclient := oauth2Config.Client(ctx, token)\n\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"microsoft: get user: %v\", err)\n\t}\n\n\tif c.emailToLowercase {\n\t\tuser.Email = strings.ToLower(user.Email)\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:        user.ID,\n\t\tUsername:      user.Name,\n\t\tEmail:         user.Email,\n\t\tEmailVerified: true,\n\t}\n\n\tif c.groupsRequired(s.Groups) {\n\t\tgroups, err := c.getGroups(ctx, client, user.ID)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"microsoft: get groups: %w\", err)\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\tif s.OfflineAccess {\n\t\tdata := connectorData{\n\t\t\tAccessToken:  token.AccessToken,\n\t\t\tRefreshToken: token.RefreshToken,\n\t\t\tExpiry:       token.Expiry,\n\t\t}\n\t\tconnData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"microsoft: marshal connector data: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\ntype tokenNotifyFunc func(*oauth2.Token) error\n\n// notifyRefreshTokenSource is essentially `oauth2.ReuseTokenSource` with `TokenNotifyFunc` added.\ntype notifyRefreshTokenSource struct {\n\tnew oauth2.TokenSource\n\tmu  sync.Mutex // guards t\n\tt   *oauth2.Token\n\tf   tokenNotifyFunc // called when token refreshed so new refresh token can be persisted\n}\n\n// Token returns the current token if it's still valid, else will\n// refresh the current token (using r.Context for HTTP client\n// information) and return the new one.\nfunc (s *notifyRefreshTokenSource) Token() (*oauth2.Token, error) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif s.t.Valid() {\n\t\treturn s.t, nil\n\t}\n\tt, err := s.new.Token()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.t = t\n\treturn t, s.f(t)\n}\n\nfunc (c *microsoftConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\tif len(identity.ConnectorData) == 0 {\n\t\treturn identity, errors.New(\"microsoft: no upstream access token found\")\n\t}\n\n\tvar data connectorData\n\tif err := json.Unmarshal(identity.ConnectorData, &data); err != nil {\n\t\treturn identity, fmt.Errorf(\"microsoft: unmarshal access token: %v\", err)\n\t}\n\ttok := &oauth2.Token{\n\t\tAccessToken:  data.AccessToken,\n\t\tRefreshToken: data.RefreshToken,\n\t\tExpiry:       data.Expiry,\n\t}\n\n\tclient := oauth2.NewClient(ctx, &notifyRefreshTokenSource{\n\t\tnew: c.oauth2Config(s).TokenSource(ctx, tok),\n\t\tt:   tok,\n\t\tf: func(tok *oauth2.Token) error {\n\t\t\tdata := connectorData{\n\t\t\t\tAccessToken:  tok.AccessToken,\n\t\t\t\tRefreshToken: tok.RefreshToken,\n\t\t\t\tExpiry:       tok.Expiry,\n\t\t\t}\n\t\t\tconnData, err := json.Marshal(data)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"microsoft: marshal connector data: %v\", err)\n\t\t\t}\n\t\t\tidentity.ConnectorData = connData\n\t\t\treturn nil\n\t\t},\n\t})\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"microsoft: get user: %v\", err)\n\t}\n\n\tidentity.Username = user.Name\n\tidentity.Email = user.Email\n\n\tif c.groupsRequired(s.Groups) {\n\t\tgroups, err := c.getGroups(ctx, client, user.ID)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"microsoft: get groups: %w\", err)\n\t\t}\n\t\tidentity.Groups = groups\n\t}\n\n\treturn identity, nil\n}\n\n// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/user\n// id                - The unique identifier for the user. Inherited from\n//\n//\tdirectoryObject. Key. Not nullable. Read-only.\n//\n// displayName       - The name displayed in the address book for the user.\n//\n//\tThis is usually the combination of the user's first name,\n//\tmiddle initial and last name. This property is required\n//\twhen a user is created and it cannot be cleared during\n//\tupdates. Supports $filter and $orderby.\n//\n// userPrincipalName - The user principal name (UPN) of the user.\n//\n//\tThe UPN is an Internet-style login name for the user\n//\tbased on the Internet standard RFC 822. By convention,\n//\tthis should map to the user's email name. The general\n//\tformat is alias@domain, where domain must be present in\n//\tthe tenant’s collection of verified domains. This\n//\tproperty is required when a user is created. The\n//\tverified domains for the tenant can be accessed from the\n//\tverifiedDomains property of organization. Supports\n//\t$filter and $orderby.\ntype user struct {\n\tID    string `json:\"id\"`\n\tName  string `json:\"displayName\"`\n\tEmail string `json:\"userPrincipalName\"`\n}\n\nfunc (c *microsoftConnector) user(ctx context.Context, client *http.Client) (u user, err error) {\n\t// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_get\n\treq, err := http.NewRequest(\"GET\", c.graphURL+\"/v1.0/me?$select=id,displayName,userPrincipalName\", nil)\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"new req: %v\", err)\n\t}\n\n\tresp, err := client.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn u, newGraphError(resp.Body)\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(&u); err != nil {\n\t\treturn u, fmt.Errorf(\"JSON decode: %v\", err)\n\t}\n\n\treturn u, err\n}\n\n// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/resources/group\n// displayName - The display name for the group. This property is required when\n//\n//\ta group is created and it cannot be cleared during updates.\n//\tSupports $filter and $orderby.\ntype group struct {\n\tName string `json:\"displayName\"`\n}\n\nfunc (c *microsoftConnector) getGroups(ctx context.Context, client *http.Client, userID string) ([]string, error) {\n\tuserGroups, err := c.getGroupIDs(ctx, client)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.groupNameFormat == GroupName {\n\t\tuserGroups, err = c.getGroupNames(ctx, client, userGroups)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// ensure that the user is in at least one required group\n\tfilteredGroups := groups_pkg.Filter(userGroups, c.groups)\n\tif len(c.groups) > 0 && len(filteredGroups) == 0 {\n\t\treturn nil, &connector.UserNotInRequiredGroupsError{UserID: userID, Groups: c.groups}\n\t} else if c.useGroupsAsWhitelist {\n\t\treturn filteredGroups, nil\n\t}\n\n\treturn userGroups, nil\n}\n\nfunc (c *microsoftConnector) getGroupIDs(ctx context.Context, client *http.Client) (ids []string, err error) {\n\t// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/user_getmembergroups\n\tin := &struct {\n\t\tSecurityEnabledOnly bool `json:\"securityEnabledOnly\"`\n\t}{c.onlySecurityGroups}\n\treqURL := c.graphURL + \"/v1.0/me/getMemberGroups\"\n\tfor {\n\t\tvar out []string\n\t\tvar next string\n\n\t\tnext, err = c.post(ctx, client, reqURL, in, &out)\n\t\tif err != nil {\n\t\t\treturn ids, err\n\t\t}\n\n\t\tids = append(ids, out...)\n\t\tif next == \"\" {\n\t\t\treturn\n\t\t}\n\t\treqURL = next\n\t}\n}\n\nfunc (c *microsoftConnector) getGroupNames(ctx context.Context, client *http.Client, ids []string) (groups []string, err error) {\n\tif len(ids) == 0 {\n\t\treturn\n\t}\n\n\t// https://developer.microsoft.com/en-us/graph/docs/api-reference/v1.0/api/directoryobject_getbyids\n\tin := &struct {\n\t\tIDs   []string `json:\"ids\"`\n\t\tTypes []string `json:\"types\"`\n\t}{ids, []string{\"group\"}}\n\treqURL := c.graphURL + \"/v1.0/directoryObjects/getByIds\"\n\tfor {\n\t\tvar out []group\n\t\tvar next string\n\n\t\tnext, err = c.post(ctx, client, reqURL, in, &out)\n\t\tif err != nil {\n\t\t\treturn groups, err\n\t\t}\n\n\t\tfor _, g := range out {\n\t\t\tgroups = append(groups, g.Name)\n\t\t}\n\t\tif next == \"\" {\n\t\t\treturn\n\t\t}\n\t\treqURL = next\n\t}\n}\n\nfunc (c *microsoftConnector) post(ctx context.Context, client *http.Client, reqURL string, in interface{}, out interface{}) (string, error) {\n\tvar payload bytes.Buffer\n\n\terr := json.NewEncoder(&payload).Encode(in)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"microsoft: JSON encode: %v\", err)\n\t}\n\n\treq, err := http.NewRequest(\"POST\", reqURL, &payload)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"new req: %v\", err)\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"post URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", newGraphError(resp.Body)\n\t}\n\n\tvar next string\n\tif err = json.NewDecoder(resp.Body).Decode(&struct {\n\t\tNextLink *string     `json:\"@odata.nextLink\"`\n\t\tValue    interface{} `json:\"value\"`\n\t}{&next, out}); err != nil {\n\t\treturn \"\", fmt.Errorf(\"JSON decode: %v\", err)\n\t}\n\n\treturn next, nil\n}\n\ntype graphError struct {\n\tCode    string `json:\"code\"`\n\tMessage string `json:\"message\"`\n}\n\nfunc (e *graphError) Error() string {\n\treturn e.Code + \": \" + e.Message\n}\n\nfunc newGraphError(r io.Reader) error {\n\t// https://developer.microsoft.com/en-us/graph/docs/concepts/errors\n\tvar ge graphError\n\tif err := json.NewDecoder(r).Decode(&struct {\n\t\tError *graphError `json:\"error\"`\n\t}{&ge}); err != nil {\n\t\treturn fmt.Errorf(\"JSON error decode: %v\", err)\n\t}\n\treturn &ge\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n"
  },
  {
    "path": "connector/microsoft/microsoft_test.go",
    "content": "package microsoft\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\ntype testResponse struct {\n\tdata interface{}\n}\n\nconst (\n\ttenant   = \"9b1c3439-a67e-4e92-bb0d-0571d44ca965\"\n\tclientID = \"a115ebf3-6020-4384-8eb1-c0c42e667b6f\"\n)\n\nvar dummyToken = testResponse{data: map[string]interface{}{\n\t\"access_token\": \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9\",\n\t\"expires_in\":   \"30\",\n}}\n\nfunc TestLoginURL(t *testing.T) {\n\ttestURL := \"https://test.com\"\n\ttestState := \"some-state\"\n\n\tconn := microsoftConnector{\n\t\tapiURL:      testURL,\n\t\tgraphURL:    testURL,\n\t\tredirectURI: testURL,\n\t\tclientID:    clientID,\n\t\ttenant:      tenant,\n\t}\n\n\tloginURL, _, _ := conn.LoginURL(connector.Scopes{}, conn.redirectURI, testState)\n\n\tparsedLoginURL, _ := url.Parse(loginURL)\n\tqueryParams := parsedLoginURL.Query()\n\n\texpectEquals(t, parsedLoginURL.Path, \"/\"+tenant+\"/oauth2/v2.0/authorize\")\n\texpectEquals(t, queryParams.Get(\"client_id\"), clientID)\n\texpectEquals(t, queryParams.Get(\"redirect_uri\"), testURL)\n\texpectEquals(t, queryParams.Get(\"response_type\"), \"code\")\n\texpectEquals(t, queryParams.Get(\"scope\"), \"user.read\")\n\texpectEquals(t, queryParams.Get(\"state\"), testState)\n\texpectEquals(t, queryParams.Get(\"prompt\"), \"\")\n\texpectEquals(t, queryParams.Get(\"domain_hint\"), \"\")\n}\n\nfunc TestLoginURLWithOptions(t *testing.T) {\n\ttestURL := \"https://test.com\"\n\tpromptType := \"consent\"\n\tdomainHint := \"domain.hint\"\n\n\tconn := microsoftConnector{\n\t\tapiURL:      testURL,\n\t\tgraphURL:    testURL,\n\t\tredirectURI: testURL,\n\t\tclientID:    clientID,\n\t\ttenant:      tenant,\n\n\t\tpromptType: promptType,\n\t\tdomainHint: domainHint,\n\t}\n\n\tloginURL, _, _ := conn.LoginURL(connector.Scopes{}, conn.redirectURI, \"some-state\")\n\n\tparsedLoginURL, _ := url.Parse(loginURL)\n\tqueryParams := parsedLoginURL.Query()\n\n\texpectEquals(t, queryParams.Get(\"prompt\"), promptType)\n\texpectEquals(t, queryParams.Get(\"domain_hint\"), domainHint)\n}\n\nfunc TestUserIdentityFromGraphAPI(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/v1.0/me?$select=id,displayName,userPrincipalName\": {\n\t\t\tdata: user{ID: \"S56767889\", Name: \"Jane Doe\", Email: \"jane.doe@example.com\"},\n\t\t},\n\t\t\"/\" + tenant + \"/oauth2/v2.0/token\": dummyToken,\n\t})\n\tdefer s.Close()\n\n\treq, _ := http.NewRequest(\"GET\", s.URL, nil)\n\n\tc := microsoftConnector{apiURL: s.URL, graphURL: s.URL, tenant: tenant}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: false}, nil, req)\n\texpectNil(t, err)\n\texpectEquals(t, identity.Username, \"Jane Doe\")\n\texpectEquals(t, identity.UserID, \"S56767889\")\n\texpectEquals(t, identity.PreferredUsername, \"\")\n\texpectEquals(t, identity.Email, \"jane.doe@example.com\")\n\texpectEquals(t, identity.EmailVerified, true)\n\texpectEquals(t, len(identity.Groups), 0)\n}\n\nfunc TestUserGroupsFromGraphAPI(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/v1.0/me?$select=id,displayName,userPrincipalName\": {data: user{}},\n\t\t\"/v1.0/me/getMemberGroups\": {data: map[string]interface{}{\n\t\t\t\"value\": []string{\"a\", \"b\"},\n\t\t}},\n\t\t\"/\" + tenant + \"/oauth2/v2.0/token\": dummyToken,\n\t})\n\tdefer s.Close()\n\n\treq, _ := http.NewRequest(\"GET\", s.URL, nil)\n\n\tc := microsoftConnector{apiURL: s.URL, graphURL: s.URL, tenant: tenant}\n\tidentity, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\texpectNil(t, err)\n\texpectEquals(t, identity.Groups, []string{\"a\", \"b\"})\n}\n\nfunc TestUserNotInRequiredGroupFromGraphAPI(t *testing.T) {\n\ts := newTestServer(map[string]testResponse{\n\t\t\"/v1.0/me?$select=id,displayName,userPrincipalName\": {\n\t\t\tdata: user{ID: \"user-id-123\", Name: \"Jane Doe\", Email: \"jane.doe@example.com\"},\n\t\t},\n\t\t// The user is a member of groups \"c\" and \"d\", but the connector only\n\t\t// allows group \"a\" — so the user should be denied.\n\t\t\"/v1.0/me/getMemberGroups\": {data: map[string]interface{}{\n\t\t\t\"value\": []string{\"c\", \"d\"},\n\t\t}},\n\t\t\"/\" + tenant + \"/oauth2/v2.0/token\": dummyToken,\n\t})\n\tdefer s.Close()\n\n\treq, _ := http.NewRequest(\"GET\", s.URL, nil)\n\n\tc := microsoftConnector{\n\t\tapiURL:   s.URL,\n\t\tgraphURL: s.URL,\n\t\ttenant:   tenant,\n\t\tgroups:   []string{\"a\"},\n\t}\n\t_, err := c.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\tif err == nil {\n\t\tt.Fatal(\"expected error when user is not in any required group, got nil\")\n\t}\n\n\tvar groupsErr *connector.UserNotInRequiredGroupsError\n\tif !errors.As(err, &groupsErr) {\n\t\tt.Errorf(\"expected *connector.UserNotInRequiredGroupsError, got %T: %v\", err, err)\n\t}\n}\n\nfunc newTestServer(responses map[string]testResponse) *httptest.Server {\n\ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresponse, found := responses[r.RequestURI]\n\t\tif !found {\n\t\t\tfmt.Fprintf(os.Stderr, \"Mock response for %q not found\\n\", r.RequestURI)\n\t\t\thttp.NotFound(w, r)\n\t\t\treturn\n\t\t}\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(response.data)\n\t}))\n\treturn s\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Errorf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/mock/connectortest.go",
    "content": "// Package mock implements connectors which help test various server components.\npackage mock\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// NewCallbackConnector returns a mock connector which requires no user interaction. It always returns\n// the same (fake) identity.\nfunc NewCallbackConnector(logger *slog.Logger) connector.Connector {\n\treturn &Callback{\n\t\tIdentity: connector.Identity{\n\t\t\tUserID:        \"0-385-28089-0\",\n\t\t\tUsername:      \"Kilgore Trout\",\n\t\t\tEmail:         \"kilgore@kilgore.trout\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"authors\"},\n\t\t\tConnectorData: connectorData,\n\t\t},\n\t\tLogger: logger,\n\t}\n}\n\nvar (\n\t_ connector.CallbackConnector      = &Callback{}\n\t_ connector.RefreshConnector       = &Callback{}\n\t_ connector.TokenIdentityConnector = &Callback{}\n)\n\n// Callback is a connector that requires no user interaction and always returns the same identity.\ntype Callback struct {\n\t// The returned identity.\n\tIdentity connector.Identity\n\tLogger   *slog.Logger\n}\n\n// LoginURL returns the URL to redirect the user to login with.\nfunc (m *Callback) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tu, err := url.Parse(callbackURL)\n\tif err != nil {\n\t\treturn \"\", nil, fmt.Errorf(\"failed to parse callbackURL %q: %v\", callbackURL, err)\n\t}\n\tv := u.Query()\n\tv.Set(\"state\", state)\n\tu.RawQuery = v.Encode()\n\treturn u.String(), nil, nil\n}\n\nvar connectorData = []byte(\"foobar\")\n\n// HandleCallback parses the request and returns the user's identity\nfunc (m *Callback) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (connector.Identity, error) {\n\treturn m.Identity, nil\n}\n\n// Refresh updates the identity during a refresh token request.\nfunc (m *Callback) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\treturn m.Identity, nil\n}\n\nfunc (m *Callback) TokenIdentity(ctx context.Context, subjectTokenType, subjectToken string) (connector.Identity, error) {\n\treturn m.Identity, nil\n}\n\n// CallbackConfig holds the configuration parameters for a connector which requires no interaction.\ntype CallbackConfig struct{}\n\n// Open returns an authentication strategy which requires no user interaction.\nfunc (c *CallbackConfig) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tlogger = logger.With(slog.Group(\"connector\", \"type\", \"callback\", \"id\", id))\n\treturn NewCallbackConnector(logger), nil\n}\n\n// PasswordConfig holds the configuration for a mock connector which prompts for the supplied\n// username and password.\ntype PasswordConfig struct {\n\tUsername string `json:\"username\"`\n\tPassword string `json:\"password\"`\n}\n\n// Open returns an authentication strategy which prompts for a predefined username and password.\nfunc (c *PasswordConfig) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tif c.Username == \"\" {\n\t\treturn nil, errors.New(\"no username supplied\")\n\t}\n\tif c.Password == \"\" {\n\t\treturn nil, errors.New(\"no password supplied\")\n\t}\n\treturn &passwordConnector{c.Username, c.Password, logger}, nil\n}\n\nvar (\n\t_ connector.PasswordConnector = passwordConnector{}\n\t_ connector.RefreshConnector  = passwordConnector{}\n)\n\ntype passwordConnector struct {\n\tusername string\n\tpassword string\n\tlogger   *slog.Logger\n}\n\nfunc (p passwordConnector) Close() error { return nil }\n\nfunc (p passwordConnector) Login(ctx context.Context, s connector.Scopes, username, password string) (identity connector.Identity, validPassword bool, err error) {\n\tif username == p.username && password == p.password {\n\t\treturn connector.Identity{\n\t\t\tUserID:        \"0-385-28089-0\",\n\t\t\tUsername:      \"Kilgore Trout\",\n\t\t\tEmail:         \"kilgore@kilgore.trout\",\n\t\t\tEmailVerified: true,\n\t\t\tConnectorData: []byte(`{\"test\": \"true\"}`),\n\t\t}, true, nil\n\t}\n\treturn identity, false, nil\n}\n\nfunc (p passwordConnector) Prompt() string { return \"\" }\n\nfunc (p passwordConnector) Refresh(_ context.Context, _ connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\treturn identity, nil\n}\n"
  },
  {
    "path": "connector/oauth/oauth.go",
    "content": "package oauth\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/httpclient\"\n)\n\nvar _ connector.CallbackConnector = (*oauthConnector)(nil)\n\ntype oauthConnector struct {\n\tclientID             string\n\tclientSecret         string\n\tredirectURI          string\n\ttokenURL             string\n\tauthorizationURL     string\n\tuserInfoURL          string\n\tscopes               []string\n\tuserIDKey            string\n\tuserNameKey          string\n\tpreferredUsernameKey string\n\temailKey             string\n\temailVerifiedKey     string\n\tgroupsKey            string\n\thttpClient           *http.Client\n\tlogger               *slog.Logger\n}\n\ntype connectorData struct {\n\tAccessToken string\n}\n\ntype Config struct {\n\tClientID           string   `json:\"clientID\"`\n\tClientSecret       string   `json:\"clientSecret\"`\n\tRedirectURI        string   `json:\"redirectURI\"`\n\tTokenURL           string   `json:\"tokenURL\"`\n\tAuthorizationURL   string   `json:\"authorizationURL\"`\n\tUserInfoURL        string   `json:\"userInfoURL\"`\n\tScopes             []string `json:\"scopes\"`\n\tRootCAs            []string `json:\"rootCAs\"`\n\tInsecureSkipVerify bool     `json:\"insecureSkipVerify\"`\n\tUserIDKey          string   `json:\"userIDKey\"` // defaults to \"id\"\n\tClaimMapping       struct {\n\t\tUserNameKey          string `json:\"userNameKey\"`          // defaults to \"user_name\"\n\t\tPreferredUsernameKey string `json:\"preferredUsernameKey\"` // defaults to \"preferred_username\"\n\t\tGroupsKey            string `json:\"groupsKey\"`            // defaults to \"groups\"\n\t\tEmailKey             string `json:\"emailKey\"`             // defaults to \"email\"\n\t\tEmailVerifiedKey     string `json:\"emailVerifiedKey\"`     // defaults to \"email_verified\"\n\t} `json:\"claimMapping\"`\n}\n\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tvar err error\n\n\tuserIDKey := c.UserIDKey\n\tif userIDKey == \"\" {\n\t\tuserIDKey = \"id\"\n\t}\n\n\tuserNameKey := c.ClaimMapping.UserNameKey\n\tif userNameKey == \"\" {\n\t\tuserNameKey = \"user_name\"\n\t}\n\n\tpreferredUsernameKey := c.ClaimMapping.PreferredUsernameKey\n\tif preferredUsernameKey == \"\" {\n\t\tpreferredUsernameKey = \"preferred_username\"\n\t}\n\n\tgroupsKey := c.ClaimMapping.GroupsKey\n\tif groupsKey == \"\" {\n\t\tgroupsKey = \"groups\"\n\t}\n\n\temailKey := c.ClaimMapping.EmailKey\n\tif emailKey == \"\" {\n\t\temailKey = \"email\"\n\t}\n\n\temailVerifiedKey := c.ClaimMapping.EmailVerifiedKey\n\tif emailVerifiedKey == \"\" {\n\t\temailVerifiedKey = \"email_verified\"\n\t}\n\n\toauthConn := &oauthConnector{\n\t\tclientID:             c.ClientID,\n\t\tclientSecret:         c.ClientSecret,\n\t\ttokenURL:             c.TokenURL,\n\t\tauthorizationURL:     c.AuthorizationURL,\n\t\tuserInfoURL:          c.UserInfoURL,\n\t\tscopes:               c.Scopes,\n\t\tredirectURI:          c.RedirectURI,\n\t\tlogger:               logger.With(slog.Group(\"connector\", \"type\", \"oauth\", \"id\", id)),\n\t\tuserIDKey:            userIDKey,\n\t\tuserNameKey:          userNameKey,\n\t\tpreferredUsernameKey: preferredUsernameKey,\n\t\tgroupsKey:            groupsKey,\n\t\temailKey:             emailKey,\n\t\temailVerifiedKey:     emailVerifiedKey,\n\t}\n\n\toauthConn.httpClient, err = httpclient.NewHTTPClient(c.RootCAs, c.InsecureSkipVerify)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn oauthConn, err\n}\n\nfunc (c *oauthConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", callbackURL, c.redirectURI)\n\t}\n\n\toauth2Config := &oauth2.Config{\n\t\tClientID:     c.clientID,\n\t\tClientSecret: c.clientSecret,\n\t\tEndpoint:     oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL},\n\t\tRedirectURL:  c.redirectURI,\n\t\tScopes:       c.scopes,\n\t}\n\n\treturn oauth2Config.AuthCodeURL(state), nil, nil\n}\n\nfunc (c *oauthConnector) HandleCallback(s connector.Scopes, _ []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, errors.New(q.Get(\"error_description\"))\n\t}\n\n\toauth2Config := &oauth2.Config{\n\t\tClientID:     c.clientID,\n\t\tClientSecret: c.clientSecret,\n\t\tEndpoint:     oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL},\n\t\tRedirectURL:  c.redirectURI,\n\t\tScopes:       c.scopes,\n\t}\n\n\tctx := context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)\n\n\ttoken, err := oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"OAuth connector: failed to get token: %v\", err)\n\t}\n\n\tclient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token))\n\n\tuserInfoResp, err := client.Get(c.userInfoURL)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"OAuth Connector: failed to execute request to userinfo: %v\", err)\n\t}\n\tdefer userInfoResp.Body.Close()\n\n\tif userInfoResp.StatusCode != http.StatusOK {\n\t\treturn identity, fmt.Errorf(\"OAuth Connector: failed to execute request to userinfo: status %d\", userInfoResp.StatusCode)\n\t}\n\n\tvar userInfoResult map[string]interface{}\n\terr = json.NewDecoder(userInfoResp.Body).Decode(&userInfoResult)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"OAuth Connector: failed to parse userinfo: %v\", err)\n\t}\n\n\tuserID, found := userInfoResult[c.userIDKey]\n\tif !found {\n\t\treturn identity, fmt.Errorf(\"OAuth Connector: not found %v claim\", c.userIDKey)\n\t}\n\n\tswitch userID.(type) {\n\tcase float64, int64, string:\n\t\tidentity.UserID = fmt.Sprintf(\"%v\", userID)\n\tdefault:\n\t\treturn identity, fmt.Errorf(\"OAuth Connector: %v claim should be string or number, got %T\", c.userIDKey, userID)\n\t}\n\n\tidentity.Username, _ = userInfoResult[c.userNameKey].(string)\n\tidentity.PreferredUsername, _ = userInfoResult[c.preferredUsernameKey].(string)\n\tidentity.Email, _ = userInfoResult[c.emailKey].(string)\n\tidentity.EmailVerified, _ = userInfoResult[c.emailVerifiedKey].(bool)\n\n\tif s.Groups {\n\t\tgroups := map[string]struct{}{}\n\n\t\tc.addGroupsFromMap(groups, userInfoResult)\n\t\tc.addGroupsFromToken(groups, token.AccessToken)\n\n\t\tfor groupName := range groups {\n\t\t\tidentity.Groups = append(identity.Groups, groupName)\n\t\t}\n\t}\n\n\tif s.OfflineAccess {\n\t\tdata := connectorData{AccessToken: token.AccessToken}\n\t\tconnData, err := json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"OAuth Connector: failed to parse connector data for offline access: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\nfunc (c *oauthConnector) addGroupsFromMap(groups map[string]struct{}, result map[string]interface{}) error {\n\tgroupsClaim, ok := result[c.groupsKey].([]interface{})\n\tif !ok {\n\t\treturn errors.New(\"cannot convert to slice\")\n\t}\n\n\tfor _, group := range groupsClaim {\n\t\tif groupString, ok := group.(string); ok {\n\t\t\tgroups[groupString] = struct{}{}\n\t\t}\n\t\tif groupMap, ok := group.(map[string]interface{}); ok {\n\t\t\tif groupName, ok := groupMap[\"name\"].(string); ok {\n\t\t\t\tgroups[groupName] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc (c *oauthConnector) addGroupsFromToken(groups map[string]struct{}, token string) error {\n\tparts := strings.Split(token, \".\")\n\tif len(parts) < 2 {\n\t\treturn errors.New(\"invalid token\")\n\t}\n\n\tdecoded, err := decode(parts[1])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar claimsMap map[string]interface{}\n\terr = json.Unmarshal(decoded, &claimsMap)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn c.addGroupsFromMap(groups, claimsMap)\n}\n\nfunc decode(seg string) ([]byte, error) {\n\tif l := len(seg) % 4; l > 0 {\n\t\tseg += strings.Repeat(\"=\", 4-l)\n\t}\n\n\treturn base64.URLEncoding.DecodeString(seg)\n}\n"
  },
  {
    "path": "connector/oauth/oauth_test.go",
    "content": "package oauth\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"sort\"\n\t\"testing\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nfunc TestOpen(t *testing.T) {\n\ttokenClaims := map[string]interface{}{}\n\tuserInfoClaims := map[string]interface{}{}\n\n\ttestServer := testSetup(t, tokenClaims, userInfoClaims)\n\tdefer testServer.Close()\n\n\tconn := newConnector(t, testServer.URL)\n\n\tsort.Strings(conn.scopes)\n\n\tassert.Equal(t, conn.clientID, \"testClient\")\n\tassert.Equal(t, conn.clientSecret, \"testSecret\")\n\tassert.Equal(t, conn.redirectURI, testServer.URL+\"/callback\")\n\tassert.Equal(t, conn.tokenURL, testServer.URL+\"/token\")\n\tassert.Equal(t, conn.authorizationURL, testServer.URL+\"/authorize\")\n\tassert.Equal(t, conn.userInfoURL, testServer.URL+\"/userinfo\")\n\tassert.Equal(t, len(conn.scopes), 2)\n\tassert.Equal(t, conn.scopes[0], \"groups\")\n\tassert.Equal(t, conn.scopes[1], \"openid\")\n}\n\nfunc TestLoginURL(t *testing.T) {\n\ttokenClaims := map[string]interface{}{}\n\tuserInfoClaims := map[string]interface{}{}\n\n\ttestServer := testSetup(t, tokenClaims, userInfoClaims)\n\tdefer testServer.Close()\n\n\tconn := newConnector(t, testServer.URL)\n\n\tloginURL, _, err := conn.LoginURL(connector.Scopes{}, conn.redirectURI, \"some-state\")\n\tassert.Equal(t, err, nil)\n\n\texpectedURL, err := url.Parse(testServer.URL + \"/authorize\")\n\tassert.Equal(t, err, nil)\n\n\tvalues := url.Values{}\n\tvalues.Add(\"client_id\", \"testClient\")\n\tvalues.Add(\"redirect_uri\", conn.redirectURI)\n\tvalues.Add(\"response_type\", \"code\")\n\tvalues.Add(\"scope\", \"openid groups\")\n\tvalues.Add(\"state\", \"some-state\")\n\texpectedURL.RawQuery = values.Encode()\n\n\tassert.Equal(t, loginURL, expectedURL.String())\n}\n\nfunc TestHandleCallBackForGroupsInUserInfo(t *testing.T) {\n\ttokenClaims := map[string]interface{}{}\n\n\tuserInfoClaims := map[string]interface{}{\n\t\t\"name\":               \"test-name\",\n\t\t\"user_id_key\":        \"test-user-id\",\n\t\t\"user_name_key\":      \"test-username\",\n\t\t\"preferred_username\": \"test-preferred-username\",\n\t\t\"mail\":               \"mod_mail\",\n\t\t\"has_verified_email\": false,\n\t\t\"groups_key\":         []string{\"admin-group\", \"user-group\"},\n\t}\n\n\ttestServer := testSetup(t, tokenClaims, userInfoClaims)\n\tdefer testServer.Close()\n\n\tconn := newConnector(t, testServer.URL)\n\treq := newRequestWithAuthCode(t, testServer.URL, \"TestHandleCallBackForGroupsInUserInfo\")\n\n\tidentity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\tassert.Equal(t, err, nil)\n\n\tsort.Strings(identity.Groups)\n\tassert.Equal(t, len(identity.Groups), 2)\n\tassert.Equal(t, identity.Groups[0], \"admin-group\")\n\tassert.Equal(t, identity.Groups[1], \"user-group\")\n\tassert.Equal(t, identity.UserID, \"test-user-id\")\n\tassert.Equal(t, identity.Username, \"test-username\")\n\tassert.Equal(t, identity.PreferredUsername, \"test-preferred-username\")\n\tassert.Equal(t, identity.Email, \"mod_mail\")\n\tassert.Equal(t, identity.EmailVerified, false)\n}\n\nfunc TestHandleCallBackForGroupMapsInUserInfo(t *testing.T) {\n\ttokenClaims := map[string]interface{}{}\n\n\tuserInfoClaims := map[string]interface{}{\n\t\t\"name\":               \"test-name\",\n\t\t\"user_id_key\":        \"test-user-id\",\n\t\t\"user_name_key\":      \"test-username\",\n\t\t\"preferred_username\": \"test-preferred-username\",\n\t\t\"mail\":               \"mod_mail\",\n\t\t\"has_verified_email\": false,\n\t\t\"groups_key\": []interface{}{\n\t\t\tmap[string]string{\"name\": \"admin-group\", \"id\": \"111\"},\n\t\t\tmap[string]string{\"name\": \"user-group\", \"id\": \"222\"},\n\t\t},\n\t}\n\n\ttestServer := testSetup(t, tokenClaims, userInfoClaims)\n\tdefer testServer.Close()\n\n\tconn := newConnector(t, testServer.URL)\n\treq := newRequestWithAuthCode(t, testServer.URL, \"TestHandleCallBackForGroupMapsInUserInfo\")\n\n\tidentity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\tassert.Equal(t, err, nil)\n\n\tsort.Strings(identity.Groups)\n\tassert.Equal(t, len(identity.Groups), 2)\n\tassert.Equal(t, identity.Groups[0], \"admin-group\")\n\tassert.Equal(t, identity.Groups[1], \"user-group\")\n\tassert.Equal(t, identity.UserID, \"test-user-id\")\n\tassert.Equal(t, identity.Username, \"test-username\")\n\tassert.Equal(t, identity.PreferredUsername, \"test-preferred-username\")\n\tassert.Equal(t, identity.Email, \"mod_mail\")\n\tassert.Equal(t, identity.EmailVerified, false)\n}\n\nfunc TestHandleCallBackForGroupsInToken(t *testing.T) {\n\ttokenClaims := map[string]interface{}{\n\t\t\"groups_key\": []string{\"test-group\"},\n\t}\n\n\tuserInfoClaims := map[string]interface{}{\n\t\t\"name\":               \"test-name\",\n\t\t\"user_id_key\":        \"test-user-id\",\n\t\t\"user_name_key\":      \"test-username\",\n\t\t\"preferred_username\": \"test-preferred-username\",\n\t\t\"email\":              \"test-email\",\n\t\t\"email_verified\":     true,\n\t}\n\n\ttestServer := testSetup(t, tokenClaims, userInfoClaims)\n\tdefer testServer.Close()\n\n\tconn := newConnector(t, testServer.URL)\n\treq := newRequestWithAuthCode(t, testServer.URL, \"TestHandleCallBackForGroupsInToken\")\n\n\tidentity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\tassert.Equal(t, err, nil)\n\n\tassert.Equal(t, len(identity.Groups), 1)\n\tassert.Equal(t, identity.Groups[0], \"test-group\")\n\tassert.Equal(t, identity.PreferredUsername, \"test-preferred-username\")\n\tassert.Equal(t, identity.UserID, \"test-user-id\")\n\tassert.Equal(t, identity.Username, \"test-username\")\n\tassert.Equal(t, identity.Email, \"\")\n\tassert.Equal(t, identity.EmailVerified, false)\n}\n\nfunc TestHandleCallbackForNumericUserID(t *testing.T) {\n\ttokenClaims := map[string]interface{}{}\n\n\tuserInfoClaims := map[string]interface{}{\n\t\t\"name\":               \"test-name\",\n\t\t\"user_id_key\":        1000,\n\t\t\"user_name_key\":      \"test-username\",\n\t\t\"preferred_username\": \"test-preferred-username\",\n\t\t\"mail\":               \"mod_mail\",\n\t\t\"has_verified_email\": false,\n\t}\n\n\ttestServer := testSetup(t, tokenClaims, userInfoClaims)\n\tdefer testServer.Close()\n\n\tconn := newConnector(t, testServer.URL)\n\treq := newRequestWithAuthCode(t, testServer.URL, \"TestHandleCallbackForNumericUserID\")\n\n\tidentity, err := conn.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\tassert.Equal(t, err, nil)\n\n\tassert.Equal(t, identity.UserID, \"1000\")\n\tassert.Equal(t, identity.Username, \"test-username\")\n\tassert.Equal(t, identity.PreferredUsername, \"test-preferred-username\")\n\tassert.Equal(t, identity.Email, \"mod_mail\")\n\tassert.Equal(t, identity.EmailVerified, false)\n}\n\nfunc testSetup(t *testing.T, tokenClaims map[string]interface{}, userInfoClaims map[string]interface{}) *httptest.Server {\n\tkey, err := rsa.GenerateKey(rand.Reader, 1024)\n\tif err != nil {\n\t\tt.Fatal(\"Failed to generate rsa key\", err)\n\t}\n\n\tjwk := jose.JSONWebKey{\n\t\tKey:       key,\n\t\tKeyID:     \"some-key\",\n\t\tAlgorithm: \"RSA\",\n\t}\n\n\tmux := http.NewServeMux()\n\n\tmux.HandleFunc(\"/token\", func(w http.ResponseWriter, r *http.Request) {\n\t\ttoken, err := newToken(&jwk, tokenClaims)\n\t\tif err != nil {\n\t\t\tt.Fatal(\"unable to generate token\", err)\n\t\t}\n\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(&map[string]string{\n\t\t\t\"access_token\": token,\n\t\t\t\"id_token\":     token,\n\t\t\t\"token_type\":   \"Bearer\",\n\t\t})\n\t})\n\n\tmux.HandleFunc(\"/userinfo\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(userInfoClaims)\n\t})\n\n\treturn httptest.NewServer(mux)\n}\n\nfunc newToken(key *jose.JSONWebKey, claims map[string]interface{}) (string, error) {\n\tsigningKey := jose.SigningKey{Key: key, Algorithm: jose.RS256}\n\n\tsigner, err := jose.NewSigner(signingKey, &jose.SignerOptions{})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"new signer: %v\", err)\n\t}\n\n\tpayload, err := json.Marshal(claims)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"marshaling claims: %v\", err)\n\t}\n\n\tsignature, err := signer.Sign(payload)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"signing payload: %v\", err)\n\t}\n\n\treturn signature.CompactSerialize()\n}\n\nfunc newConnector(t *testing.T, serverURL string) *oauthConnector {\n\ttestConfig := Config{\n\t\tClientID:         \"testClient\",\n\t\tClientSecret:     \"testSecret\",\n\t\tRedirectURI:      serverURL + \"/callback\",\n\t\tTokenURL:         serverURL + \"/token\",\n\t\tAuthorizationURL: serverURL + \"/authorize\",\n\t\tUserInfoURL:      serverURL + \"/userinfo\",\n\t\tScopes:           []string{\"openid\", \"groups\"},\n\t\tUserIDKey:        \"user_id_key\",\n\t}\n\n\ttestConfig.ClaimMapping.UserNameKey = \"user_name_key\"\n\ttestConfig.ClaimMapping.GroupsKey = \"groups_key\"\n\ttestConfig.ClaimMapping.EmailKey = \"mail\"\n\ttestConfig.ClaimMapping.EmailVerifiedKey = \"has_verified_email\"\n\n\tlog := slog.New(slog.DiscardHandler)\n\n\tconn, err := testConfig.Open(\"id\", log)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\toauthConn, ok := conn.(*oauthConnector)\n\tif !ok {\n\t\tt.Fatal(errors.New(\"failed to convert to oauthConnector\"))\n\t}\n\n\treturn oauthConn\n}\n\nfunc newRequestWithAuthCode(t *testing.T, serverURL string, code string) *http.Request {\n\treq, err := http.NewRequest(\"GET\", serverURL, nil)\n\tif err != nil {\n\t\tt.Fatal(\"failed to create request\", err)\n\t}\n\n\tvalues := req.URL.Query()\n\tvalues.Add(\"code\", code)\n\treq.URL.RawQuery = values.Encode()\n\n\treturn req\n}\n"
  },
  {
    "path": "connector/oidc/oidc.go",
    "content": "// Package oidc implements logging in through OpenID Connect providers.\npackage oidc\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\tgroups_pkg \"github.com/dexidp/dex/pkg/groups\"\n\t\"github.com/dexidp/dex/pkg/httpclient\"\n)\n\nconst (\n\tcodeChallengeMethodPlain = \"plain\"\n\tcodeChallengeMethodS256  = \"S256\"\n)\n\nfunc contains(arr []string, item string) bool {\n\tfor _, itemFromArray := range arr {\n\t\tif itemFromArray == item {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Config holds configuration options for OpenID Connect logins.\ntype Config struct {\n\tIssuer string `json:\"issuer\"`\n\t// Some offspec providers like Azure, Oracle IDCS have oidc discovery url\n\t// different from issuer url which causes issuerValidation to fail\n\t// IssuerAlias provides a way to override the Issuer url\n\t// from the .well-known/openid-configuration issuer\n\tIssuerAlias  string `json:\"issuerAlias\"`\n\tClientID     string `json:\"clientID\"`\n\tClientSecret string `json:\"clientSecret\"`\n\tRedirectURI  string `json:\"redirectURI\"`\n\n\t// The section to override options discovered automatically from\n\t// the providers' discovery URL (.well-known/openid-configuration).\n\tProviderDiscoveryOverrides ProviderDiscoveryOverrides `json:\"providerDiscoveryOverrides\"`\n\n\t// Causes client_secret to be passed as POST parameters instead of basic\n\t// auth. This is specifically \"NOT RECOMMENDED\" by the OAuth2 RFC, but some\n\t// providers require it.\n\t//\n\t// https://tools.ietf.org/html/rfc6749#section-2.3.1\n\tBasicAuthUnsupported *bool `json:\"basicAuthUnsupported\"`\n\n\tScopes []string `json:\"scopes\"` // defaults to \"profile\" and \"email\"\n\n\t// HostedDomains was an optional list of whitelisted domains when using the OIDC connector with Google.\n\t// Only users from a whitelisted domain were allowed to log in.\n\t// Support for this option was removed from the OIDC connector.\n\t// Consider switching to the Google connector which supports this option.\n\t//\n\t// Deprecated: will be removed in future releases.\n\tHostedDomains []string `json:\"hostedDomains\"`\n\n\t// Certificates for SSL validation\n\tRootCAs []string `json:\"rootCAs\"`\n\n\t// Override the value of email_verified to true in the returned claims\n\tInsecureSkipEmailVerified bool `json:\"insecureSkipEmailVerified\"`\n\n\t// InsecureEnableGroups enables groups claims. This is disabled by default until https://github.com/dexidp/dex/issues/1065 is resolved\n\tInsecureEnableGroups bool     `json:\"insecureEnableGroups\"`\n\tAllowedGroups        []string `json:\"allowedGroups\"`\n\n\t// AcrValues (Authentication Context Class Reference Values) that specifies the Authentication Context Class Values\n\t// within the Authentication Request that the Authorization Server is being requested to use for\n\t// processing requests from this Client, with the values appearing in order of preference.\n\tAcrValues []string `json:\"acrValues\"`\n\n\t// Disable certificate verification\n\tInsecureSkipVerify bool `json:\"insecureSkipVerify\"`\n\n\t// GetUserInfo uses the userinfo endpoint to get additional claims for\n\t// the token. This is especially useful where upstreams return \"thin\"\n\t// id tokens\n\tGetUserInfo bool `json:\"getUserInfo\"`\n\n\tUserIDKey string `json:\"userIDKey\"`\n\n\tUserNameKey string `json:\"userNameKey\"`\n\n\t// PromptType will be used for the prompt parameter (when offline_access, by default prompt=consent)\n\tPromptType *string `json:\"promptType\"`\n\n\t// PKCEChallenge specifies which PKCE algorithm will be used\n\t// If not setted it will be auto-detected the best-fit for the connector.\n\tPKCEChallenge string `json:\"pkceChallenge\"`\n\n\t// OverrideClaimMapping will be used to override the options defined in claimMappings.\n\t// i.e. if there are 'email' and `preferred_email` claims available, by default Dex will always use the `email` claim independent of the ClaimMapping.EmailKey.\n\t// This setting allows you to override the default behavior of Dex and enforce the mappings defined in `claimMapping`.\n\tOverrideClaimMapping bool `json:\"overrideClaimMapping\"` // defaults to false\n\n\tClaimMapping struct {\n\t\t// Configurable key which contains the preferred username claims\n\t\tPreferredUsernameKey string `json:\"preferred_username\"` // defaults to \"preferred_username\"\n\n\t\t// Configurable key which contains the email claims\n\t\tEmailKey string `json:\"email\"` // defaults to \"email\"\n\n\t\t// Configurable key which contains the groups claims\n\t\tGroupsKey string `json:\"groups\"` // defaults to \"groups\"\n\t} `json:\"claimMapping\"`\n\n\t// ClaimMutations holds all claim mutations options\n\tClaimMutations struct {\n\t\tNewGroupFromClaims []NewGroupFromClaims `json:\"newGroupFromClaims\"`\n\t\tFilterGroupClaims  FilterGroupClaims    `json:\"filterGroupClaims\"`\n\t\tModifyGroupNames   ModifyGroupNames     `json:\"modifyGroupNames\"`\n\t} `json:\"claimModifications\"`\n}\n\ntype ProviderDiscoveryOverrides struct {\n\t// TokenURL provides a way to user overwrite the Token URL\n\t// from the .well-known/openid-configuration token_endpoint\n\tTokenURL string `json:\"tokenURL\"`\n\t// AuthURL provides a way to user overwrite the Auth URL\n\t// from the .well-known/openid-configuration authorization_endpoint\n\tAuthURL string `json:\"authURL\"`\n\t// JWKSURL provides a way to user overwrite the JWKS URL\n\t// from the .well-known/openid-configuration jwks_uri\n\tJWKSURL string `json:\"jwksURL\"`\n}\n\nfunc (o *ProviderDiscoveryOverrides) Empty() bool {\n\treturn o.TokenURL == \"\" && o.AuthURL == \"\" && o.JWKSURL == \"\"\n}\n\nfunc getProvider(ctx context.Context, issuer string, overrides ProviderDiscoveryOverrides) (*oidc.Provider, error) {\n\tprovider, err := oidc.NewProvider(ctx, issuer)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get provider: %v\", err)\n\t}\n\n\tif overrides.Empty() {\n\t\treturn provider, nil\n\t}\n\n\tv := &struct {\n\t\tIssuer        string   `json:\"issuer\"`\n\t\tAuthURL       string   `json:\"authorization_endpoint\"`\n\t\tTokenURL      string   `json:\"token_endpoint\"`\n\t\tDeviceAuthURL string   `json:\"device_authorization_endpoint\"`\n\t\tJWKSURL       string   `json:\"jwks_uri\"`\n\t\tUserInfoURL   string   `json:\"userinfo_endpoint\"`\n\t\tAlgorithms    []string `json:\"id_token_signing_alg_values_supported\"`\n\t}{}\n\tif err := provider.Claims(v); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to extract provider discovery claims: %v\", err)\n\t}\n\tconfig := oidc.ProviderConfig{\n\t\tIssuerURL:     v.Issuer,\n\t\tAuthURL:       v.AuthURL,\n\t\tTokenURL:      v.TokenURL,\n\t\tDeviceAuthURL: v.DeviceAuthURL,\n\t\tJWKSURL:       v.JWKSURL,\n\t\tUserInfoURL:   v.UserInfoURL,\n\t\tAlgorithms:    v.Algorithms,\n\t}\n\n\tif overrides.TokenURL != \"\" {\n\t\tconfig.TokenURL = overrides.TokenURL\n\t}\n\tif overrides.AuthURL != \"\" {\n\t\tconfig.AuthURL = overrides.AuthURL\n\t}\n\tif overrides.JWKSURL != \"\" {\n\t\tconfig.JWKSURL = overrides.JWKSURL\n\t}\n\treturn config.NewProvider(context.Background()), nil\n}\n\n// NewGroupFromClaims creates a new group from a list of claims and appends it to the list of existing groups.\ntype NewGroupFromClaims struct {\n\t// List of claim to join together\n\tClaims []string `json:\"claims\"`\n\n\t// String to separate the claims\n\tDelimiter string `json:\"delimiter\"`\n\n\t// Should Dex remove the Delimiter string from claim values\n\t// This is done to keep resulting claim structure in full control of the Dex operator\n\tClearDelimiter bool `json:\"clearDelimiter\"`\n\n\t// String to place before the first claim\n\tPrefix string `json:\"prefix\"`\n}\n\n// FilterGroupClaims is a regex filter for to keep only the matching groups.\n// This is useful when the groups list is too large to fit within an HTTP header.\ntype FilterGroupClaims struct {\n\tGroupsFilter string `json:\"groupsFilter\"`\n}\n\n// ModifyGroupNames allows to modify the group claims by adding a prefix and/or suffix to each group.\ntype ModifyGroupNames struct {\n\tPrefix string `json:\"prefix\"`\n\tSuffix string `json:\"suffix\"`\n}\n\n// Domains that don't support basic auth. golang.org/x/oauth2 has an internal\n// list, but it only matches specific URLs, not top level domains.\nvar brokenAuthHeaderDomains = []string{\n\t// See: https://github.com/dexidp/dex/issues/859\n\t\"okta.com\",\n\t\"oktapreview.com\",\n}\n\n// connectorData stores information for sessions authenticated by this connector\ntype connectorData struct {\n\tRefreshToken []byte\n}\n\n// Detect auth header provider issues for known providers. This lets users\n// avoid having to explicitly set \"basicAuthUnsupported\" in their config.\n//\n// Setting the config field always overrides values returned by this function.\nfunc knownBrokenAuthHeaderProvider(issuerURL string) bool {\n\tif u, err := url.Parse(issuerURL); err == nil {\n\t\tfor _, host := range brokenAuthHeaderDomains {\n\t\t\tif u.Host == host || strings.HasSuffix(u.Host, \".\"+host) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\n// PKCEChallengeData is used to store info for PKCE Challenge method and verifier\n// in the connectorData\ntype PKCEChallengeData struct {\n\tCodeChallenge       string `json:\"codeChallenge\"`\n\tCodeChallengeMethod string `json:\"codeChallengeMethod\"`\n}\n\n// Returns an AuthCodeOption according to the provided codeChallengeMethod\nfunc getAuthCodeOptionForCodeChallenge(codeVerifier, codeChallengeMethod string) (oauth2.AuthCodeOption, error) {\n\tswitch codeChallengeMethod {\n\tcase codeChallengeMethodPlain:\n\t\treturn oauth2.VerifierOption(codeVerifier), nil\n\tcase codeChallengeMethodS256:\n\t\treturn oauth2.S256ChallengeOption(codeVerifier), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unknown challenge method (%v)\", codeChallengeMethod)\n\t}\n}\n\n// Open returns a connector which can be used to login users through an upstream\n// OpenID Connect provider.\nfunc (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) {\n\tif len(c.HostedDomains) > 0 {\n\t\treturn nil, fmt.Errorf(\"support for the Hosted domains option had been deprecated and removed, consider switching to the Google connector\")\n\t}\n\n\thttpClient, err := httpclient.NewHTTPClient(c.RootCAs, c.InsecureSkipVerify)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tbgctx, cancel := context.WithCancel(context.Background())\n\tctx := context.WithValue(bgctx, oauth2.HTTPClient, httpClient)\n\tif c.IssuerAlias != \"\" {\n\t\tctx = oidc.InsecureIssuerURLContext(ctx, c.IssuerAlias)\n\t}\n\tprovider, err := getProvider(ctx, c.Issuer, c.ProviderDiscoveryOverrides)\n\tif err != nil {\n\t\tcancel()\n\t\treturn nil, err\n\t}\n\tif !c.ProviderDiscoveryOverrides.Empty() {\n\t\tlogger.Warn(\"overrides for connector are set, this can be a vulnerability when not properly configured\", \"connector_id\", id)\n\t}\n\n\tendpoint := provider.Endpoint()\n\n\tif c.BasicAuthUnsupported != nil {\n\t\t// Setting \"basicAuthUnsupported\" always overrides our detection.\n\t\tif *c.BasicAuthUnsupported {\n\t\t\tendpoint.AuthStyle = oauth2.AuthStyleInParams\n\t\t}\n\t} else if knownBrokenAuthHeaderProvider(c.Issuer) {\n\t\tendpoint.AuthStyle = oauth2.AuthStyleInParams\n\t}\n\n\tscopes := []string{oidc.ScopeOpenID}\n\tif len(c.Scopes) > 0 {\n\t\tscopes = append(scopes, c.Scopes...)\n\t} else {\n\t\tscopes = append(scopes, \"profile\", \"email\")\n\t}\n\n\t// PromptType should be \"consent\" by default, if not set\n\tpromptType := \"consent\"\n\tif c.PromptType != nil {\n\t\tpromptType = *c.PromptType\n\t}\n\n\tvar groupsFilter *regexp.Regexp\n\tif c.ClaimMutations.FilterGroupClaims.GroupsFilter != \"\" {\n\t\tgroupsFilter, err = regexp.Compile(c.ClaimMutations.FilterGroupClaims.GroupsFilter)\n\t\tif err != nil {\n\t\t\tlogger.Warn(\"ignoring invalid\", \"invalid_regex\", c.ClaimMutations.FilterGroupClaims.GroupsFilter, \"connector_id\", id)\n\t\t}\n\t}\n\n\t// Obtain CodeChallengeMethodsSupported from the provider\n\tvar metadata struct {\n\t\tCodeChallengeMethodsSupported []string `json:\"code_challenge_methods_supported\"`\n\t}\n\tif err := provider.Claims(&metadata); err != nil {\n\t\tlogger.Warn(\"failed to parse provider metadata\")\n\t}\n\t// if PKCEChallenge method has not been setted in the config, auto-detect the best fit\n\tif c.PKCEChallenge == \"\" {\n\t\tif contains(metadata.CodeChallengeMethodsSupported, codeChallengeMethodS256) {\n\t\t\tc.PKCEChallenge = codeChallengeMethodS256\n\t\t} else if contains(metadata.CodeChallengeMethodsSupported, codeChallengeMethodPlain) {\n\t\t\tc.PKCEChallenge = codeChallengeMethodPlain\n\t\t}\n\t} else {\n\t\t// if PKCEChallenge method has been setted in the config, check if it is supported\n\t\tif !contains(metadata.CodeChallengeMethodsSupported, c.PKCEChallenge) {\n\t\t\tlogger.Warn(\"provided PKCEChallenge method not supported by the connector\")\n\t\t}\n\t}\n\n\tclientID := c.ClientID\n\treturn &oidcConnector{\n\t\tprovider:    provider,\n\t\tredirectURI: c.RedirectURI,\n\t\toauth2Config: &oauth2.Config{\n\t\t\tClientID:     clientID,\n\t\t\tClientSecret: c.ClientSecret,\n\t\t\tEndpoint:     endpoint,\n\t\t\tScopes:       scopes,\n\t\t\tRedirectURL:  c.RedirectURI,\n\t\t},\n\t\tverifier: provider.VerifierContext(\n\t\t\tctx, // Pass our ctx with customized http.Client\n\t\t\t&oidc.Config{ClientID: clientID},\n\t\t),\n\t\tlogger:                    logger.With(slog.Group(\"connector\", \"type\", \"oidc\", \"id\", id)),\n\t\tcancel:                    cancel,\n\t\thttpClient:                httpClient,\n\t\tinsecureSkipEmailVerified: c.InsecureSkipEmailVerified,\n\t\tinsecureEnableGroups:      c.InsecureEnableGroups,\n\t\tallowedGroups:             c.AllowedGroups,\n\t\tacrValues:                 c.AcrValues,\n\t\tgetUserInfo:               c.GetUserInfo,\n\t\tpromptType:                promptType,\n\t\tuserIDKey:                 c.UserIDKey,\n\t\tuserNameKey:               c.UserNameKey,\n\t\toverrideClaimMapping:      c.OverrideClaimMapping,\n\t\tpreferredUsernameKey:      c.ClaimMapping.PreferredUsernameKey,\n\t\temailKey:                  c.ClaimMapping.EmailKey,\n\t\tgroupsKey:                 c.ClaimMapping.GroupsKey,\n\t\tnewGroupFromClaims:        c.ClaimMutations.NewGroupFromClaims,\n\t\tgroupsFilter:              groupsFilter,\n\t\tgroupsPrefix:              c.ClaimMutations.ModifyGroupNames.Prefix,\n\t\tgroupsSuffix:              c.ClaimMutations.ModifyGroupNames.Suffix,\n\t\tpkceChallenge:             c.PKCEChallenge,\n\t}, nil\n}\n\nvar (\n\t_ connector.CallbackConnector      = (*oidcConnector)(nil)\n\t_ connector.RefreshConnector       = (*oidcConnector)(nil)\n\t_ connector.TokenIdentityConnector = (*oidcConnector)(nil)\n)\n\ntype oidcConnector struct {\n\tprovider                  *oidc.Provider\n\tredirectURI               string\n\toauth2Config              *oauth2.Config\n\tverifier                  *oidc.IDTokenVerifier\n\tcancel                    context.CancelFunc\n\tlogger                    *slog.Logger\n\thttpClient                *http.Client\n\tinsecureSkipEmailVerified bool\n\tinsecureEnableGroups      bool\n\tallowedGroups             []string\n\tacrValues                 []string\n\tgetUserInfo               bool\n\tpromptType                string\n\tuserIDKey                 string\n\tuserNameKey               string\n\toverrideClaimMapping      bool\n\tpreferredUsernameKey      string\n\temailKey                  string\n\tgroupsKey                 string\n\tnewGroupFromClaims        []NewGroupFromClaims\n\tgroupsFilter              *regexp.Regexp\n\tgroupsPrefix              string\n\tgroupsSuffix              string\n\tpkceChallenge             string\n}\n\nfunc (c *oidcConnector) Close() error {\n\tc.cancel()\n\treturn nil\n}\n\nfunc (c *oidcConnector) LoginURL(s connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\", callbackURL, c.redirectURI)\n\t}\n\n\tvar opts []oauth2.AuthCodeOption\n\tvar connectorData []byte\n\n\tif len(c.acrValues) > 0 {\n\t\tacrValues := strings.Join(c.acrValues, \" \")\n\t\topts = append(opts, oauth2.SetAuthURLParam(\"acr_values\", acrValues))\n\t}\n\n\tif s.OfflineAccess {\n\t\topts = append(opts, oauth2.AccessTypeOffline, oauth2.SetAuthURLParam(\"prompt\", c.promptType))\n\t}\n\n\tif c.pkceChallenge != \"\" {\n\t\tcodeVerifier := oauth2.GenerateVerifier()\n\t\tauthCodeOption, err := getAuthCodeOptionForCodeChallenge(codeVerifier, c.pkceChallenge)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"oidc: failed to get PKCE AuthCodeOption for CodeChallenge: %v\", err)\n\t\t}\n\t\tdata := PKCEChallengeData{\n\t\t\tCodeChallenge:       codeVerifier,\n\t\t\tCodeChallengeMethod: c.pkceChallenge,\n\t\t}\n\t\tconnectorData, err = json.Marshal(data)\n\t\tif err != nil {\n\t\t\treturn \"\", nil, fmt.Errorf(\"oidc: failed to create PKCEChallenge data: %v\", err)\n\t\t}\n\t\topts = append(opts, authCodeOption)\n\t}\n\n\treturn c.oauth2Config.AuthCodeURL(state, opts...), connectorData, nil\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n\ntype caller uint\n\nconst (\n\tcreateCaller caller = iota\n\trefreshCaller\n\texchangeCaller\n)\n\nfunc (c *oidcConnector) HandleCallback(s connector.Scopes, connData []byte, r *http.Request) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\tctx := context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)\n\n\tvar opts []oauth2.AuthCodeOption\n\tif c.pkceChallenge != \"\" {\n\t\tvar data PKCEChallengeData\n\t\tif err := json.Unmarshal(connData, &data); err != nil {\n\t\t\treturn identity, fmt.Errorf(\"oidc: failed to parse PKCEChallenge data: %v\", err)\n\t\t}\n\t\tif data.CodeChallenge == \"\" {\n\t\t\treturn identity, fmt.Errorf(\"oidc: invalid PKCE CodeChallenge\")\n\t\t}\n\t\topts = append(opts, oauth2.VerifierOption(data.CodeChallenge))\n\t}\n\n\ttoken, err := c.oauth2Config.Exchange(ctx, q.Get(\"code\"), opts...)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"oidc: failed to get token: %v\", err)\n\t}\n\treturn c.createIdentity(ctx, identity, token, createCaller)\n}\n\n// Refresh is used to refresh a session with the refresh token provided by the IdP\nfunc (c *oidcConnector) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\tcd := connectorData{}\n\terr := json.Unmarshal(identity.ConnectorData, &cd)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"oidc: failed to unmarshal connector data: %v\", err)\n\t}\n\n\tctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)\n\n\tt := &oauth2.Token{\n\t\tRefreshToken: string(cd.RefreshToken),\n\t\tExpiry:       time.Now().Add(-time.Hour),\n\t}\n\ttoken, err := c.oauth2Config.TokenSource(ctx, t).Token()\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"oidc: failed to get refresh token: %v\", err)\n\t}\n\treturn c.createIdentity(ctx, identity, token, refreshCaller)\n}\n\nfunc (c *oidcConnector) TokenIdentity(ctx context.Context, subjectTokenType, subjectToken string) (connector.Identity, error) {\n\tvar identity connector.Identity\n\n\tctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)\n\n\ttoken := &oauth2.Token{\n\t\tAccessToken: subjectToken,\n\t\tTokenType:   subjectTokenType,\n\t}\n\treturn c.createIdentity(ctx, identity, token, exchangeCaller)\n}\n\nfunc (c *oidcConnector) createIdentity(ctx context.Context, identity connector.Identity, token *oauth2.Token, caller caller) (connector.Identity, error) {\n\tvar claims map[string]interface{}\n\n\tif rawIDToken, ok := token.Extra(\"id_token\").(string); ok {\n\t\tidToken, err := c.verifier.Verify(ctx, rawIDToken)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"oidc: failed to verify ID Token: %v\", err)\n\t\t}\n\n\t\tif err := idToken.Claims(&claims); err != nil {\n\t\t\treturn identity, fmt.Errorf(\"oidc: failed to decode claims: %v\", err)\n\t\t}\n\t} else if caller == exchangeCaller {\n\t\tswitch token.TokenType {\n\t\tcase \"urn:ietf:params:oauth:token-type:id_token\":\n\t\t\t// Verify only works on ID tokens\n\t\t\tidToken, err := c.provider.Verifier(&oidc.Config{SkipClientIDCheck: true}).Verify(ctx, token.AccessToken)\n\t\t\tif err != nil {\n\t\t\t\treturn identity, fmt.Errorf(\"oidc: failed to verify token: %v\", err)\n\t\t\t}\n\t\t\tif err := idToken.Claims(&claims); err != nil {\n\t\t\t\treturn identity, fmt.Errorf(\"oidc: failed to decode claims: %v\", err)\n\t\t\t}\n\t\tcase \"urn:ietf:params:oauth:token-type:access_token\":\n\t\t\tif !c.getUserInfo {\n\t\t\t\treturn identity, fmt.Errorf(\"oidc: getUserInfo is required for access token exchange\")\n\t\t\t}\n\t\tdefault:\n\t\t\treturn identity, fmt.Errorf(\"unknown token type for token exchange: %s\", token.TokenType)\n\t\t}\n\t} else if caller != refreshCaller {\n\t\t// ID tokens aren't mandatory in the reply when using a refresh_token grant\n\t\treturn identity, errors.New(\"oidc: no id_token in token response\")\n\t}\n\n\t// We immediately want to run getUserInfo if configured before we validate the claims.\n\t// For token exchanges with access tokens, this is how we verify the token.\n\tif c.getUserInfo {\n\t\tuserInfo, err := c.provider.UserInfo(ctx, oauth2.StaticTokenSource(&oauth2.Token{\n\t\t\tAccessToken: token.AccessToken,\n\t\t\tTokenType:   \"Bearer\", // The UserInfo endpoint requires a bearer token as per RFC6750\n\t\t}))\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"oidc: error loading userinfo: %v\", err)\n\t\t}\n\t\tif err := userInfo.Claims(&claims); err != nil {\n\t\t\treturn identity, fmt.Errorf(\"oidc: failed to decode userinfo claims: %v\", err)\n\t\t}\n\t}\n\n\tconst subjectClaimKey = \"sub\"\n\tsubject, found := claims[subjectClaimKey].(string)\n\tif !found {\n\t\treturn identity, fmt.Errorf(\"missing \\\"%s\\\" claim\", subjectClaimKey)\n\t}\n\n\tuserNameKey := \"name\"\n\tif c.userNameKey != \"\" {\n\t\tuserNameKey = c.userNameKey\n\t}\n\tname, found := claims[userNameKey].(string)\n\tif !found {\n\t\treturn identity, fmt.Errorf(\"missing \\\"%s\\\" claim\", userNameKey)\n\t}\n\n\tpreferredUsername, found := claims[\"preferred_username\"].(string)\n\tif (!found || c.overrideClaimMapping) && c.preferredUsernameKey != \"\" {\n\t\tpreferredUsername, _ = claims[c.preferredUsernameKey].(string)\n\t}\n\n\thasEmailScope := false\n\tfor _, s := range c.oauth2Config.Scopes {\n\t\tif s == \"email\" {\n\t\t\thasEmailScope = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tvar email string\n\temailKey := \"email\"\n\temail, found = claims[emailKey].(string)\n\tif (!found || c.overrideClaimMapping) && c.emailKey != \"\" {\n\t\temailKey = c.emailKey\n\t\temail, found = claims[emailKey].(string)\n\t}\n\n\tif !found && hasEmailScope {\n\t\treturn identity, fmt.Errorf(\"missing email claim, not found \\\"%s\\\" key\", emailKey)\n\t}\n\n\temailVerified, found := claims[\"email_verified\"].(bool)\n\tif !found {\n\t\tif c.insecureSkipEmailVerified {\n\t\t\temailVerified = true\n\t\t} else if hasEmailScope {\n\t\t\treturn identity, errors.New(\"missing \\\"email_verified\\\" claim\")\n\t\t}\n\t}\n\n\tvar groups []string\n\tif c.insecureEnableGroups {\n\t\tgroupsKey := \"groups\"\n\t\tvs, found := claims[groupsKey].([]interface{})\n\t\tif (!found || c.overrideClaimMapping) && c.groupsKey != \"\" {\n\t\t\tgroupsKey = c.groupsKey\n\t\t\tvs, found = claims[groupsKey].([]interface{})\n\t\t}\n\n\t\t// Fallback when claims[groupsKey] is a string instead of an array of strings.\n\t\tif g, b := claims[groupsKey].(string); b {\n\t\t\tgroups = []string{g}\n\t\t}\n\n\t\tif found {\n\t\t\tfor _, v := range vs {\n\t\t\t\tif s, ok := v.(string); ok {\n\t\t\t\t\tif c.groupsFilter != nil && !c.groupsFilter.MatchString(s) {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tgroups = append(groups, s)\n\t\t\t\t} else if groupMap, ok := v.(map[string]interface{}); ok {\n\t\t\t\t\tif s, ok := groupMap[\"name\"].(string); ok {\n\t\t\t\t\t\tif c.groupsFilter != nil && !c.groupsFilter.MatchString(s) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgroups = append(groups, s)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn identity, fmt.Errorf(\"malformed \\\"%v\\\" claim\", groupsKey)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate that the user is part of allowedGroups\n\t\tif len(c.allowedGroups) > 0 {\n\t\t\tgroupMatches := groups_pkg.Filter(groups, c.allowedGroups)\n\n\t\t\tif len(groupMatches) == 0 {\n\t\t\t\t// No group membership matches found, disallowing\n\t\t\t\treturn identity, fmt.Errorf(\"user not a member of allowed groups\")\n\t\t\t}\n\n\t\t\tgroups = groupMatches\n\t\t}\n\t}\n\n\t// add prefix/suffix to groups\n\tif c.groupsPrefix != \"\" || c.groupsSuffix != \"\" {\n\t\tfor i, group := range groups {\n\t\t\tgroups[i] = c.groupsPrefix + group + c.groupsSuffix\n\t\t}\n\t}\n\n\tfor _, config := range c.newGroupFromClaims {\n\t\tnewGroupSegments := []string{\n\t\t\tconfig.Prefix,\n\t\t}\n\t\tfor _, claimName := range config.Claims {\n\t\t\tclaimValue, ok := claims[claimName].(string)\n\t\t\tif !ok { // Non string claim value are ignored, concatenating them doesn't really make any sense\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif config.ClearDelimiter {\n\t\t\t\t// Removing the delimiter string from the concatenated claim to ensure resulting claim structure\n\t\t\t\t// is in full control of Dex operator\n\t\t\t\tclaimValue = strings.ReplaceAll(claimValue, config.Delimiter, \"\")\n\t\t\t}\n\n\t\t\tnewGroupSegments = append(newGroupSegments, claimValue)\n\t\t}\n\n\t\tif len(newGroupSegments) > 1 {\n\t\t\tgroups = append(groups, strings.Join(newGroupSegments, config.Delimiter))\n\t\t}\n\t}\n\n\tcd := connectorData{\n\t\tRefreshToken: []byte(token.RefreshToken),\n\t}\n\n\tconnData, err := json.Marshal(&cd)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"oidc: failed to encode connector data: %v\", err)\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:            subject,\n\t\tUsername:          name,\n\t\tPreferredUsername: preferredUsername,\n\t\tEmail:             email,\n\t\tEmailVerified:     emailVerified,\n\t\tGroups:            groups,\n\t\tConnectorData:     connData,\n\t}\n\n\tif c.userIDKey != \"\" {\n\t\tuserID, found := claims[c.userIDKey].(string)\n\t\tif !found {\n\t\t\treturn identity, fmt.Errorf(\"oidc: not found %v claim\", c.userIDKey)\n\t\t}\n\t\tidentity.UserID = userID\n\t}\n\n\treturn identity, nil\n}\n"
  },
  {
    "path": "connector/oidc/oidc_test.go",
    "content": "package oidc\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/base64\"\n\t\"encoding/binary\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\nfunc TestKnownBrokenAuthHeaderProvider(t *testing.T) {\n\ttests := []struct {\n\t\tissuerURL string\n\t\texpect    bool\n\t}{\n\t\t{\"https://dev.oktapreview.com\", true},\n\t\t{\"https://dev.okta.com\", true},\n\t\t{\"https://okta.com\", true},\n\t\t{\"https://dev.oktaaccounts.com\", false},\n\t\t{\"https://accounts.google.com\", false},\n\t}\n\n\tfor _, tc := range tests {\n\t\tgot := knownBrokenAuthHeaderProvider(tc.issuerURL)\n\t\tif got != tc.expect {\n\t\t\tt.Errorf(\"knownBrokenAuthHeaderProvider(%q), want=%t, got=%t\", tc.issuerURL, tc.expect, got)\n\t\t}\n\t}\n}\n\nfunc TestHandleCallback(t *testing.T) {\n\tt.Helper()\n\n\ttests := []struct {\n\t\tname                      string\n\t\tuserIDKey                 string\n\t\tuserNameKey               string\n\t\toverrideClaimMapping      bool\n\t\tpreferredUsernameKey      string\n\t\temailKey                  string\n\t\tgroupsKey                 string\n\t\tinsecureSkipEmailVerified bool\n\t\tscopes                    []string\n\t\texpectUserID              string\n\t\texpectUserName            string\n\t\texpectGroups              []string\n\t\texpectPreferredUsername   string\n\t\texpectedEmailField        string\n\t\ttoken                     map[string]interface{}\n\t\tgroupsRegex               string\n\t\tnewGroupFromClaims        []NewGroupFromClaims\n\t\tgroupsPrefix              string\n\t\tgroupsSuffix              string\n\t\tpkceChallenge             string\n\t}{\n\t\t{\n\t\t\tname:               \"simpleCase\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\", \"group2\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\"},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"customEmailClaim\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\temailKey:           \"mail\",\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"mail\":           \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                 \"overrideWithCustomEmailClaim\",\n\t\t\tuserIDKey:            \"\", // not configured\n\t\t\tuserNameKey:          \"\", // not configured\n\t\t\toverrideClaimMapping: true,\n\t\t\temailKey:             \"custommail\",\n\t\t\texpectUserID:         \"subvalue\",\n\t\t\texpectUserName:       \"namevalue\",\n\t\t\texpectedEmailField:   \"customemailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"custommail\":     \"customemailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                      \"email_verified not in claims, configured to be skipped\",\n\t\t\tinsecureSkipEmailVerified: true,\n\t\t\texpectUserID:              \"subvalue\",\n\t\t\texpectUserName:            \"namevalue\",\n\t\t\texpectedEmailField:        \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":   \"subvalue\",\n\t\t\t\t\"name\":  \"namevalue\",\n\t\t\t\t\"email\": \"emailvalue\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"withUserIDKey\",\n\t\t\tuserIDKey:          \"name\",\n\t\t\texpectUserID:       \"namevalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"withUserNameKey\",\n\t\t\tuserNameKey:        \"user_name\",\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"username\",\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"user_name\":      \"username\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                    \"withPreferredUsernameKey\",\n\t\t\tpreferredUsernameKey:    \"username_key\",\n\t\t\texpectUserID:            \"subvalue\",\n\t\t\texpectUserName:          \"namevalue\",\n\t\t\texpectPreferredUsername: \"username_value\",\n\t\t\texpectedEmailField:      \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"username_key\":   \"username_value\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                    \"withoutPreferredUsernameKeyAndBackendReturns\",\n\t\t\texpectUserID:            \"subvalue\",\n\t\t\texpectUserName:          \"namevalue\",\n\t\t\texpectPreferredUsername: \"preferredusernamevalue\",\n\t\t\texpectedEmailField:      \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":                \"subvalue\",\n\t\t\t\t\"name\":               \"namevalue\",\n\t\t\t\t\"preferred_username\": \"preferredusernamevalue\",\n\t\t\t\t\"email\":              \"emailvalue\",\n\t\t\t\t\"email_verified\":     true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                    \"withoutPreferredUsernameKeyAndBackendNotReturn\",\n\t\t\texpectUserID:            \"subvalue\",\n\t\t\texpectUserName:          \"namevalue\",\n\t\t\texpectPreferredUsername: \"\",\n\t\t\texpectedEmailField:      \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                      \"emptyEmailScope\",\n\t\t\texpectUserID:              \"subvalue\",\n\t\t\texpectUserName:            \"namevalue\",\n\t\t\texpectedEmailField:        \"\",\n\t\t\tscopes:                    []string{\"groups\"},\n\t\t\tinsecureSkipEmailVerified: true,\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":       \"subvalue\",\n\t\t\t\t\"name\":      \"namevalue\",\n\t\t\t\t\"user_name\": \"username\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                      \"emptyEmailScopeButEmailProvided\",\n\t\t\texpectUserID:              \"subvalue\",\n\t\t\texpectUserName:            \"namevalue\",\n\t\t\texpectedEmailField:        \"emailvalue\",\n\t\t\tscopes:                    []string{\"groups\"},\n\t\t\tinsecureSkipEmailVerified: true,\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":       \"subvalue\",\n\t\t\t\t\"name\":      \"namevalue\",\n\t\t\t\t\"user_name\": \"username\",\n\t\t\t\t\"email\":     \"emailvalue\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                      \"customGroupsKey\",\n\t\t\tgroupsKey:                 \"cognito:groups\",\n\t\t\texpectUserID:              \"subvalue\",\n\t\t\texpectUserName:            \"namevalue\",\n\t\t\texpectedEmailField:        \"emailvalue\",\n\t\t\texpectGroups:              []string{\"group3\", \"group4\"},\n\t\t\tscopes:                    []string{\"groups\"},\n\t\t\tinsecureSkipEmailVerified: true,\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"user_name\":      \"username\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"cognito:groups\": []string{\"group3\", \"group4\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                      \"customGroupsKeyButGroupsProvided\",\n\t\t\tgroupsKey:                 \"cognito:groups\",\n\t\t\texpectUserID:              \"subvalue\",\n\t\t\texpectUserName:            \"namevalue\",\n\t\t\texpectedEmailField:        \"emailvalue\",\n\t\t\texpectGroups:              []string{\"group1\", \"group2\"},\n\t\t\tscopes:                    []string{\"groups\"},\n\t\t\tinsecureSkipEmailVerified: true,\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"user_name\":      \"username\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\"},\n\t\t\t\t\"cognito:groups\": []string{\"group3\", \"group4\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:                      \"customGroupsKeyDespiteGroupsProvidedButOverride\",\n\t\t\toverrideClaimMapping:      true,\n\t\t\tgroupsKey:                 \"cognito:groups\",\n\t\t\texpectUserID:              \"subvalue\",\n\t\t\texpectUserName:            \"namevalue\",\n\t\t\texpectedEmailField:        \"emailvalue\",\n\t\t\texpectGroups:              []string{\"group3\", \"group4\"},\n\t\t\tscopes:                    []string{\"groups\"},\n\t\t\tinsecureSkipEmailVerified: true,\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"user_name\":      \"username\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\"},\n\t\t\t\t\"cognito:groups\": []string{\"group3\", \"group4\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"singularGroupResponseAsString\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         \"group1\",\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"singularGroupResponseAsMap\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []map[string]string{{\"name\": \"group1\"}},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"multipleGroupResponseAsMap\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\", \"group2\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []map[string]string{{\"name\": \"group1\"}, {\"name\": \"group2\"}},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"newGroupFromClaims\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\", \"gh::acme::pipeline-one\", \"clr_delim-acme-foobar\", \"keep_delim-acme-foo-bar\", \"bk-emailvalue\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\tnewGroupFromClaims: []NewGroupFromClaims{\n\t\t\t\t{ // The basic functionality, should create \"gh::acme::pipeline-one\".\n\t\t\t\t\tClaims: []string{\n\t\t\t\t\t\t\"organization\",\n\t\t\t\t\t\t\"pipeline\",\n\t\t\t\t\t},\n\t\t\t\t\tDelimiter: \"::\",\n\t\t\t\t\tPrefix:    \"gh\",\n\t\t\t\t},\n\t\t\t\t{ // Non existing claims, should not generate any any new group claim.\n\t\t\t\t\tClaims: []string{\n\t\t\t\t\t\t\"non-existing1\",\n\t\t\t\t\t\t\"non-existing2\",\n\t\t\t\t\t},\n\t\t\t\t\tDelimiter: \"::\",\n\t\t\t\t\tPrefix:    \"tfe\",\n\t\t\t\t},\n\t\t\t\t{ // In this case the delimiter character(\"-\") should be removed removed from \"claim-with-delimiter\" claim to ensure the resulting\n\t\t\t\t\t// claim structure is in full control of the Dex operator and not the person creating a new pipeline.\n\t\t\t\t\t// Should create \"clr_delim-acme-foobar\" and not \"tfe-acme-foo-bar\".\n\t\t\t\t\tClaims: []string{\n\t\t\t\t\t\t\"organization\",\n\t\t\t\t\t\t\"claim-with-delimiter\",\n\t\t\t\t\t},\n\t\t\t\t\tDelimiter:      \"-\",\n\t\t\t\t\tClearDelimiter: true,\n\t\t\t\t\tPrefix:         \"clr_delim\",\n\t\t\t\t},\n\t\t\t\t{ // In this case the delimiter character(\"-\") should be NOT removed from \"claim-with-delimiter\" claim.\n\t\t\t\t\t// Should create  \"keep_delim-acme-foo-bar\".\n\t\t\t\t\tClaims: []string{\n\t\t\t\t\t\t\"organization\",\n\t\t\t\t\t\t\"claim-with-delimiter\",\n\t\t\t\t\t},\n\t\t\t\t\tDelimiter: \"-\",\n\t\t\t\t\t// ClearDelimiter: false,\n\t\t\t\t\tPrefix: \"keep_delim\",\n\t\t\t\t},\n\t\t\t\t{ // Ignore non string claims (like arrays), this should result in \"bk-emailvalue\".\n\t\t\t\t\tClaims: []string{\n\t\t\t\t\t\t\"non-string-claim\",\n\t\t\t\t\t\t\"non-string-claim2\",\n\t\t\t\t\t\t\"email\",\n\t\t\t\t\t},\n\t\t\t\t\tDelimiter: \"-\",\n\t\t\t\t\tPrefix:    \"bk\",\n\t\t\t\t},\n\t\t\t},\n\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":                  \"subvalue\",\n\t\t\t\t\"name\":                 \"namevalue\",\n\t\t\t\t\"groups\":               \"group1\",\n\t\t\t\t\"organization\":         \"acme\",\n\t\t\t\t\"pipeline\":             \"pipeline-one\",\n\t\t\t\t\"email\":                \"emailvalue\",\n\t\t\t\t\"email_verified\":       true,\n\t\t\t\t\"claim-with-delimiter\": \"foo-bar\",\n\t\t\t\t\"non-string-claim\": []string{\n\t\t\t\t\t\"element1\",\n\t\t\t\t\t\"element2\",\n\t\t\t\t},\n\t\t\t\t\"non-string-claim2\": 666,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"prefixGroupNames\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"prefix-group1\", \"prefix-group2\", \"prefix-groupA\", \"prefix-groupB\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\tgroupsPrefix:       \"prefix-\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\", \"groupA\", \"groupB\"},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"suffixGroupNames\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1-suffix\", \"group2-suffix\", \"groupA-suffix\", \"groupB-suffix\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\tgroupsSuffix:       \"-suffix\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\", \"groupA\", \"groupB\"},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"preAndSuffixGroupNames\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"prefix-group1-suffix\", \"prefix-group2-suffix\", \"prefix-groupA-suffix\", \"prefix-groupB-suffix\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\tgroupsPrefix:       \"prefix-\",\n\t\t\tgroupsSuffix:       \"-suffix\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\", \"groupA\", \"groupB\"},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"filterGroupClaims\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\tgroupsRegex:        `^.*\\d$`,\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\", \"group2\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\", \"groupA\", \"groupB\"},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"filterGroupClaimsMap\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\tgroupsRegex:        `^.*\\d$`,\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\", \"group2\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []map[string]string{{\"name\": \"group1\"}, {\"name\": \"group2\"}, {\"name\": \"groupA\"}, {\"name\": \"groupB\"}},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"S256PKCEChallenge\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\tpkceChallenge:      \"S256\",\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\", \"group2\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\"},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:               \"plainPKCEChallenge\",\n\t\t\tuserIDKey:          \"\", // not configured\n\t\t\tuserNameKey:        \"\", // not configured\n\t\t\tpkceChallenge:      \"plain\",\n\t\t\texpectUserID:       \"subvalue\",\n\t\t\texpectUserName:     \"namevalue\",\n\t\t\texpectGroups:       []string{\"group1\", \"group2\"},\n\t\t\texpectedEmailField: \"emailvalue\",\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":            \"subvalue\",\n\t\t\t\t\"name\":           \"namevalue\",\n\t\t\t\t\"groups\":         []string{\"group1\", \"group2\"},\n\t\t\t\t\"email\":          \"emailvalue\",\n\t\t\t\t\"email_verified\": true,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tidTokenDesired := true\n\t\t\ttestServer, err := setupServer(tc.token, idTokenDesired)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to setup test server\", err)\n\t\t\t}\n\t\t\tdefer testServer.Close()\n\n\t\t\tvar scopes []string\n\t\t\tif len(tc.scopes) > 0 {\n\t\t\t\tscopes = tc.scopes\n\t\t\t} else {\n\t\t\t\tscopes = []string{\"email\", \"groups\"}\n\t\t\t}\n\t\t\tserverURL := testServer.URL\n\t\t\tbasicAuth := true\n\t\t\tconfig := Config{\n\t\t\t\tIssuer:                    serverURL,\n\t\t\t\tClientID:                  \"clientID\",\n\t\t\t\tClientSecret:              \"clientSecret\",\n\t\t\t\tScopes:                    scopes,\n\t\t\t\tRedirectURI:               fmt.Sprintf(\"%s/callback\", serverURL),\n\t\t\t\tUserIDKey:                 tc.userIDKey,\n\t\t\t\tUserNameKey:               tc.userNameKey,\n\t\t\t\tInsecureSkipEmailVerified: tc.insecureSkipEmailVerified,\n\t\t\t\tInsecureEnableGroups:      true,\n\t\t\t\tBasicAuthUnsupported:      &basicAuth,\n\t\t\t\tOverrideClaimMapping:      tc.overrideClaimMapping,\n\t\t\t\tPKCEChallenge:             tc.pkceChallenge,\n\t\t\t}\n\t\t\tconfig.ClaimMapping.PreferredUsernameKey = tc.preferredUsernameKey\n\t\t\tconfig.ClaimMapping.EmailKey = tc.emailKey\n\t\t\tconfig.ClaimMapping.GroupsKey = tc.groupsKey\n\t\t\tconfig.ClaimMutations.NewGroupFromClaims = tc.newGroupFromClaims\n\t\t\tconfig.ClaimMutations.FilterGroupClaims.GroupsFilter = tc.groupsRegex\n\t\t\tconfig.ClaimMutations.ModifyGroupNames.Prefix = tc.groupsPrefix\n\t\t\tconfig.ClaimMutations.ModifyGroupNames.Suffix = tc.groupsSuffix\n\n\t\t\tconn, err := newConnector(config)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to create new connector\", err)\n\t\t\t}\n\n\t\t\treq, err := newRequestWithAuthCode(testServer.URL, \"someCode\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to create request\", err)\n\t\t\t}\n\n\t\t\tconnectorDataStrTemplate := `{\"codeChallenge\":\"abcdefgh123456qwertuiop89101112uvpwizABC234\",\"codeChallengeMethod\":\"%s\"}`\n\t\t\tconnectorDataStr := fmt.Sprintf(connectorDataStrTemplate, config.PKCEChallenge)\n\t\t\tconnectorData := []byte(connectorDataStr)\n\n\t\t\tidentity, err := conn.HandleCallback(connector.Scopes{Groups: true}, connectorData, req)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"handle callback failed\", err)\n\t\t\t}\n\n\t\t\texpectEquals(t, identity.UserID, tc.expectUserID)\n\t\t\texpectEquals(t, identity.Username, tc.expectUserName)\n\t\t\texpectEquals(t, identity.PreferredUsername, tc.expectPreferredUsername)\n\t\t\texpectEquals(t, identity.Email, tc.expectedEmailField)\n\t\t\texpectEquals(t, identity.EmailVerified, true)\n\t\t\texpectEquals(t, identity.Groups, tc.expectGroups)\n\t\t})\n\t}\n}\n\nfunc TestRefresh(t *testing.T) {\n\tt.Helper()\n\n\ttests := []struct {\n\t\tname           string\n\t\texpectUserID   string\n\t\texpectUserName string\n\t\tidTokenDesired bool\n\t\ttoken          map[string]interface{}\n\t}{\n\t\t{\n\t\t\tname:           \"IDTokenOnRefresh\",\n\t\t\texpectUserID:   \"subvalue\",\n\t\t\texpectUserName: \"namevalue\",\n\t\t\tidTokenDesired: true,\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":  \"subvalue\",\n\t\t\t\t\"name\": \"namevalue\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:           \"NoIDTokenOnRefresh\",\n\t\t\texpectUserID:   \"subvalue\",\n\t\t\texpectUserName: \"namevalue\",\n\t\t\tidTokenDesired: false,\n\t\t\ttoken: map[string]interface{}{\n\t\t\t\t\"sub\":  \"subvalue\",\n\t\t\t\t\"name\": \"namevalue\",\n\t\t\t},\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttestServer, err := setupServer(tc.token, tc.idTokenDesired)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to setup test server\", err)\n\t\t\t}\n\t\t\tdefer testServer.Close()\n\n\t\t\tscopes := []string{\"openid\", \"offline_access\"}\n\t\t\tserverURL := testServer.URL\n\t\t\tconfig := Config{\n\t\t\t\tIssuer:       serverURL,\n\t\t\t\tClientID:     \"clientID\",\n\t\t\t\tClientSecret: \"clientSecret\",\n\t\t\t\tScopes:       scopes,\n\t\t\t\tRedirectURI:  fmt.Sprintf(\"%s/callback\", serverURL),\n\t\t\t\tGetUserInfo:  true,\n\t\t\t}\n\n\t\t\tconn, err := newConnector(config)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to create new connector\", err)\n\t\t\t}\n\n\t\t\treq, err := newRequestWithAuthCode(testServer.URL, \"someCode\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to create request\", err)\n\t\t\t}\n\n\t\t\trefreshTokenStr := \"{\\\"RefreshToken\\\":\\\"asdf\\\"}\"\n\t\t\trefreshToken := []byte(refreshTokenStr)\n\n\t\t\tidentity := connector.Identity{\n\t\t\t\tUserID:        tc.expectUserID,\n\t\t\t\tUsername:      tc.expectUserName,\n\t\t\t\tConnectorData: refreshToken,\n\t\t\t}\n\n\t\t\trefreshIdentity, err := conn.Refresh(req.Context(), connector.Scopes{OfflineAccess: true}, identity)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"Refresh failed\", err)\n\t\t\t}\n\n\t\t\texpectEquals(t, refreshIdentity.UserID, tc.expectUserID)\n\t\t\texpectEquals(t, refreshIdentity.Username, tc.expectUserName)\n\t\t})\n\t}\n}\n\nfunc TestTokenIdentity(t *testing.T) {\n\ttokenTypeAccess := \"urn:ietf:params:oauth:token-type:access_token\"\n\ttokenTypeID := \"urn:ietf:params:oauth:token-type:id_token\"\n\tlong2short := map[string]string{\n\t\ttokenTypeAccess: \"access_token\",\n\t\ttokenTypeID:     \"id_token\",\n\t}\n\n\ttests := []struct {\n\t\tname        string\n\t\tsubjectType string\n\t\tuserInfo    bool\n\t\texpectError bool\n\t}{\n\t\t{\n\t\t\tname:        \"id_token\",\n\t\t\tsubjectType: tokenTypeID,\n\t\t}, {\n\t\t\tname:        \"access_token\",\n\t\t\tsubjectType: tokenTypeAccess,\n\t\t\texpectError: true,\n\t\t}, {\n\t\t\tname:        \"id_token with user info\",\n\t\t\tsubjectType: tokenTypeID,\n\t\t\tuserInfo:    true,\n\t\t}, {\n\t\t\tname:        \"access_token with user info\",\n\t\t\tsubjectType: tokenTypeAccess,\n\t\t\tuserInfo:    true,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\t\t\tctx, cancel := context.WithCancel(ctx)\n\t\t\tdefer cancel()\n\n\t\t\ttestServer, err := setupServer(map[string]any{\n\t\t\t\t\"sub\":  \"subvalue\",\n\t\t\t\t\"name\": \"namevalue\",\n\t\t\t}, true)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to setup test server\", err)\n\t\t\t}\n\t\t\tconn, err := newConnector(Config{\n\t\t\t\tIssuer:      testServer.URL,\n\t\t\t\tScopes:      []string{\"openid\", \"groups\"},\n\t\t\t\tGetUserInfo: tc.userInfo,\n\t\t\t})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to create new connector\", err)\n\t\t\t}\n\n\t\t\tres, err := http.Get(testServer.URL + \"/token\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to get initial token\", err)\n\t\t\t}\n\t\t\tdefer res.Body.Close()\n\t\t\tvar tokenResponse map[string]any\n\t\t\terr = json.NewDecoder(res.Body).Decode(&tokenResponse)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(\"failed to decode initial token\", err)\n\t\t\t}\n\n\t\t\torigToken := tokenResponse[long2short[tc.subjectType]].(string)\n\t\t\tidentity, err := conn.TokenIdentity(ctx, tc.subjectType, origToken)\n\t\t\tif err != nil {\n\t\t\t\tif tc.expectError {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tt.Fatal(\"failed to get token identity\", err)\n\t\t\t}\n\n\t\t\t// assert identity\n\t\t\texpectEquals(t, identity.UserID, \"subvalue\")\n\t\t\texpectEquals(t, identity.Username, \"namevalue\")\n\t\t})\n\t}\n}\n\nfunc TestPromptType(t *testing.T) {\n\tpointer := func(s string) *string {\n\t\treturn &s\n\t}\n\n\ttests := []struct {\n\t\tname       string\n\t\tpromptType *string\n\t\tres        string\n\t}{\n\t\t{name: \"none\", promptType: pointer(\"none\"), res: \"none\"},\n\t\t{name: \"provided empty string\", promptType: pointer(\"\"), res: \"\"},\n\t\t{name: \"login\", promptType: pointer(\"login\"), res: \"login\"},\n\t\t{name: \"consent\", promptType: pointer(\"consent\"), res: \"consent\"},\n\t\t{name: \"default value\", promptType: nil, res: \"consent\"},\n\t}\n\n\ttestServer, err := setupServer(nil, true)\n\trequire.NoError(t, err)\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tconn, err := newConnector(Config{\n\t\t\t\tIssuer:     testServer.URL,\n\t\t\t\tScopes:     []string{\"openid\", \"groups\"},\n\t\t\t\tPromptType: tc.promptType,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Equal(t, tc.res, conn.promptType)\n\t\t})\n\t}\n}\n\nfunc TestProviderOverride(t *testing.T) {\n\ttestServer, err := setupServer(map[string]any{\n\t\t\"sub\":  \"subvalue\",\n\t\t\"name\": \"namevalue\",\n\t}, true)\n\tif err != nil {\n\t\tt.Fatal(\"failed to setup test server\", err)\n\t}\n\n\tt.Run(\"No override\", func(t *testing.T) {\n\t\tconn, err := newConnector(Config{\n\t\t\tIssuer: testServer.URL,\n\t\t\tScopes: []string{\"openid\", \"groups\"},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"failed to create new connector\", err)\n\t\t}\n\n\t\texpAuth := fmt.Sprintf(\"%s/authorize\", testServer.URL)\n\t\tif conn.provider.Endpoint().AuthURL != expAuth {\n\t\t\tt.Fatalf(\"unexpected auth URL: %s, expected: %s\\n\", conn.provider.Endpoint().AuthURL, expAuth)\n\t\t}\n\n\t\texpToken := fmt.Sprintf(\"%s/token\", testServer.URL)\n\t\tif conn.provider.Endpoint().TokenURL != expToken {\n\t\t\tt.Fatalf(\"unexpected token URL: %s, expected: %s\\n\", conn.provider.Endpoint().TokenURL, expToken)\n\t\t}\n\t})\n\n\tt.Run(\"Override\", func(t *testing.T) {\n\t\tconn, err := newConnector(Config{\n\t\t\tIssuer:                     testServer.URL,\n\t\t\tScopes:                     []string{\"openid\", \"groups\"},\n\t\t\tProviderDiscoveryOverrides: ProviderDiscoveryOverrides{TokenURL: \"/test1\", AuthURL: \"/test2\"},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatal(\"failed to create new connector\", err)\n\t\t}\n\n\t\texpAuth := \"/test2\"\n\t\tif conn.provider.Endpoint().AuthURL != expAuth {\n\t\t\tt.Fatalf(\"unexpected auth URL: %s, expected: %s\\n\", conn.provider.Endpoint().AuthURL, expAuth)\n\t\t}\n\n\t\texpToken := \"/test1\"\n\t\tif conn.provider.Endpoint().TokenURL != expToken {\n\t\t\tt.Fatalf(\"unexpected token URL: %s, expected: %s\\n\", conn.provider.Endpoint().TokenURL, expToken)\n\t\t}\n\t})\n}\n\nfunc setupServer(tok map[string]interface{}, idTokenDesired bool) (*httptest.Server, error) {\n\tkey, err := rsa.GenerateKey(rand.Reader, 1024)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to generate rsa key: %v\", err)\n\t}\n\n\tjwk := jose.JSONWebKey{\n\t\tKey:       key,\n\t\tKeyID:     \"keyId\",\n\t\tAlgorithm: \"RSA\",\n\t}\n\n\tmux := http.NewServeMux()\n\n\tmux.HandleFunc(\"/keys\", func(w http.ResponseWriter, r *http.Request) {\n\t\tjson.NewEncoder(w).Encode(&map[string]interface{}{\n\t\t\t\"keys\": []map[string]interface{}{{\n\t\t\t\t\"alg\": jwk.Algorithm,\n\t\t\t\t\"kty\": jwk.Algorithm,\n\t\t\t\t\"kid\": jwk.KeyID,\n\t\t\t\t\"n\":   n(&key.PublicKey),\n\t\t\t\t\"e\":   e(&key.PublicKey),\n\t\t\t}},\n\t\t})\n\t})\n\n\tmux.HandleFunc(\"/token\", func(w http.ResponseWriter, r *http.Request) {\n\t\turl := fmt.Sprintf(\"http://%s\", r.Host)\n\t\ttok[\"iss\"] = url\n\t\ttok[\"exp\"] = time.Now().Add(time.Hour).Unix()\n\t\ttok[\"aud\"] = \"clientID\"\n\t\ttoken, err := newToken(&jwk, tok)\n\t\tif err != nil {\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t}\n\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tif idTokenDesired {\n\t\t\tjson.NewEncoder(w).Encode(&map[string]string{\n\t\t\t\t\"access_token\": token,\n\t\t\t\t\"id_token\":     token,\n\t\t\t\t\"token_type\":   \"Bearer\",\n\t\t\t})\n\t\t} else {\n\t\t\tjson.NewEncoder(w).Encode(&map[string]string{\n\t\t\t\t\"access_token\": token,\n\t\t\t\t\"token_type\":   \"Bearer\",\n\t\t\t})\n\t\t}\n\t})\n\n\tmux.HandleFunc(\"/userinfo\", func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(tok)\n\t})\n\n\tmux.HandleFunc(\"/.well-known/openid-configuration\", func(w http.ResponseWriter, r *http.Request) {\n\t\turl := fmt.Sprintf(\"http://%s\", r.Host)\n\n\t\tjson.NewEncoder(w).Encode(&map[string]string{\n\t\t\t\"issuer\":                 url,\n\t\t\t\"token_endpoint\":         fmt.Sprintf(\"%s/token\", url),\n\t\t\t\"authorization_endpoint\": fmt.Sprintf(\"%s/authorize\", url),\n\t\t\t\"userinfo_endpoint\":      fmt.Sprintf(\"%s/userinfo\", url),\n\t\t\t\"jwks_uri\":               fmt.Sprintf(\"%s/keys\", url),\n\t\t})\n\t})\n\n\treturn httptest.NewServer(mux), nil\n}\n\nfunc newToken(key *jose.JSONWebKey, claims map[string]interface{}) (string, error) {\n\tsigningKey := jose.SigningKey{\n\t\tKey:       key,\n\t\tAlgorithm: jose.RS256,\n\t}\n\n\tsigner, err := jose.NewSigner(signingKey, &jose.SignerOptions{})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create new signer: %v\", err)\n\t}\n\n\tpayload, err := json.Marshal(claims)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to marshal claims: %v\", err)\n\t}\n\n\tsignature, err := signer.Sign(payload)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to sign: %v\", err)\n\t}\n\treturn signature.CompactSerialize()\n}\n\nfunc newConnector(config Config) (*oidcConnector, error) {\n\tlogger := slog.New(slog.DiscardHandler)\n\tconn, err := config.Open(\"id\", logger)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"unable to open: %v\", err)\n\t}\n\n\toidcConn, ok := conn.(*oidcConnector)\n\tif !ok {\n\t\treturn nil, errors.New(\"failed to convert to oidcConnector\")\n\t}\n\n\treturn oidcConn, nil\n}\n\nfunc newRequestWithAuthCode(serverURL string, code string) (*http.Request, error) {\n\treq, err := http.NewRequest(\"GET\", serverURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create request: %v\", err)\n\t}\n\n\tvalues := req.URL.Query()\n\tvalues.Add(\"code\", code)\n\treq.URL.RawQuery = values.Encode()\n\n\treturn req, nil\n}\n\nfunc n(pub *rsa.PublicKey) string {\n\treturn encode(pub.N.Bytes())\n}\n\nfunc e(pub *rsa.PublicKey) string {\n\tdata := make([]byte, 8)\n\tbinary.BigEndian.PutUint64(data, uint64(pub.E))\n\treturn encode(bytes.TrimLeft(data, \"\\x00\"))\n}\n\nfunc encode(payload []byte) string {\n\tresult := base64.URLEncoding.EncodeToString(payload)\n\treturn strings.TrimRight(result, \"=\")\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n"
  },
  {
    "path": "connector/openshift/openshift.go",
    "content": "package openshift\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/groups\"\n\t\"github.com/dexidp/dex/pkg/httpclient\"\n\t\"github.com/dexidp/dex/storage/kubernetes/k8sapi\"\n)\n\nconst (\n\twellKnownURLPath = \"/.well-known/oauth-authorization-server\"\n\tusersURLPath     = \"/apis/user.openshift.io/v1/users/~\"\n)\n\n// Config holds configuration options for OpenShift login\ntype Config struct {\n\tIssuer       string   `json:\"issuer\"`\n\tClientID     string   `json:\"clientID\"`\n\tClientSecret string   `json:\"clientSecret\"`\n\tRedirectURI  string   `json:\"redirectURI\"`\n\tGroups       []string `json:\"groups\"`\n\tInsecureCA   bool     `json:\"insecureCA\"`\n\tRootCA       string   `json:\"rootCA\"`\n}\n\nvar (\n\t_ connector.CallbackConnector = (*openshiftConnector)(nil)\n\t_ connector.RefreshConnector  = (*openshiftConnector)(nil)\n)\n\ntype openshiftConnector struct {\n\tapiURL       string\n\tredirectURI  string\n\tclientID     string\n\tclientSecret string\n\tcancel       context.CancelFunc\n\tlogger       *slog.Logger\n\thttpClient   *http.Client\n\toauth2Config *oauth2.Config\n\tinsecureCA   bool\n\trootCA       string\n\tgroups       []string\n}\n\ntype user struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\tIdentities        []string `json:\"identities\" protobuf:\"bytes,3,rep,name=identities\"`\n\tFullName          string   `json:\"fullName,omitempty\" protobuf:\"bytes,2,opt,name=fullName\"`\n\tGroups            []string `json:\"groups\" protobuf:\"bytes,4,rep,name=groups\"`\n}\n\n// Open returns a connector which can be used to login users through an upstream\n// OpenShift OAuth2 provider.\nfunc (c *Config) Open(id string, logger *slog.Logger) (conn connector.Connector, err error) {\n\tvar rootCAs []string\n\tif c.RootCA != \"\" {\n\t\trootCAs = append(rootCAs, c.RootCA)\n\t}\n\n\thttpClient, err := httpclient.NewHTTPClient(rootCAs, c.InsecureCA)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create HTTP client: %w\", err)\n\t}\n\n\treturn c.OpenWithHTTPClient(id, logger, httpClient)\n}\n\n// OpenWithHTTPClient returns a connector which can be used to login users through an upstream\n// OpenShift OAuth2 provider. It provides the ability to inject a http.Client.\nfunc (c *Config) OpenWithHTTPClient(id string, logger *slog.Logger,\n\thttpClient *http.Client,\n) (conn connector.Connector, err error) {\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\n\twellKnownURL := strings.TrimSuffix(c.Issuer, \"/\") + wellKnownURLPath\n\treq, err := http.NewRequest(http.MethodGet, wellKnownURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create a request to OpenShift endpoint %w\", err)\n\t}\n\n\topenshiftConnector := openshiftConnector{\n\t\tapiURL:       c.Issuer,\n\t\tcancel:       cancel,\n\t\tclientID:     c.ClientID,\n\t\tclientSecret: c.ClientSecret,\n\t\tinsecureCA:   c.InsecureCA,\n\t\tlogger:       logger.With(slog.Group(\"connector\", \"type\", \"openshift\", \"id\", id)),\n\t\tredirectURI:  c.RedirectURI,\n\t\trootCA:       c.RootCA,\n\t\tgroups:       c.Groups,\n\t\thttpClient:   httpClient,\n\t}\n\n\tvar metadata struct {\n\t\tAuth  string `json:\"authorization_endpoint\"`\n\t\tToken string `json:\"token_endpoint\"`\n\t}\n\n\tresp, err := openshiftConnector.httpClient.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to query OpenShift endpoint %w\", err)\n\t}\n\n\tdefer resp.Body.Close()\n\n\tif err := json.NewDecoder(resp.Body).Decode(&metadata); err != nil {\n\t\treturn nil, fmt.Errorf(\"discovery through endpoint %s failed to decode body: %w\",\n\t\t\twellKnownURL, err)\n\t}\n\n\topenshiftConnector.oauth2Config = &oauth2.Config{\n\t\tClientID:     c.ClientID,\n\t\tClientSecret: c.ClientSecret,\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL: metadata.Auth, TokenURL: metadata.Token,\n\t\t},\n\t\tScopes:      []string{\"user:info\"},\n\t\tRedirectURL: c.RedirectURI,\n\t}\n\treturn &openshiftConnector, nil\n}\n\nfunc (c *openshiftConnector) Close() error {\n\tc.cancel()\n\treturn nil\n}\n\n// LoginURL returns the URL to redirect the user to login with.\nfunc (c *openshiftConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, []byte, error) {\n\tif c.redirectURI != callbackURL {\n\t\treturn \"\", nil, fmt.Errorf(\"expected callback URL %q did not match the URL in the config %q\",\n\t\t\tcallbackURL, c.redirectURI)\n\t}\n\treturn c.oauth2Config.AuthCodeURL(state), nil, nil\n}\n\ntype oauth2Error struct {\n\terror            string\n\terrorDescription string\n}\n\nfunc (e *oauth2Error) Error() string {\n\tif e.errorDescription == \"\" {\n\t\treturn e.error\n\t}\n\treturn e.error + \": \" + e.errorDescription\n}\n\n// HandleCallback parses the request and returns the user's identity\nfunc (c *openshiftConnector) HandleCallback(s connector.Scopes,\n\tconnData []byte,\n\tr *http.Request,\n) (identity connector.Identity, err error) {\n\tq := r.URL.Query()\n\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\treturn identity, &oauth2Error{errType, q.Get(\"error_description\")}\n\t}\n\n\tctx := r.Context()\n\tif c.httpClient != nil {\n\t\tctx = context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)\n\t}\n\n\ttoken, err := c.oauth2Config.Exchange(ctx, q.Get(\"code\"))\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"oidc: failed to get token: %v\", err)\n\t}\n\n\treturn c.identity(ctx, s, token)\n}\n\nfunc (c *openshiftConnector) Refresh(ctx context.Context, s connector.Scopes,\n\toldID connector.Identity,\n) (connector.Identity, error) {\n\tvar token oauth2.Token\n\terr := json.Unmarshal(oldID.ConnectorData, &token)\n\tif err != nil {\n\t\treturn connector.Identity{}, fmt.Errorf(\"parsing token: %w\", err)\n\t}\n\tif c.httpClient != nil {\n\t\tctx = context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)\n\t}\n\treturn c.identity(ctx, s, &token)\n}\n\nfunc (c *openshiftConnector) identity(ctx context.Context, s connector.Scopes,\n\ttoken *oauth2.Token,\n) (identity connector.Identity, err error) {\n\tclient := c.oauth2Config.Client(ctx, token)\n\tuser, err := c.user(ctx, client)\n\tif err != nil {\n\t\treturn identity, fmt.Errorf(\"openshift: get user: %v\", err)\n\t}\n\n\tif len(c.groups) > 0 {\n\t\tvalidGroups := validateAllowedGroups(user.Groups, c.groups)\n\n\t\tif !validGroups {\n\t\t\treturn identity, fmt.Errorf(\"openshift: user %q is not in any of the required groups\", user.Name)\n\t\t}\n\t}\n\n\tidentity = connector.Identity{\n\t\tUserID:            user.UID,\n\t\tUsername:          user.Name,\n\t\tPreferredUsername: user.Name,\n\t\tEmail:             user.Name,\n\t\tGroups:            user.Groups,\n\t}\n\n\tif s.OfflineAccess {\n\t\tconnData, err := json.Marshal(token)\n\t\tif err != nil {\n\t\t\treturn identity, fmt.Errorf(\"marshal connector data: %v\", err)\n\t\t}\n\t\tidentity.ConnectorData = connData\n\t}\n\n\treturn identity, nil\n}\n\n// user function returns the OpenShift user associated with the authenticated user\nfunc (c *openshiftConnector) user(ctx context.Context, client *http.Client) (u user, err error) {\n\turl := c.apiURL + usersURLPath\n\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"new req: %v\", err)\n\t}\n\n\tresp, err := client.Do(req.WithContext(ctx))\n\tif err != nil {\n\t\treturn u, fmt.Errorf(\"get URL %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn u, fmt.Errorf(\"read body: %v\", err)\n\t\t}\n\t\treturn u, fmt.Errorf(\"%s: %s\", resp.Status, body)\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(&u); err != nil {\n\t\treturn u, fmt.Errorf(\"JSON decode: %v\", err)\n\t}\n\n\treturn u, err\n}\n\nfunc validateAllowedGroups(userGroups, allowedGroups []string) bool {\n\tmatchingGroups := groups.Filter(userGroups, allowedGroups)\n\n\treturn len(matchingGroups) != 0\n}\n"
  },
  {
    "path": "connector/openshift/openshift_test.go",
    "content": "package openshift\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/httpclient\"\n\t\"github.com/dexidp/dex/storage/kubernetes/k8sapi\"\n)\n\nfunc TestOpen(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\t_, err = http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\tc := Config{\n\t\tIssuer:       s.URL,\n\t\tClientID:     \"testClientId\",\n\t\tClientSecret: \"testClientSecret\",\n\t\tRedirectURI:  \"https://localhost/callback\",\n\t\tInsecureCA:   true,\n\t}\n\n\tlogger := slog.New(slog.DiscardHandler)\n\n\toconfig, err := c.Open(\"id\", logger)\n\n\toc, ok := oconfig.(*openshiftConnector)\n\n\texpectNil(t, err)\n\texpectEquals(t, ok, true)\n\texpectEquals(t, oc.apiURL, s.URL)\n\texpectEquals(t, oc.clientID, \"testClientId\")\n\texpectEquals(t, oc.clientSecret, \"testClientSecret\")\n\texpectEquals(t, oc.redirectURI, \"https://localhost/callback\")\n\texpectEquals(t, oc.oauth2Config.Endpoint.AuthURL, fmt.Sprintf(\"%s/oauth/authorize\", s.URL))\n\texpectEquals(t, oc.oauth2Config.Endpoint.TokenURL, fmt.Sprintf(\"%s/oauth/token\", s.URL))\n}\n\nfunc TestGetUser(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/apis/user.openshift.io/v1/users/~\": user{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"jdoe\",\n\t\t\t},\n\t\t\tFullName: \"John Doe\",\n\t\t\tGroups:   []string{\"users\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\t_, err = http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\th, err := httpclient.NewHTTPClient(nil, true)\n\n\texpectNil(t, err)\n\n\toc := openshiftConnector{apiURL: s.URL, httpClient: h}\n\tu, err := oc.user(context.Background(), h)\n\n\texpectNil(t, err)\n\texpectEquals(t, u.Name, \"jdoe\")\n\texpectEquals(t, u.FullName, \"John Doe\")\n\texpectEquals(t, len(u.Groups), 1)\n}\n\nfunc TestVerifySingleGroupFn(t *testing.T) {\n\tallowedGroups := []string{\"users\"}\n\tgroupMembership := []string{\"users\", \"org1\"}\n\n\tvalidGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)\n\n\texpectEquals(t, validGroupMembership, true)\n}\n\nfunc TestVerifySingleGroupFailureFn(t *testing.T) {\n\tallowedGroups := []string{\"admins\"}\n\tgroupMembership := []string{\"users\"}\n\n\tvalidGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)\n\n\texpectEquals(t, validGroupMembership, false)\n}\n\nfunc TestVerifyMultipleGroupFn(t *testing.T) {\n\tallowedGroups := []string{\"users\", \"admins\"}\n\tgroupMembership := []string{\"users\", \"org1\"}\n\n\tvalidGroupMembership := validateAllowedGroups(groupMembership, allowedGroups)\n\n\texpectEquals(t, validGroupMembership, true)\n}\n\nfunc TestVerifyGroup(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/apis/user.openshift.io/v1/users/~\": user{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"jdoe\",\n\t\t\t},\n\t\t\tFullName: \"John Doe\",\n\t\t\tGroups:   []string{\"users\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\t_, err = http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\th, err := httpclient.NewHTTPClient(nil, true)\n\n\texpectNil(t, err)\n\n\toc := openshiftConnector{apiURL: s.URL, httpClient: h}\n\tu, err := oc.user(context.Background(), h)\n\n\texpectNil(t, err)\n\texpectEquals(t, u.Name, \"jdoe\")\n\texpectEquals(t, u.FullName, \"John Doe\")\n\texpectEquals(t, len(u.Groups), 1)\n}\n\nfunc TestCallbackIdentity(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\t\"/apis/user.openshift.io/v1/users/~\": user{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"jdoe\",\n\t\t\t\tUID:  \"12345\",\n\t\t\t},\n\t\t\tFullName: \"John Doe\",\n\t\t\tGroups:   []string{\"users\"},\n\t\t},\n\t\t\"/oauth/token\": map[string]interface{}{\n\t\t\t\"access_token\": \"oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC\",\n\t\t\t\"expires_in\":   \"30\",\n\t\t},\n\t})\n\tdefer s.Close()\n\n\thostURL, err := url.Parse(s.URL)\n\texpectNil(t, err)\n\n\treq, err := http.NewRequest(\"GET\", hostURL.String(), nil)\n\texpectNil(t, err)\n\n\th, err := httpclient.NewHTTPClient(nil, true)\n\n\texpectNil(t, err)\n\n\toc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL:  fmt.Sprintf(\"%s/oauth/authorize\", s.URL),\n\t\t\tTokenURL: fmt.Sprintf(\"%s/oauth/token\", s.URL),\n\t\t},\n\t}}\n\tidentity, err := oc.HandleCallback(connector.Scopes{Groups: true}, nil, req)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.UserID, \"12345\")\n\texpectEquals(t, identity.Username, \"jdoe\")\n\texpectEquals(t, identity.PreferredUsername, \"jdoe\")\n\texpectEquals(t, identity.Email, \"jdoe\")\n\texpectEquals(t, len(identity.Groups), 1)\n\texpectEquals(t, identity.Groups[0], \"users\")\n}\n\nfunc TestRefreshIdentity(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\tusersURLPath: user{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"jdoe\",\n\t\t\t\tUID:  \"12345\",\n\t\t\t},\n\t\t\tFullName: \"John Doe\",\n\t\t\tGroups:   []string{\"users\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\th, err := httpclient.NewHTTPClient(nil, true)\n\texpectNil(t, err)\n\n\toc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL:  fmt.Sprintf(\"%s/oauth/authorize\", s.URL),\n\t\t\tTokenURL: fmt.Sprintf(\"%s/oauth/token\", s.URL),\n\t\t},\n\t}}\n\n\tdata, err := json.Marshal(oauth2.Token{AccessToken: \"fFAGRNJru1FTz70BzhT3Zg\"})\n\texpectNil(t, err)\n\n\toldID := connector.Identity{ConnectorData: data}\n\n\tidentity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID)\n\n\texpectNil(t, err)\n\texpectEquals(t, identity.UserID, \"12345\")\n\texpectEquals(t, identity.Username, \"jdoe\")\n\texpectEquals(t, identity.PreferredUsername, \"jdoe\")\n\texpectEquals(t, identity.Email, \"jdoe\")\n\texpectEquals(t, len(identity.Groups), 1)\n\texpectEquals(t, identity.Groups[0], \"users\")\n}\n\nfunc TestRefreshIdentityFailure(t *testing.T) {\n\ts := newTestServer(map[string]interface{}{\n\t\tusersURLPath: user{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"jdoe\",\n\t\t\t\tUID:  \"12345\",\n\t\t\t},\n\t\t\tFullName: \"John Doe\",\n\t\t\tGroups:   []string{\"users\"},\n\t\t},\n\t})\n\tdefer s.Close()\n\n\th, err := httpclient.NewHTTPClient(nil, true)\n\texpectNil(t, err)\n\n\toc := openshiftConnector{apiURL: s.URL, httpClient: h, oauth2Config: &oauth2.Config{\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL:  fmt.Sprintf(\"%s/oauth/authorize\", s.URL),\n\t\t\tTokenURL: fmt.Sprintf(\"%s/oauth/token\", s.URL),\n\t\t},\n\t}}\n\n\tdata, err := json.Marshal(oauth2.Token{AccessToken: \"oRzxVjCnohYRHEYEhZshkmakKmoyVoTjfUGC\", Expiry: time.Now().Add(-time.Hour)})\n\texpectNil(t, err)\n\n\toldID := connector.Identity{ConnectorData: data}\n\n\tidentity, err := oc.Refresh(context.Background(), connector.Scopes{Groups: true}, oldID)\n\texpectNotNil(t, err)\n\texpectEquals(t, connector.Identity{}, identity)\n}\n\nfunc newTestServer(responses map[string]interface{}) *httptest.Server {\n\tvar s *httptest.Server\n\ts = httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tresponses[\"/.well-known/oauth-authorization-server\"] = map[string]interface{}{\n\t\t\t\"issuer\":                           s.URL,\n\t\t\t\"authorization_endpoint\":           fmt.Sprintf(\"%s/oauth/authorize\", s.URL),\n\t\t\t\"token_endpoint\":                   fmt.Sprintf(\"%s/oauth/token\", s.URL),\n\t\t\t\"scopes_supported\":                 []string{\"user:full\", \"user:info\", \"user:check-access\", \"user:list-scoped-projects\", \"user:list-projects\"},\n\t\t\t\"response_types_supported\":         []string{\"token\", \"code\"},\n\t\t\t\"grant_types_supported\":            []string{\"authorization_code\", \"implicit\"},\n\t\t\t\"code_challenge_methods_supported\": []string{\"plain\", \"S256\"},\n\t\t}\n\n\t\tresponse := responses[r.RequestURI]\n\t\tw.Header().Add(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(response)\n\t}))\n\n\treturn s\n}\n\nfunc expectNil(t *testing.T, a interface{}) {\n\tif a != nil {\n\t\tt.Errorf(\"Expected %+v to equal nil\", a)\n\t}\n}\n\nfunc expectEquals(t *testing.T, a interface{}, b interface{}) {\n\tif !reflect.DeepEqual(a, b) {\n\t\tt.Errorf(\"Expected %+v to equal %+v\", a, b)\n\t}\n}\n\nfunc expectNotNil(t *testing.T, a interface{}) {\n\tif a == nil {\n\t\tt.Errorf(\"Expected %+v to not equal nil\", a)\n\t}\n}\n"
  },
  {
    "path": "connector/saml/saml.go",
    "content": "// Package saml contains login methods for SAML.\npackage saml\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/beevik/etree\"\n\txrv \"github.com/mattermost/xml-roundtrip-validator\"\n\t\"github.com/pkg/errors\"\n\tdsig \"github.com/russellhaering/goxmldsig\"\n\t\"github.com/russellhaering/goxmldsig/etreeutils\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/groups\"\n)\n\nconst (\n\tbindingRedirect = \"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n\tbindingPOST     = \"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n\n\tnameIDFormatEmailAddress = \"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\"\n\tnameIDFormatUnspecified  = \"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\"\n\tnameIDFormatX509Subject  = \"urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName\"\n\tnameIDFormatWindowsDN    = \"urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName\"\n\tnameIDFormatEncrypted    = \"urn:oasis:names:tc:SAML:2.0:nameid-format:encrypted\"\n\tnameIDFormatEntity       = \"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\"\n\tnameIDFormatKerberos     = \"urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos\"\n\tnameIDFormatPersistent   = \"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\"\n\tnameIDformatTransient    = \"urn:oasis:names:tc:SAML:2.0:nameid-format:transient\"\n\n\t// top level status codes\n\tstatusCodeSuccess = \"urn:oasis:names:tc:SAML:2.0:status:Success\"\n\n\t// subject confirmation methods\n\tsubjectConfirmationMethodBearer = \"urn:oasis:names:tc:SAML:2.0:cm:bearer\"\n\n\t// allowed clock drift for timestamp validation\n\tallowedClockDrift = time.Duration(30) * time.Second\n)\n\nvar (\n\tnameIDFormats = []string{\n\t\tnameIDFormatEmailAddress,\n\t\tnameIDFormatUnspecified,\n\t\tnameIDFormatX509Subject,\n\t\tnameIDFormatWindowsDN,\n\t\tnameIDFormatEncrypted,\n\t\tnameIDFormatEntity,\n\t\tnameIDFormatKerberos,\n\t\tnameIDFormatPersistent,\n\t\tnameIDformatTransient,\n\t}\n\tnameIDFormatLookup = make(map[string]string)\n\n\tlookupOnce sync.Once\n)\n\n// Config represents configuration options for the SAML provider.\ntype Config struct {\n\t// TODO(ericchiang): A bunch of these fields could be auto-filled if\n\t// we supported SAML metadata discovery.\n\t//\n\t// https://www.oasis-open.org/committees/download.php/35391/sstc-saml-metadata-errata-2.0-wd-04-diff.pdf\n\n\tEntityIssuer string `json:\"entityIssuer\"`\n\tSSOIssuer    string `json:\"ssoIssuer\"`\n\tSSOURL       string `json:\"ssoURL\"`\n\n\t// X509 CA file or raw data to verify XML signatures.\n\tCA     string `json:\"ca\"`\n\tCAData []byte `json:\"caData\"`\n\n\tInsecureSkipSignatureValidation bool `json:\"insecureSkipSignatureValidation\"`\n\n\t// Assertion attribute names to lookup various claims with.\n\tUsernameAttr string `json:\"usernameAttr\"`\n\tEmailAttr    string `json:\"emailAttr\"`\n\tGroupsAttr   string `json:\"groupsAttr\"`\n\t// If GroupsDelim is supplied the connector assumes groups are returned as a\n\t// single string instead of multiple attribute values. This delimiter will be\n\t// used split the groups string.\n\tGroupsDelim   string   `json:\"groupsDelim\"`\n\tAllowedGroups []string `json:\"allowedGroups\"`\n\tFilterGroups  bool     `json:\"filterGroups\"`\n\tRedirectURI   string   `json:\"redirectURI\"`\n\n\t// Requested format of the NameID. The NameID value is is mapped to the ID Token\n\t// 'sub' claim.\n\t//\n\t// This can be an abbreviated form of the full URI with just the last component. For\n\t// example, if this value is set to \"emailAddress\" the format will resolve to:\n\t//\n\t//\t\turn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\n\t//\n\t// If no value is specified, this value defaults to:\n\t//\n\t//\t\turn:oasis:names:tc:SAML:2.0:nameid-format:persistent\n\t//\n\tNameIDPolicyFormat string `json:\"nameIDPolicyFormat\"`\n}\n\ntype certStore struct {\n\tcerts []*x509.Certificate\n}\n\nfunc (c certStore) Certificates() (roots []*x509.Certificate, err error) {\n\treturn c.certs, nil\n}\n\n// Open validates the config and returns a connector. It does not actually\n// validate connectivity with the provider.\nfunc (c *Config) Open(id string, logger *slog.Logger) (connector.Connector, error) {\n\tlogger = logger.With(slog.Group(\"connector\", \"type\", \"saml\", \"id\", id))\n\treturn c.openConnector(logger)\n}\n\nfunc (c *Config) openConnector(logger *slog.Logger) (*provider, error) {\n\trequiredFields := []struct {\n\t\tname, val string\n\t}{\n\t\t{\"ssoURL\", c.SSOURL},\n\t\t{\"usernameAttr\", c.UsernameAttr},\n\t\t{\"emailAttr\", c.EmailAttr},\n\t\t{\"redirectURI\", c.RedirectURI},\n\t}\n\tvar missing []string\n\tfor _, f := range requiredFields {\n\t\tif f.val == \"\" {\n\t\t\tmissing = append(missing, f.name)\n\t\t}\n\t}\n\tswitch len(missing) {\n\tcase 0:\n\tcase 1:\n\t\treturn nil, fmt.Errorf(\"missing required field %q\", missing[0])\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"missing required fields %q\", missing)\n\t}\n\n\tp := &provider{\n\t\tentityIssuer:  c.EntityIssuer,\n\t\tssoIssuer:     c.SSOIssuer,\n\t\tssoURL:        c.SSOURL,\n\t\tnow:           time.Now,\n\t\tusernameAttr:  c.UsernameAttr,\n\t\temailAttr:     c.EmailAttr,\n\t\tgroupsAttr:    c.GroupsAttr,\n\t\tgroupsDelim:   c.GroupsDelim,\n\t\tallowedGroups: c.AllowedGroups,\n\t\tfilterGroups:  c.FilterGroups,\n\t\tredirectURI:   c.RedirectURI,\n\t\tlogger:        logger,\n\n\t\tnameIDPolicyFormat: c.NameIDPolicyFormat,\n\t}\n\n\tif p.nameIDPolicyFormat == \"\" {\n\t\tp.nameIDPolicyFormat = nameIDFormatPersistent\n\t} else {\n\t\tlookupOnce.Do(func() {\n\t\t\tsuffix := func(s, sep string) string {\n\t\t\t\tif i := strings.LastIndex(s, sep); i > 0 {\n\t\t\t\t\treturn s[i+1:]\n\t\t\t\t}\n\t\t\t\treturn s\n\t\t\t}\n\t\t\tfor _, format := range nameIDFormats {\n\t\t\t\tnameIDFormatLookup[suffix(format, \":\")] = format\n\t\t\t\tnameIDFormatLookup[format] = format\n\t\t\t}\n\t\t})\n\n\t\tif format, ok := nameIDFormatLookup[p.nameIDPolicyFormat]; ok {\n\t\t\tp.nameIDPolicyFormat = format\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"invalid nameIDPolicyFormat: %q\", p.nameIDPolicyFormat)\n\t\t}\n\t}\n\n\tif !c.InsecureSkipSignatureValidation {\n\t\tif (c.CA == \"\") == (c.CAData == nil) {\n\t\t\treturn nil, errors.New(\"must provide either 'ca' or 'caData'\")\n\t\t}\n\n\t\tvar caData []byte\n\t\tif c.CA != \"\" {\n\t\t\tdata, err := os.ReadFile(c.CA)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"read ca file: %v\", err)\n\t\t\t}\n\t\t\tcaData = data\n\t\t} else {\n\t\t\tcaData = c.CAData\n\t\t}\n\n\t\tvar (\n\t\t\tcerts []*x509.Certificate\n\t\t\tblock *pem.Block\n\t\t)\n\t\tfor {\n\t\t\tblock, caData = pem.Decode(caData)\n\t\t\tif block == nil {\n\t\t\t\tcaData = bytes.TrimSpace(caData)\n\t\t\t\tif len(caData) > 0 { // if there's some left, we've been given bad caData\n\t\t\t\t\treturn nil, fmt.Errorf(\"parse cert: trailing data: %q\", string(caData))\n\t\t\t\t}\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcert, err := x509.ParseCertificate(block.Bytes)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"parse cert: %v\", err)\n\t\t\t}\n\t\t\tcerts = append(certs, cert)\n\t\t}\n\t\tif len(certs) == 0 {\n\t\t\treturn nil, errors.New(\"no certificates found in ca data\")\n\t\t}\n\t\tp.validator = dsig.NewDefaultValidationContext(certStore{certs})\n\t}\n\treturn p, nil\n}\n\nvar (\n\t_ connector.SAMLConnector    = (*provider)(nil)\n\t_ connector.RefreshConnector = (*provider)(nil)\n)\n\ntype provider struct {\n\tentityIssuer string\n\tssoIssuer    string\n\tssoURL       string\n\n\tnow func() time.Time\n\n\t// If nil, don't do signature validation.\n\tvalidator *dsig.ValidationContext\n\n\t// Attribute mappings\n\tusernameAttr  string\n\temailAttr     string\n\tgroupsAttr    string\n\tgroupsDelim   string\n\tallowedGroups []string\n\tfilterGroups  bool\n\n\tredirectURI string\n\n\tnameIDPolicyFormat string\n\n\tlogger *slog.Logger\n}\n\n// cachedIdentity stores the identity from SAML assertion for refresh token support.\n// Since SAML has no native refresh mechanism, we cache the identity obtained during\n// the initial authentication and return it on subsequent refresh requests.\ntype cachedIdentity struct {\n\tUserID            string   `json:\"userId\"`\n\tUsername          string   `json:\"username\"`\n\tPreferredUsername string   `json:\"preferredUsername\"`\n\tEmail             string   `json:\"email\"`\n\tEmailVerified     bool     `json:\"emailVerified\"`\n\tGroups            []string `json:\"groups,omitempty\"`\n}\n\n// marshalCachedIdentity serializes the identity into ConnectorData for refresh token support.\nfunc marshalCachedIdentity(ident connector.Identity) (connector.Identity, error) {\n\tci := cachedIdentity{\n\t\tUserID:            ident.UserID,\n\t\tUsername:          ident.Username,\n\t\tPreferredUsername: ident.PreferredUsername,\n\t\tEmail:             ident.Email,\n\t\tEmailVerified:     ident.EmailVerified,\n\t\tGroups:            ident.Groups,\n\t}\n\tconnectorData, err := json.Marshal(ci)\n\tif err != nil {\n\t\treturn ident, fmt.Errorf(\"saml: failed to marshal cached identity: %v\", err)\n\t}\n\tident.ConnectorData = connectorData\n\treturn ident, nil\n}\n\nfunc (p *provider) POSTData(s connector.Scopes, id string) (action, value string, err error) {\n\tr := &authnRequest{\n\t\tProtocolBinding: bindingPOST,\n\t\tID:              id,\n\t\tIssueInstant:    xmlTime(p.now()),\n\t\tDestination:     p.ssoURL,\n\t\tNameIDPolicy: &nameIDPolicy{\n\t\t\tAllowCreate: true,\n\t\t\tFormat:      p.nameIDPolicyFormat,\n\t\t},\n\t\tAssertionConsumerServiceURL: p.redirectURI,\n\t}\n\tif p.entityIssuer != \"\" {\n\t\t// Issuer for the request is optional. For example, okta always ignores\n\t\t// this value.\n\t\tr.Issuer = &issuer{Issuer: p.entityIssuer}\n\t}\n\n\tdata, err := xml.MarshalIndent(r, \"\", \"  \")\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"marshal authn request: %v\", err)\n\t}\n\n\t// See: https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf\n\t// \"3.5.4 Message Encoding\"\n\treturn p.ssoURL, base64.StdEncoding.EncodeToString(data), nil\n}\n\n// HandlePOST interprets a request from a SAML provider attempting to verify a\n// user's identity.\n//\n// The steps taken are:\n//\n// * Validate XML document does not contain malicious inputs.\n// * Verify signature on XML document (or verify sig on assertion elements).\n// * Verify various parts of the Assertion element. Conditions, audience, etc.\n// * Map the Assertion's attribute elements to user info.\nfunc (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo string) (ident connector.Identity, err error) {\n\trawResp, err := base64.StdEncoding.DecodeString(samlResponse)\n\tif err != nil {\n\t\treturn ident, fmt.Errorf(\"decode response: %v\", err)\n\t}\n\n\tbyteReader := bytes.NewReader(rawResp)\n\tif xrvErr := xrv.Validate(byteReader); xrvErr != nil {\n\t\treturn ident, errors.Wrap(xrvErr, \"validating XML response\")\n\t}\n\n\t// Root element is allowed to not be signed if the Assertion element is.\n\trootElementSigned := true\n\tif p.validator != nil {\n\t\trawResp, rootElementSigned, err = verifyResponseSig(p.validator, rawResp)\n\t\tif err != nil {\n\t\t\treturn ident, fmt.Errorf(\"verify signature: %v\", err)\n\t\t}\n\t}\n\n\tvar resp response\n\tif err := xml.Unmarshal(rawResp, &resp); err != nil {\n\t\treturn ident, fmt.Errorf(\"unmarshal response: %v\", err)\n\t}\n\n\t// If the root element isn't signed, there's no reason to inspect these\n\t// elements. They're not verified.\n\tif rootElementSigned {\n\t\tif p.ssoIssuer != \"\" && resp.Issuer != nil && resp.Issuer.Issuer != p.ssoIssuer {\n\t\t\treturn ident, fmt.Errorf(\"expected Issuer value %s, got %s\", p.ssoIssuer, resp.Issuer.Issuer)\n\t\t}\n\n\t\t// Verify InResponseTo value matches the expected ID associated with\n\t\t// the RelayState.\n\t\tif resp.InResponseTo != inResponseTo {\n\t\t\treturn ident, fmt.Errorf(\"expected InResponseTo value %s, got %s\", inResponseTo, resp.InResponseTo)\n\t\t}\n\n\t\t// Destination is optional.\n\t\tif resp.Destination != \"\" && resp.Destination != p.redirectURI {\n\t\t\treturn ident, fmt.Errorf(\"expected destination %q got %q\", p.redirectURI, resp.Destination)\n\t\t}\n\n\t\t// Status is a required element.\n\t\tif resp.Status == nil {\n\t\t\treturn ident, fmt.Errorf(\"response did not contain a Status element\")\n\t\t}\n\n\t\tif err = p.validateStatus(resp.Status); err != nil {\n\t\t\treturn ident, err\n\t\t}\n\t}\n\n\tassertion := resp.Assertion\n\tif assertion == nil {\n\t\treturn ident, fmt.Errorf(\"response did not contain an assertion\")\n\t}\n\n\t// Subject is usually optional, but we need it for the user ID, so complain\n\t// if it's not present.\n\tsubject := assertion.Subject\n\tif subject == nil {\n\t\treturn ident, fmt.Errorf(\"response did not contain a subject\")\n\t}\n\n\t// Validate that the response is to the request we originally sent.\n\tif err = p.validateSubject(subject, inResponseTo); err != nil {\n\t\treturn ident, err\n\t}\n\n\t// Conditions element is optional, but must be validated if present.\n\tif assertion.Conditions != nil {\n\t\t// Validate that dex is the intended audience of this response.\n\t\tif err = p.validateConditions(assertion.Conditions); err != nil {\n\t\t\treturn ident, err\n\t\t}\n\t}\n\n\tswitch {\n\tcase subject.NameID != nil:\n\t\tif ident.UserID = subject.NameID.Value; ident.UserID == \"\" {\n\t\t\treturn ident, fmt.Errorf(\"element NameID does not contain a value\")\n\t\t}\n\tdefault:\n\t\treturn ident, fmt.Errorf(\"subject does not contain an NameID element\")\n\t}\n\n\t// After verifying the assertion, map data in the attribute statements to\n\t// various user info.\n\tattributes := assertion.AttributeStatement\n\tif attributes == nil {\n\t\treturn ident, fmt.Errorf(\"response did not contain a AttributeStatement\")\n\t}\n\n\t// Log the actual attributes we got back from the server. This helps debug\n\t// configuration errors on the server side, where the SAML server doesn't\n\t// send us the correct attributes.\n\tp.logger.Info(\"parsed and verified saml response attributes\", \"attributes\", attributes)\n\n\t// Grab the email.\n\tif ident.Email, _ = attributes.get(p.emailAttr); ident.Email == \"\" {\n\t\treturn ident, fmt.Errorf(\"no attribute with name %q: %s\", p.emailAttr, attributes.names())\n\t}\n\t// TODO(ericchiang): Does SAML have an email_verified equivalent?\n\tident.EmailVerified = true\n\n\t// Grab the username.\n\tif ident.Username, _ = attributes.get(p.usernameAttr); ident.Username == \"\" {\n\t\treturn ident, fmt.Errorf(\"no attribute with name %q: %s\", p.usernameAttr, attributes.names())\n\t}\n\n\tif len(p.allowedGroups) == 0 && (!s.Groups || p.groupsAttr == \"\") {\n\t\t// Groups not requested or not configured. We're done.\n\t\treturn marshalCachedIdentity(ident)\n\t}\n\n\tif len(p.allowedGroups) > 0 && (!s.Groups || p.groupsAttr == \"\") {\n\t\t// allowedGroups set but no groups or groupsAttr. Disallowing.\n\t\treturn ident, fmt.Errorf(\"user not a member of allowed groups\")\n\t}\n\n\t// Grab the groups.\n\tif p.groupsDelim != \"\" {\n\t\tgroupsStr, ok := attributes.get(p.groupsAttr)\n\t\tif !ok {\n\t\t\treturn ident, fmt.Errorf(\"no attribute with name %q: %s\", p.groupsAttr, attributes.names())\n\t\t}\n\t\t// TODO(ericchiang): Do we need to further trim whitespace?\n\t\tident.Groups = strings.Split(groupsStr, p.groupsDelim)\n\t} else {\n\t\tgroups, ok := attributes.all(p.groupsAttr)\n\t\tif !ok {\n\t\t\treturn ident, fmt.Errorf(\"no attribute with name %q: %s\", p.groupsAttr, attributes.names())\n\t\t}\n\t\tident.Groups = groups\n\t}\n\n\tif len(p.allowedGroups) == 0 {\n\t\t// No allowed groups set, just return the ident\n\t\treturn marshalCachedIdentity(ident)\n\t}\n\n\t// Look for membership in one of the allowed groups\n\tgroupMatches := groups.Filter(ident.Groups, p.allowedGroups)\n\n\tif len(groupMatches) == 0 {\n\t\t// No group membership matches found, disallowing\n\t\treturn ident, fmt.Errorf(\"user not a member of allowed groups\")\n\t}\n\n\tif p.filterGroups {\n\t\tident.Groups = groupMatches\n\t}\n\n\t// Otherwise, we're good\n\treturn marshalCachedIdentity(ident)\n}\n\n// Refresh implements connector.RefreshConnector.\n// Since SAML has no native refresh mechanism, this method returns the cached\n// identity from the initial SAML assertion stored in ConnectorData.\nfunc (p *provider) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {\n\tif len(ident.ConnectorData) == 0 {\n\t\treturn ident, fmt.Errorf(\"saml: no connector data available for refresh\")\n\t}\n\n\tvar ci cachedIdentity\n\tif err := json.Unmarshal(ident.ConnectorData, &ci); err != nil {\n\t\treturn ident, fmt.Errorf(\"saml: failed to unmarshal cached identity: %v\", err)\n\t}\n\n\tident.UserID = ci.UserID\n\tident.Username = ci.Username\n\tident.PreferredUsername = ci.PreferredUsername\n\tident.Email = ci.Email\n\tident.EmailVerified = ci.EmailVerified\n\n\t// Only populate groups if the client requested the groups scope.\n\tif s.Groups {\n\t\tident.Groups = ci.Groups\n\t} else {\n\t\tident.Groups = nil\n\t}\n\n\treturn ident, nil\n}\n\n// validateStatus verifies that the response has a good status code or\n// formats a human readable error based on the bad status.\nfunc (p *provider) validateStatus(status *status) error {\n\t// StatusCode is mandatory in the Status type\n\tstatusCode := status.StatusCode\n\tif statusCode == nil {\n\t\treturn fmt.Errorf(\"response did not contain a StatusCode\")\n\t}\n\n\tif statusCode.Value != statusCodeSuccess {\n\t\tparts := strings.Split(statusCode.Value, \":\")\n\t\tlastPart := parts[len(parts)-1]\n\t\terrorMessage := fmt.Sprintf(\"status code of the Response was not Success, was %q\", lastPart)\n\t\tstatusMessage := status.StatusMessage\n\t\tif statusMessage != nil && statusMessage.Value != \"\" {\n\t\t\terrorMessage += \" -> \" + statusMessage.Value\n\t\t}\n\t\treturn errors.New(errorMessage)\n\t}\n\treturn nil\n}\n\n// validateSubject ensures the response is to the request we expect.\n//\n// This is described in the spec \"Profiles for the OASIS Security\n// Assertion Markup Language\" in section 3.3 Bearer.\n// see https://www.oasis-open.org/committees/download.php/35389/sstc-saml-profiles-errata-2.0-wd-06-diff.pdf\n//\n// Some of these fields are optional, but we're going to be strict here since\n// we have no other way of guaranteeing that this is actually the response to\n// the request we expect.\nfunc (p *provider) validateSubject(subject *subject, inResponseTo string) error {\n\t// Optional according to the spec, but again, we're going to be strict here.\n\tif len(subject.SubjectConfirmations) == 0 {\n\t\treturn fmt.Errorf(\"subject contained no SubjectConfirmations\")\n\t}\n\n\terrs := make([]error, 0, len(subject.SubjectConfirmations))\n\t// One of these must match our assumptions, not all.\n\tfor _, c := range subject.SubjectConfirmations {\n\t\terr := func() error {\n\t\t\tif c.Method != subjectConfirmationMethodBearer {\n\t\t\t\treturn fmt.Errorf(\"unexpected subject confirmation method: %v\", c.Method)\n\t\t\t}\n\n\t\t\tdata := c.SubjectConfirmationData\n\t\t\tif data == nil {\n\t\t\t\treturn fmt.Errorf(\"no SubjectConfirmationData field found in SubjectConfirmation\")\n\t\t\t}\n\t\t\tif data.InResponseTo != inResponseTo {\n\t\t\t\treturn fmt.Errorf(\"expected SubjectConfirmationData InResponseTo value %q, got %q\", inResponseTo, data.InResponseTo)\n\t\t\t}\n\n\t\t\tnotBefore := time.Time(data.NotBefore)\n\t\t\tnotOnOrAfter := time.Time(data.NotOnOrAfter)\n\t\t\tnow := p.now()\n\t\t\tif !notBefore.IsZero() && before(now, notBefore) {\n\t\t\t\treturn fmt.Errorf(\"at %s got response that cannot be processed before %s\", now, notBefore)\n\t\t\t}\n\t\t\tif !notOnOrAfter.IsZero() && after(now, notOnOrAfter) {\n\t\t\t\treturn fmt.Errorf(\"at %s got response that cannot be processed because it expired at %s\", now, notOnOrAfter)\n\t\t\t}\n\t\t\tif r := data.Recipient; r != \"\" && r != p.redirectURI {\n\t\t\t\treturn fmt.Errorf(\"expected Recipient %q got %q\", p.redirectURI, r)\n\t\t\t}\n\t\t\treturn nil\n\t\t}()\n\t\tif err == nil {\n\t\t\t// Subject is valid.\n\t\t\treturn nil\n\t\t}\n\t\terrs = append(errs, err)\n\t}\n\n\tif len(errs) == 1 {\n\t\treturn fmt.Errorf(\"failed to validate subject confirmation: %v\", errs[0])\n\t}\n\treturn fmt.Errorf(\"failed to validate subject confirmation: %v\", errs)\n}\n\n// validateConditions ensures that dex is the intended audience\n// for the request, and not another service provider.\n//\n// See: https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf\n// \"2.3.3 Element <Assertion>\"\nfunc (p *provider) validateConditions(conditions *conditions) error {\n\t// Ensure the conditions haven't expired.\n\tnow := p.now()\n\tnotBefore := time.Time(conditions.NotBefore)\n\tif !notBefore.IsZero() && before(now, notBefore) {\n\t\treturn fmt.Errorf(\"at %s got response that cannot be processed before %s\", now, notBefore)\n\t}\n\n\tnotOnOrAfter := time.Time(conditions.NotOnOrAfter)\n\tif !notOnOrAfter.IsZero() && after(now, notOnOrAfter) {\n\t\treturn fmt.Errorf(\"at %s got response that cannot be processed because it expired at %s\", now, notOnOrAfter)\n\t}\n\n\t// Sometimes, dex's issuer string can be different than the redirect URI,\n\t// but if dex's issuer isn't explicitly provided assume the redirect URI.\n\texpAud := p.entityIssuer\n\tif expAud == \"\" {\n\t\texpAud = p.redirectURI\n\t}\n\n\t// AudienceRestriction elements indicate the intended audience(s) of an\n\t// assertion. If dex isn't in these audiences, reject the assertion.\n\t//\n\t// Note that if there are multiple AudienceRestriction elements, each must\n\t// individually contain dex in their audience list.\n\tfor _, r := range conditions.AudienceRestriction {\n\t\tvalues := make([]string, len(r.Audiences))\n\t\tissuerInAudiences := false\n\t\tfor i, aud := range r.Audiences {\n\t\t\tif aud.Value == expAud {\n\t\t\t\tissuerInAudiences = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tvalues[i] = aud.Value\n\t\t}\n\n\t\tif !issuerInAudiences {\n\t\t\treturn fmt.Errorf(\"required audience %s was not in Response audiences %s\", expAud, values)\n\t\t}\n\t}\n\treturn nil\n}\n\n// verifyResponseSig attempts to verify the signature of a SAML response or\n// the assertion.\n//\n// If the root element is properly signed, this method returns it.\n//\n// The SAML spec requires supporting responses where the root element is\n// unverified, but the sub <Assertion> elements are signed. In these cases,\n// this method returns rootVerified=false to indicate that the <Assertion>\n// elements should be trusted, but all other elements MUST be ignored.\n//\n// Note: we still don't support multiple <Assertion> tags. If there are\n// multiple present this code will only process the first.\nfunc verifyResponseSig(validator *dsig.ValidationContext, data []byte) (signed []byte, rootVerified bool, err error) {\n\tdoc := etree.NewDocument()\n\tif err = doc.ReadFromBytes(data); err != nil {\n\t\treturn nil, false, fmt.Errorf(\"parse document: %v\", err)\n\t}\n\n\tresponse := doc.Root()\n\tif response == nil {\n\t\treturn nil, false, fmt.Errorf(\"parse document: empty root\")\n\t}\n\ttransformedResponse, err := validator.Validate(response)\n\tif err == nil {\n\t\t// Root element is verified, return it.\n\t\tdoc.SetRoot(transformedResponse)\n\t\tsigned, err = doc.WriteToBytes()\n\t\treturn signed, true, err\n\t}\n\n\t// Ensures xmlns are copied down to the assertion element when they are defined in the root\n\t//\n\t// TODO: Only select from child elements of the root.\n\tassertion, err := etreeutils.NSSelectOne(response, \"urn:oasis:names:tc:SAML:2.0:assertion\", \"Assertion\")\n\tif err != nil || assertion == nil {\n\t\treturn nil, false, fmt.Errorf(\"response does not contain an Assertion element\")\n\t}\n\ttransformedAssertion, err := validator.Validate(assertion)\n\tif err != nil {\n\t\treturn nil, false, fmt.Errorf(\"response does not contain a valid signature element: %v\", err)\n\t}\n\n\t// Verified an assertion but not the response. Can't trust any child elements,\n\t// except the assertion. Remove them all.\n\tfor _, el := range response.ChildElements() {\n\t\tresponse.RemoveChild(el)\n\t}\n\n\t// We still return the full <Response> element, even though it's unverified\n\t// because the <Assertion> element is not a valid XML document on its own.\n\t// It still requires the root element to define things like namespaces.\n\tresponse.AddChild(transformedAssertion)\n\tsigned, err = doc.WriteToBytes()\n\treturn signed, false, err\n}\n\n// before determines if a given time is before the current time, with an\n// allowed clock drift.\nfunc before(now, notBefore time.Time) bool {\n\treturn now.Add(allowedClockDrift).Before(notBefore)\n}\n\n// after determines if a given time is after the current time, with an\n// allowed clock drift.\nfunc after(now, notOnOrAfter time.Time) bool {\n\treturn now.After(notOnOrAfter.Add(allowedClockDrift))\n}\n"
  },
  {
    "path": "connector/saml/saml_test.go",
    "content": "package saml\n\nimport (\n\t\"context\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"log/slog\"\n\t\"os\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/kylelemons/godebug/pretty\"\n\tdsig \"github.com/russellhaering/goxmldsig\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// responseTest maps a SAML 2.0 response object to a set of expected values.\n//\n// Tests are defined in the \"testdata\" directory and are self-signed using xmlsec1.\n//\n// To add a new test, define a new, unsigned SAML 2.0 response that exercises some\n// case, then sign it using the \"testdata/gen.sh\" script.\n//\n//\tcp testdata/good-resp.tmpl testdata/( testname ).tmpl\n//\tvim ( testname ).tmpl # Modify your template for your test case.\n//\tvim testdata/gen.sh   # Add a xmlsec1 command to the generation script.\n//\t./testdata/gen.sh     # Sign your template.\n//\n// To install xmlsec1 on Fedora run:\n//\n//\tsudo dnf install xmlsec1 xmlsec1-openssl\n//\n// On mac:\n//\n//\tbrew install Libxmlsec1\ntype responseTest struct {\n\t// CA file and XML file of the response.\n\tcaFile   string\n\trespFile string\n\n\t// Values that should be used to validate the signature.\n\tnow          string\n\tinResponseTo string\n\tredirectURI  string\n\tentityIssuer string\n\n\t// Attribute customization.\n\tusernameAttr  string\n\temailAttr     string\n\tgroupsAttr    string\n\tallowedGroups []string\n\tfilterGroups  bool\n\n\t// Expected outcome of the test.\n\twantErr   bool\n\twantIdent connector.Identity\n}\n\nfunc TestGoodResponse(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/good-resp.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestGroups(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/good-resp.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tgroupsAttr:   \"groups\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"Admins\", \"Everyone\"},\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestGroupsWhitelist(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:        \"testdata/ca.crt\",\n\t\trespFile:      \"testdata/good-resp.xml\",\n\t\tnow:           \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr:  \"Name\",\n\t\temailAttr:     \"email\",\n\t\tgroupsAttr:    \"groups\",\n\t\tallowedGroups: []string{\"Admins\"},\n\t\tinResponseTo:  \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:   \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"Admins\", \"Everyone\"},\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestGroupsWhitelistWithFiltering(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:        \"testdata/ca.crt\",\n\t\trespFile:      \"testdata/good-resp.xml\",\n\t\tnow:           \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr:  \"Name\",\n\t\temailAttr:     \"email\",\n\t\tgroupsAttr:    \"groups\",\n\t\tallowedGroups: []string{\"Admins\"},\n\t\tfilterGroups:  true,\n\t\tinResponseTo:  \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:   \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"Admins\"}, // \"Everyone\" is filtered\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestGroupsWhitelistEmpty(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:        \"testdata/ca.crt\",\n\t\trespFile:      \"testdata/good-resp.xml\",\n\t\tnow:           \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr:  \"Name\",\n\t\temailAttr:     \"email\",\n\t\tgroupsAttr:    \"groups\",\n\t\tallowedGroups: []string{},\n\t\tinResponseTo:  \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:   \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"Admins\", \"Everyone\"},\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestGroupsWhitelistDisallowed(t *testing.T) {\n\ttest := responseTest{\n\t\twantErr:       true,\n\t\tcaFile:        \"testdata/ca.crt\",\n\t\trespFile:      \"testdata/good-resp.xml\",\n\t\tnow:           \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr:  \"Name\",\n\t\temailAttr:     \"email\",\n\t\tgroupsAttr:    \"groups\",\n\t\tallowedGroups: []string{\"Nope\"},\n\t\tinResponseTo:  \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:   \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"Admins\", \"Everyone\"},\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestGroupsWhitelistDisallowedNoGroupsOnIdent(t *testing.T) {\n\ttest := responseTest{\n\t\twantErr:       true,\n\t\tcaFile:        \"testdata/ca.crt\",\n\t\trespFile:      \"testdata/good-resp.xml\",\n\t\tnow:           \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr:  \"Name\",\n\t\temailAttr:     \"email\",\n\t\tgroupsAttr:    \"groups\",\n\t\tallowedGroups: []string{\"Nope\"},\n\t\tinResponseTo:  \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:   \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{},\n\t\t},\n\t}\n\ttest.run(t)\n}\n\n// TestOkta tests against an actual response from Okta.\nfunc TestOkta(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/okta-ca.pem\",\n\t\trespFile:     \"testdata/okta-resp.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestBadStatus(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/bad-status.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\nfunc TestInvalidCA(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/bad-ca.crt\", // Not the CA that signed this response.\n\t\trespFile:     \"testdata/good-resp.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\nfunc TestUnsignedResponse(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/good-resp.tmpl\", // Use the unsigned template, not the signed document.\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\nfunc TestExpiredAssertion(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/assertion-signed.xml\",\n\t\tnow:          \"2020-04-04T04:34:59.330Z\", // Assertion has expired.\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\n// TestAssertionSignedNotResponse ensures the connector validates SAML 2.0\n// responses where the assertion is signed but the root element, the\n// response, isn't.\nfunc TestAssertionSignedNotResponse(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/assertion-signed.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestInvalidSubjectInResponseTo(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/assertion-signed.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"invalid-id\", // Bad InResponseTo value.\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\nfunc TestInvalidSubjectRecipient(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/assertion-signed.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://bad.com/dex/callback\", // Doesn't match Recipient value.\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\nfunc TestInvalidAssertionAudience(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/assertion-signed.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\t// EntityIssuer overrides RedirectURI when determining the expected\n\t\t// audience. In this case, ensure the audience is invalid.\n\t\tentityIssuer: \"http://localhost:5556/dex/callback\",\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\n// TestTwoAssertionFirstSigned tries to catch an edge case where an attacker\n// provides a second assertion that's not signed.\nfunc TestTwoAssertionFirstSigned(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/two-assertions-first-signed.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantIdent: connector.Identity{\n\t\t\tUserID:        \"eric.chiang+okta@coreos.com\",\n\t\t\tUsername:      \"Eric\",\n\t\t\tEmail:         \"eric.chiang+okta@coreos.com\",\n\t\t\tEmailVerified: true,\n\t\t},\n\t}\n\ttest.run(t)\n}\n\nfunc TestTamperedResponseNameID(t *testing.T) {\n\ttest := responseTest{\n\t\tcaFile:       \"testdata/ca.crt\",\n\t\trespFile:     \"testdata/tampered-resp.xml\",\n\t\tnow:          \"2017-04-04T04:34:59.330Z\",\n\t\tusernameAttr: \"Name\",\n\t\temailAttr:    \"email\",\n\t\tinResponseTo: \"6zmm5mguyebwvajyf2sdwwcw6m\",\n\t\tredirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\twantErr:      true,\n\t}\n\ttest.run(t)\n}\n\nfunc loadCert(ca string) (*x509.Certificate, error) {\n\tdata, err := os.ReadFile(ca)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tblock, _ := pem.Decode(data)\n\tif block == nil {\n\t\treturn nil, errors.New(\"ca file didn't contain any PEM data\")\n\t}\n\treturn x509.ParseCertificate(block.Bytes)\n}\n\nfunc (r responseTest) run(t *testing.T) {\n\tc := Config{\n\t\tCA:            r.caFile,\n\t\tUsernameAttr:  r.usernameAttr,\n\t\tEmailAttr:     r.emailAttr,\n\t\tGroupsAttr:    r.groupsAttr,\n\t\tRedirectURI:   r.redirectURI,\n\t\tEntityIssuer:  r.entityIssuer,\n\t\tAllowedGroups: r.allowedGroups,\n\t\tFilterGroups:  r.filterGroups,\n\t\t// Never logging in, don't need this.\n\t\tSSOURL: \"http://foo.bar/\",\n\t}\n\tnow, err := time.Parse(timeFormat, r.now)\n\tif err != nil {\n\t\tt.Fatalf(\"parse test time: %v\", err)\n\t}\n\n\tconn, err := c.openConnector(slog.New(slog.DiscardHandler))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tconn.now = func() time.Time { return now }\n\tresp, err := os.ReadFile(r.respFile)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tsamlResp := base64.StdEncoding.EncodeToString(resp)\n\n\tscopes := connector.Scopes{\n\t\tOfflineAccess: false,\n\t\tGroups:        true,\n\t}\n\tident, err := conn.HandlePOST(scopes, samlResp, r.inResponseTo)\n\tif err != nil {\n\t\tif !r.wantErr {\n\t\t\tt.Fatalf(\"handle response: %v\", err)\n\t\t}\n\t\treturn\n\t}\n\n\tif r.wantErr {\n\t\tt.Fatalf(\"wanted error\")\n\t}\n\tsort.Strings(ident.Groups)\n\tsort.Strings(r.wantIdent.Groups)\n\n\t// Verify ConnectorData contains valid cached identity, then clear it\n\t// for the main identity comparison (ConnectorData is an implementation\n\t// detail of refresh token support).\n\tif len(ident.ConnectorData) > 0 {\n\t\tvar ci cachedIdentity\n\t\tif err := json.Unmarshal(ident.ConnectorData, &ci); err != nil {\n\t\t\tt.Fatalf(\"failed to unmarshal ConnectorData: %v\", err)\n\t\t}\n\t\tif ci.UserID != ident.UserID {\n\t\t\tt.Errorf(\"cached identity UserID mismatch: got %q, want %q\", ci.UserID, ident.UserID)\n\t\t}\n\t\tif ci.Email != ident.Email {\n\t\t\tt.Errorf(\"cached identity Email mismatch: got %q, want %q\", ci.Email, ident.Email)\n\t\t}\n\t}\n\tident.ConnectorData = nil\n\n\tif diff := pretty.Compare(ident, r.wantIdent); diff != \"\" {\n\t\tt.Error(diff)\n\t}\n}\n\nfunc TestConfigCAData(t *testing.T) {\n\tlogger := slog.New(slog.DiscardHandler)\n\tvalidPEM, err := os.ReadFile(\"testdata/ca.crt\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvalid2ndPEM, err := os.ReadFile(\"testdata/okta-ca.pem\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// copy helper, avoid messing with the byte slice among different cases\n\tc := func(bs []byte) []byte {\n\t\treturn append([]byte(nil), bs...)\n\t}\n\n\ttests := []struct {\n\t\tname    string\n\t\tcaData  []byte\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:   \"one valid PEM entry\",\n\t\t\tcaData: c(validPEM),\n\t\t},\n\t\t{\n\t\t\tname:   \"one valid PEM entry with trailing newline\",\n\t\t\tcaData: append(c(validPEM), []byte(\"\\n\")...),\n\t\t},\n\t\t{\n\t\t\tname:   \"one valid PEM entry with trailing spaces\",\n\t\t\tcaData: append(c(validPEM), []byte(\"   \")...),\n\t\t},\n\t\t{\n\t\t\tname:   \"one valid PEM entry with two trailing newlines\",\n\t\t\tcaData: append(c(validPEM), []byte(\"\\n\\n\")...),\n\t\t},\n\t\t{\n\t\t\tname:   \"two valid PEM entries\",\n\t\t\tcaData: append(c(validPEM), c(valid2ndPEM)...),\n\t\t},\n\t\t{\n\t\t\tname:   \"two valid PEM entries with newline in between\",\n\t\t\tcaData: append(append(c(validPEM), []byte(\"\\n\")...), c(valid2ndPEM)...),\n\t\t},\n\t\t{\n\t\t\tname:   \"two valid PEM entries with trailing newline\",\n\t\t\tcaData: append(c(valid2ndPEM), append(c(validPEM), []byte(\"\\n\")...)...),\n\t\t},\n\t\t{\n\t\t\tname:    \"empty\",\n\t\t\tcaData:  []byte{},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"one valid PEM entry with trailing data\",\n\t\t\tcaData:  append(c(validPEM), []byte(\"yaddayadda\")...),\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"one valid PEM entry with bad data before\",\n\t\t\tcaData:  append([]byte(\"yaddayadda\"), c(validPEM)...),\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tc := Config{\n\t\t\t\tCAData:       tc.caData,\n\t\t\t\tUsernameAttr: \"user\",\n\t\t\t\tEmailAttr:    \"email\",\n\t\t\t\tRedirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\t\t\tSSOURL:       \"http://foo.bar/\",\n\t\t\t}\n\t\t\t_, err := (&c).Open(\"samltest\", logger)\n\t\t\tif tc.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Error(\"expected error, got nil\")\n\t\t\t\t}\n\t\t\t} else if err != nil {\n\t\t\t\tt.Errorf(\"expected no error, got %v\", err)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// Deprecated: Use testing framework established above.\nfunc runVerify(t *testing.T, ca string, resp string, shouldSucceed bool) {\n\tcert, err := loadCert(ca)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\ts := certStore{[]*x509.Certificate{cert}}\n\n\tvalidator := dsig.NewDefaultValidationContext(s)\n\n\tdata, err := os.ReadFile(resp)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tif _, _, err := verifyResponseSig(validator, data); err != nil {\n\t\tif shouldSucceed {\n\t\t\tt.Fatal(err)\n\t\t}\n\t} else {\n\t\tif !shouldSucceed {\n\t\t\tt.Fatalf(\"expected an invalid signature but verification has been successful\")\n\t\t}\n\t}\n}\n\nfunc TestVerify(t *testing.T) {\n\trunVerify(t, \"testdata/okta-ca.pem\", \"testdata/okta-resp.xml\", true)\n}\n\nfunc TestVerifyUnsignedMessageAndSignedAssertionWithRootXmlNs(t *testing.T) {\n\trunVerify(t, \"testdata/oam-ca.pem\", \"testdata/oam-resp.xml\", true)\n}\n\nfunc TestVerifySignedMessageAndUnsignedAssertion(t *testing.T) {\n\trunVerify(t, \"testdata/idp-cert.pem\", \"testdata/idp-resp-signed-message.xml\", true)\n}\n\nfunc TestVerifyUnsignedMessageAndSignedAssertion(t *testing.T) {\n\trunVerify(t, \"testdata/idp-cert.pem\", \"testdata/idp-resp-signed-assertion.xml\", true)\n}\n\nfunc TestVerifySignedMessageAndSignedAssertion(t *testing.T) {\n\trunVerify(t, \"testdata/idp-cert.pem\", \"testdata/idp-resp-signed-message-and-assertion.xml\", true)\n}\n\nfunc TestVerifyUnsignedMessageAndUnsignedAssertion(t *testing.T) {\n\trunVerify(t, \"testdata/idp-cert.pem\", \"testdata/idp-resp.xml\", false)\n}\n\nfunc TestSAMLRefresh(t *testing.T) {\n\t// Create a provider using the same pattern as existing tests.\n\tc := Config{\n\t\tCA:           \"testdata/ca.crt\",\n\t\tUsernameAttr: \"Name\",\n\t\tEmailAttr:    \"email\",\n\t\tGroupsAttr:   \"groups\",\n\t\tRedirectURI:  \"http://127.0.0.1:5556/dex/callback\",\n\t\tSSOURL:       \"http://foo.bar/\",\n\t}\n\n\tconn, err := c.openConnector(slog.New(slog.DiscardHandler))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Run(\"SuccessfulRefresh\", func(t *testing.T) {\n\t\tci := cachedIdentity{\n\t\t\tUserID:            \"test-user-id\",\n\t\t\tUsername:          \"testuser\",\n\t\t\tPreferredUsername: \"testuser\",\n\t\t\tEmail:             \"test@example.com\",\n\t\t\tEmailVerified:     true,\n\t\t\tGroups:            []string{\"group1\", \"group2\"},\n\t\t}\n\t\tconnectorData, err := json.Marshal(ci)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tident := connector.Identity{\n\t\t\tUserID:        \"old-id\",\n\t\t\tUsername:      \"old-name\",\n\t\t\tConnectorData: connectorData,\n\t\t}\n\n\t\trefreshed, err := conn.Refresh(context.Background(), connector.Scopes{Groups: true}, ident)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Refresh failed: %v\", err)\n\t\t}\n\n\t\tif refreshed.UserID != \"test-user-id\" {\n\t\t\tt.Errorf(\"expected UserID %q, got %q\", \"test-user-id\", refreshed.UserID)\n\t\t}\n\t\tif refreshed.Username != \"testuser\" {\n\t\t\tt.Errorf(\"expected Username %q, got %q\", \"testuser\", refreshed.Username)\n\t\t}\n\t\tif refreshed.PreferredUsername != \"testuser\" {\n\t\t\tt.Errorf(\"expected PreferredUsername %q, got %q\", \"testuser\", refreshed.PreferredUsername)\n\t\t}\n\t\tif refreshed.Email != \"test@example.com\" {\n\t\t\tt.Errorf(\"expected Email %q, got %q\", \"test@example.com\", refreshed.Email)\n\t\t}\n\t\tif !refreshed.EmailVerified {\n\t\t\tt.Error(\"expected EmailVerified to be true\")\n\t\t}\n\t\tif len(refreshed.Groups) != 2 || refreshed.Groups[0] != \"group1\" || refreshed.Groups[1] != \"group2\" {\n\t\t\tt.Errorf(\"expected groups [group1, group2], got %v\", refreshed.Groups)\n\t\t}\n\t\t// ConnectorData should be preserved through refresh\n\t\tif len(refreshed.ConnectorData) == 0 {\n\t\t\tt.Error(\"expected ConnectorData to be preserved\")\n\t\t}\n\t})\n\n\tt.Run(\"RefreshPreservesConnectorData\", func(t *testing.T) {\n\t\tci := cachedIdentity{\n\t\t\tUserID:        \"user-123\",\n\t\t\tUsername:      \"alice\",\n\t\t\tEmail:         \"alice@example.com\",\n\t\t\tEmailVerified: true,\n\t\t}\n\t\tconnectorData, err := json.Marshal(ci)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tident := connector.Identity{\n\t\t\tUserID:        \"old-id\",\n\t\t\tConnectorData: connectorData,\n\t\t}\n\n\t\trefreshed, err := conn.Refresh(context.Background(), connector.Scopes{}, ident)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Refresh failed: %v\", err)\n\t\t}\n\n\t\t// Verify the refreshed identity can be refreshed again (round-trip)\n\t\tvar roundTrip cachedIdentity\n\t\tif err := json.Unmarshal(refreshed.ConnectorData, &roundTrip); err != nil {\n\t\t\tt.Fatalf(\"failed to unmarshal ConnectorData after refresh: %v\", err)\n\t\t}\n\t\tif roundTrip.UserID != \"user-123\" {\n\t\t\tt.Errorf(\"round-trip UserID mismatch: got %q, want %q\", roundTrip.UserID, \"user-123\")\n\t\t}\n\t})\n\n\tt.Run(\"EmptyConnectorData\", func(t *testing.T) {\n\t\tident := connector.Identity{\n\t\t\tUserID:        \"test-id\",\n\t\t\tConnectorData: nil,\n\t\t}\n\t\t_, err := conn.Refresh(context.Background(), connector.Scopes{}, ident)\n\t\tif err == nil {\n\t\t\tt.Error(\"expected error for empty ConnectorData\")\n\t\t}\n\t})\n\n\tt.Run(\"InvalidJSON\", func(t *testing.T) {\n\t\tident := connector.Identity{\n\t\t\tUserID:        \"test-id\",\n\t\t\tConnectorData: []byte(\"not-json\"),\n\t\t}\n\t\t_, err := conn.Refresh(context.Background(), connector.Scopes{}, ident)\n\t\tif err == nil {\n\t\t\tt.Error(\"expected error for invalid JSON\")\n\t\t}\n\t})\n\n\tt.Run(\"HandlePOSTThenRefresh\", func(t *testing.T) {\n\t\t// Full integration: HandlePOST → get ConnectorData → Refresh → verify identity\n\t\tnow, err := time.Parse(timeFormat, \"2017-04-04T04:34:59.330Z\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tconn.now = func() time.Time { return now }\n\n\t\tresp, err := os.ReadFile(\"testdata/good-resp.xml\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tsamlResp := base64.StdEncoding.EncodeToString(resp)\n\n\t\tscopes := connector.Scopes{\n\t\t\tOfflineAccess: true,\n\t\t\tGroups:        true,\n\t\t}\n\t\tident, err := conn.HandlePOST(scopes, samlResp, \"6zmm5mguyebwvajyf2sdwwcw6m\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"HandlePOST failed: %v\", err)\n\t\t}\n\n\t\tif len(ident.ConnectorData) == 0 {\n\t\t\tt.Fatal(\"expected ConnectorData to be set after HandlePOST\")\n\t\t}\n\n\t\t// Now refresh using the ConnectorData from HandlePOST\n\t\trefreshed, err := conn.Refresh(context.Background(), scopes, ident)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Refresh failed: %v\", err)\n\t\t}\n\n\t\tif refreshed.UserID != ident.UserID {\n\t\t\tt.Errorf(\"UserID mismatch: got %q, want %q\", refreshed.UserID, ident.UserID)\n\t\t}\n\t\tif refreshed.Username != ident.Username {\n\t\t\tt.Errorf(\"Username mismatch: got %q, want %q\", refreshed.Username, ident.Username)\n\t\t}\n\t\tif refreshed.Email != ident.Email {\n\t\t\tt.Errorf(\"Email mismatch: got %q, want %q\", refreshed.Email, ident.Email)\n\t\t}\n\t\tif refreshed.EmailVerified != ident.EmailVerified {\n\t\t\tt.Errorf(\"EmailVerified mismatch: got %v, want %v\", refreshed.EmailVerified, ident.EmailVerified)\n\t\t}\n\t\tsort.Strings(refreshed.Groups)\n\t\tsort.Strings(ident.Groups)\n\t\tif len(refreshed.Groups) != len(ident.Groups) {\n\t\t\tt.Errorf(\"Groups length mismatch: got %d, want %d\", len(refreshed.Groups), len(ident.Groups))\n\t\t}\n\t\tfor i := range ident.Groups {\n\t\t\tif i < len(refreshed.Groups) && refreshed.Groups[i] != ident.Groups[i] {\n\t\t\t\tt.Errorf(\"Groups[%d] mismatch: got %q, want %q\", i, refreshed.Groups[i], ident.Groups[i])\n\t\t\t}\n\t\t}\n\t})\n\n\tt.Run(\"HandlePOSTThenDoubleRefresh\", func(t *testing.T) {\n\t\t// Verify that refresh tokens can be chained: HandlePOST → Refresh → Refresh\n\t\tnow, err := time.Parse(timeFormat, \"2017-04-04T04:34:59.330Z\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tconn.now = func() time.Time { return now }\n\n\t\tresp, err := os.ReadFile(\"testdata/good-resp.xml\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tsamlResp := base64.StdEncoding.EncodeToString(resp)\n\n\t\tscopes := connector.Scopes{OfflineAccess: true, Groups: true}\n\t\tident, err := conn.HandlePOST(scopes, samlResp, \"6zmm5mguyebwvajyf2sdwwcw6m\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"HandlePOST failed: %v\", err)\n\t\t}\n\n\t\t// First refresh\n\t\trefreshed1, err := conn.Refresh(context.Background(), scopes, ident)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"first Refresh failed: %v\", err)\n\t\t}\n\t\tif len(refreshed1.ConnectorData) == 0 {\n\t\t\tt.Fatal(\"expected ConnectorData after first refresh\")\n\t\t}\n\n\t\t// Second refresh using output of first refresh\n\t\trefreshed2, err := conn.Refresh(context.Background(), scopes, refreshed1)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"second Refresh failed: %v\", err)\n\t\t}\n\n\t\t// All fields should match original\n\t\tif refreshed2.UserID != ident.UserID {\n\t\t\tt.Errorf(\"UserID mismatch after double refresh: got %q, want %q\", refreshed2.UserID, ident.UserID)\n\t\t}\n\t\tif refreshed2.Email != ident.Email {\n\t\t\tt.Errorf(\"Email mismatch after double refresh: got %q, want %q\", refreshed2.Email, ident.Email)\n\t\t}\n\t\tif refreshed2.Username != ident.Username {\n\t\t\tt.Errorf(\"Username mismatch after double refresh: got %q, want %q\", refreshed2.Username, ident.Username)\n\t\t}\n\t})\n\n\tt.Run(\"HandlePOSTWithAssertionSignedThenRefresh\", func(t *testing.T) {\n\t\t// Test with assertion-signed.xml (signature on assertion, not response)\n\t\tnow, err := time.Parse(timeFormat, \"2017-04-04T04:34:59.330Z\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tconn.now = func() time.Time { return now }\n\n\t\tresp, err := os.ReadFile(\"testdata/assertion-signed.xml\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tsamlResp := base64.StdEncoding.EncodeToString(resp)\n\n\t\tscopes := connector.Scopes{OfflineAccess: true, Groups: true}\n\t\tident, err := conn.HandlePOST(scopes, samlResp, \"6zmm5mguyebwvajyf2sdwwcw6m\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"HandlePOST with assertion-signed failed: %v\", err)\n\t\t}\n\n\t\tif len(ident.ConnectorData) == 0 {\n\t\t\tt.Fatal(\"expected ConnectorData after HandlePOST with assertion-signed\")\n\t\t}\n\n\t\trefreshed, err := conn.Refresh(context.Background(), scopes, ident)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Refresh after assertion-signed HandlePOST failed: %v\", err)\n\t\t}\n\n\t\tif refreshed.Email != ident.Email {\n\t\t\tt.Errorf(\"Email mismatch: got %q, want %q\", refreshed.Email, ident.Email)\n\t\t}\n\t\tif refreshed.Username != ident.Username {\n\t\t\tt.Errorf(\"Username mismatch: got %q, want %q\", refreshed.Username, ident.Username)\n\t\t}\n\t})\n\n\tt.Run(\"HandlePOSTRefreshWithoutGroupsScope\", func(t *testing.T) {\n\t\t// Verify that groups are NOT returned when groups scope is not requested during refresh\n\t\tnow, err := time.Parse(timeFormat, \"2017-04-04T04:34:59.330Z\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tconn.now = func() time.Time { return now }\n\n\t\tresp, err := os.ReadFile(\"testdata/good-resp.xml\")\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tsamlResp := base64.StdEncoding.EncodeToString(resp)\n\n\t\t// Initial auth WITH groups\n\t\tscopesWithGroups := connector.Scopes{OfflineAccess: true, Groups: true}\n\t\tident, err := conn.HandlePOST(scopesWithGroups, samlResp, \"6zmm5mguyebwvajyf2sdwwcw6m\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"HandlePOST failed: %v\", err)\n\t\t}\n\t\tif len(ident.Groups) == 0 {\n\t\t\tt.Fatal(\"expected groups in initial identity\")\n\t\t}\n\n\t\t// Refresh WITHOUT groups scope\n\t\tscopesNoGroups := connector.Scopes{OfflineAccess: true, Groups: false}\n\t\trefreshed, err := conn.Refresh(context.Background(), scopesNoGroups, ident)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Refresh failed: %v\", err)\n\t\t}\n\n\t\tif len(refreshed.Groups) != 0 {\n\t\t\tt.Errorf(\"expected no groups when groups scope not requested, got %v\", refreshed.Groups)\n\t\t}\n\n\t\t// Refresh WITH groups scope — groups should be back\n\t\trefreshedWithGroups, err := conn.Refresh(context.Background(), scopesWithGroups, ident)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Refresh with groups failed: %v\", err)\n\t\t}\n\n\t\tif len(refreshedWithGroups.Groups) == 0 {\n\t\t\tt.Error(\"expected groups when groups scope is requested\")\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "connector/saml/testdata/assertion-signed.tmpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n      <SignedInfo> \n        <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n        <Reference URI=\"#id199065211253338521862321146\">\n          <Transforms> \n            <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n            <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n          </Transforms> \n          <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n          <DigestValue/> \n        </Reference> \n        </SignedInfo> \n      <SignatureValue/> \n      <KeyInfo> \n        <X509Data/> \n      </KeyInfo> \n    </Signature>\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/assertion-signed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n      <SignedInfo> \n        <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n        <Reference URI=\"#id199065211253338521862321146\">\n          <Transforms> \n            <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n            <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n          </Transforms> \n          <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n          <DigestValue>kDdpmKMPp7zjUeMTuntXJFpT6cs=</DigestValue> \n        </Reference> \n        </SignedInfo> \n      <SignatureValue>Q12w9zPo9Bqn3W2OT7lbFbhfIDQrp7SXd/+9ZWpY4mv3ApgaOcNX2VtpSE0EjorU\n1NyrktVOkCYueJm/HVQF7gyF85KRlPBDLBZOwxbnnmrRFoCk+U2kjIt8k8eZ7NsD\njLFGFgEveS359uvaHZR1Exbr0PBYwS7aXR3fpmjMjZ9T8f8Oe3Nt/9nWPgz/dFhb\nAa+AniWuupfq2v6YMLZ+9GLiO0sOr8UVkW8AYOm2Bin30epikXT1Axi/VxWz/fjP\nnMUkgusFnhkgmIf/YncAv4S9GY6DcaV2iEj6cL70S7pcgaeRFi3iozigl+9a+lFo\ndt3Jy8Jq/N3OAyDP2DxLEw==</SignatureValue> \n      <KeyInfo> \n        <X509Data>\n<X509Certificate>MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV\nBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z\nNzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI\n4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO\nZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD\nAB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA\nfaXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF\na7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9\nQacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud\nIwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP\noR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS\nhSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De\noX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd\nesSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j\n1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q=</X509Certificate>\n</X509Data> \n      </KeyInfo> \n    </Signature>\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/bad-ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDGTCCAgGgAwIBAgIJAINei+KBx541MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV\nBAoMA0JBRDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z\nNzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0JBRDETMBEGA1UEAwwKY29yZW9zLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfSVQCQMJhwdeesQqZo\nYHktgGjKA//p176kWoqVEX8+cVgKc1IEAzN73L0KsBcNkaItbZycQxkku8NGUYvt\ninEK8V61vjK3IQBOV2+qKOeWvHPXd280I2JSGy768ELpZcaEmnA0CVbMaLdHLFQ/\ntcRocDcMKDhNexlsJ/cDPwzSkky6FU6UnaSih+I432UeJs/hRvAs1YJ3+G6uSVQ7\nxo2Fk2i+qd3IvRWOBagwEhEFcd/MfAu4+w+UYW4W1BC9zJoO2ETBgUVJzaiUGZ5z\nrX/KNAX/+kAQWQLKW1sVgRKOI2yfyIVaUzF6V2uxwHrbeV75Pr3ClVdvuUWRcXKH\nWn8CAwEAAaNQME4wHQYDVR0OBBYEFLw+L3SLMIR2wfd7IQG1m3rlgGP7MB8GA1Ud\nIwQYMBaAFLw+L3SLMIR2wfd7IQG1m3rlgGP7MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBABH2mDQuf+JqjHtBCp9coi1WuX5xJuoSKyOAvlz7QJk0bb+W\noONg+ETQ7jT7KheiFQorghdulZerv+L1dmLU5ut/Zbf1zoaWLslOgVPpOy6LP0aS\nuaa31jm7Qpig47+kTZEpPN6vUPpmAD70/uo3NgRNj8pztkWr08fEIbX/3ukHvFUE\nXPxYxgF5tFj8EY6cdflzlC/0TYmJiHt/viv/yQfrvMBRvsVHfjfzNRxbNwfEdKuf\nYIs5KeP2al7APsmF5d/UFGzoQGXaKEOpCRJ+Oj3spooLhhzWqV4gPZLVgU0DD6ja\nGPb2XgLA9mdEmMukHU0RMeb8R+r8RvytWKvuJQE=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/saml/testdata/bad-ca.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn0lUAkDCYcHXn\nrEKmaGB5LYBoygP/6de+pFqKlRF/PnFYCnNSBAMze9y9CrAXDZGiLW2cnEMZJLvD\nRlGL7YpxCvFetb4ytyEATldvqijnlrxz13dvNCNiUhsu+vBC6WXGhJpwNAlWzGi3\nRyxUP7XEaHA3DCg4TXsZbCf3Az8M0pJMuhVOlJ2koofiON9lHibP4UbwLNWCd/hu\nrklUO8aNhZNovqndyL0VjgWoMBIRBXHfzHwLuPsPlGFuFtQQvcyaDthEwYFFSc2o\nlBmec61/yjQF//pAEFkCyltbFYESjiNsn8iFWlMxeldrscB623le+T69wpVXb7lF\nkXFyh1p/AgMBAAECggEAHh/PSk6XqoVlZLSzMhPCXX4hcq3wkdtz8rCl4AJqJaEb\nz2Xw1WQK/w7YzMZCXaD951KoPlh+YuEJI0BYGvoEw83nDc0p2wisT9XANDcjKI8S\nPOkMc1W0lE2Qu5onzpr+vefHoSR2GLKQiXWpK2ZURnFI01jHT3P5CNM1SU2336ES\nXT1GXlxTz59BS04bySS3s8PQZTtmFvQAjtjOWkg+nzTYgEO7xU5968Px+jdlO+xf\nZKtjlRKRNRmVlylUvCmIT6kIzIdyjuBqw4yx0bM1Av9QwpPuDkRWBeiT8o5xeY+L\nyOSxUCg24CGgAl6mGVXWh+BC9Z3h8dXg9eMervT9gQKBgQDS6QffOBr+j19FiQwH\nt+5h2klOR/mu8TX6oW8Dc6o5YsnLt0uQqa2jLq8kYRkOBVg68mMFvZXmhPoKv4hk\nrOfDKjVfPz9ShrvuY301BAp/hdi/hNP+MAt683UlcnpwLOaASZMr+MjdnVVzfF6X\niNlV2RE3pAxSFkVlMPJJTmPPEQKBgQDLsxbYFj4JBKhMehdcsqjUtDuzLBPqisVa\ncWKACxPAXIjztsEXF4F7sQk7q68uzLU80uPBVAJjf6lWwK9tcUdcBFHCXdqj8ZXB\n77W4gZSkqHY0T6DJF+NZRF+z4cOg3qtDjTvqhetl5yyiY9IPyiSckNAz40pWD/0X\nU/0nObuwjwKBgQCpWpUHmHWUkmtd2n3edMLlr/HM+d5zqxw89APAMdAt5DVFbxku\nQBE9Ru87tvv3VjNSoe8BXQpQ39Yna0SKEozHGc1hfdfK3IVrFlgjieskGsXAg1f2\nc33EbFlUiGfoSyWLPYj/dfVUflFvOh56b1iUpog8tW1vPJLcfkEOu/NJAQKBgQCu\nLAp7Z8FRarcQ9VAmhekQPq/RSv4YjOGkrNChVVdlInpDkV9XBFVF0yFm8SzQYl8R\ni+0McG2+b/j2YbleZf6zMkpKXH/HsJjxg6qpAbt8c0LnBbMgXxmZSXpfT8o7MknU\nb93scOfPcTRcAegqchiN+tDbnRwBrJgmqz0JnjbbBwKBgCXOWMgS8Azvvw+lUqua\nyLdcAilAaqchgVBkI5ATZQodopEP+Gvevvzh6G8uQJp9fGrL/fR/tNg2viwCF/X8\nROATx1z98/ItA3kyYhiNt/A6EqOb8SVOMq/eeMpvk+RB6gA9YdMf5H9XClW4vxLy\njaw+YQ6hOyMTmFfUI2/FumFE\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "connector/saml/testdata/bad-status.tmpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n    <SignedInfo> \n      <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n      <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n      <Reference>\n        <Transforms> \n          <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n          <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        </Transforms> \n        <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n        <DigestValue/> \n      </Reference> \n      </SignedInfo> \n    <SignatureValue/> \n    <KeyInfo> \n      <X509Data/> \n    </KeyInfo> \n  </Signature>\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:AuthnFailed\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/bad-status.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n    <SignedInfo> \n      <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n      <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n      <Reference>\n        <Transforms> \n          <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n          <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        </Transforms> \n        <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n        <DigestValue>6a1wCEwD2CQ3tYWkRrVs/upMJ10=</DigestValue> \n      </Reference> \n      </SignedInfo> \n    <SignatureValue>J/yFKDQIAKkfoJyK/5Apylg6KFaK7Kwr/gn1stAUJqAab7I61gx/fVw4TCCGH5Nv\n6yXdw1f/QN6CBoNXA9sus2tG5KQ7od/lrPs+kmyaDVTp87Q6mqvQBD/Ekt2N/SQ6\njvlK70BGjJibVGoZf20EZdojpZEgvDdNa2YsVws0ZEuY4/XMsmddJnY76UDZvR8X\nNVryuMuK8rYWXMl8tLaZo5k/LgxxeDDWWwcicBQcWIOl/tuGbfssixgBHFWY4ye3\nHD8ChdxfS7sxHxAbipZ03BoutVriGQBeO3f+nI4TEIZov7V41YUqSyfcRGb3+WT4\ndm7kMaNn6GUyv3/TXuGIQg==</SignatureValue> \n    <KeyInfo> \n      <X509Data>\n<X509Certificate>MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV\nBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z\nNzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI\n4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO\nZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD\nAB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA\nfaXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF\na7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9\nQacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud\nIwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP\noR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS\nhSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De\noX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd\nesSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j\n1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q=</X509Certificate>\n</X509Data> \n    </KeyInfo> \n  </Signature>\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:AuthnFailed\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/ca.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV\nBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z\nNzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI\n4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO\nZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD\nAB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA\nfaXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF\na7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9\nQacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud\nIwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP\noR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS\nhSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De\noX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd\nesSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j\n1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/saml/testdata/ca.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCh93Slm0agiGQ9\npt2hyOJXwU/rv+AxAnXu+BoKvVroHU7nFUMw2VE2iO8M59eyTPYXT5l72kkRvQWg\ndR97jmQrP35nYQREUYT77hFXwGWhX0H0e75+1r1M0+88DCywfPhB8y93zkhjNkJw\njjHlAwAduz29H7gVuNzV2kw/a8HxQh76NVD/kwSSD51euRzHjBiK7Ri7ezGUz7j5\nty52AH2ly39r1ksy+ZwIJyb1IHHmV5srnIgX16bfHNYyLI+Mt7QaEU0amsbmltRK\nO1jChWu7Oir0wShKzyrswKCHFQeIq+o5tbYnAIAe9kknCrTySXRD3pk10TTc5IsO\n1W6WPUGnAgMBAAECggEBAJUJUDPHIwE7IAo/Drf9UpFvl2wWPmS6n+yKLeRuA0WN\nGnq27QH5Jqro7BdTCv7NpLEklNYLsar55UCWJacbCn9lSJo2Aqge3yC3GwxFRP9t\n2RHwAAVU8hHM/tmhVkn8ZLDC5o32qlNorVBG+BCEZ0n0bsYlds2+Mq8x1XGSZX7q\nYyjt02q2VCEDPVUGqR2ONEE98Bv++mUfXzZjGohBPre67PuvEppEwo/uf4ILuyYB\nlusSYcmPD2IBE/7FcRPefb2uDP9c6Tjo8PHXpK5hDBCtLz8lXheYMuTp9GqJmcOk\nkw8af2jNkMUEpFho0RqnrJNQ3A7M5PXQiWxwYSL/2RkCgYEAz4Kg0qm4Oe5jl+4r\nR986nElujo9g9Ad2sy9yT6fqceWoMKf68wh6bMx7HWXRqASS7bJ8wmS9EwgaWLBO\nmjqZax99F1UiFk3+5lNEaYB1OlFaWRsZSapoX6b2JyfEbODqSgKVIaYVTYBHLip3\n3ab/EKNUrNbW9bLSXgbt3aT2kU0CgYEAx9BiErhZqkgmip+4omCWuTr6Lj/awftB\nCVTfwLBYkh7SZcRAWc2bx7a1Bs1rvlczhGYsOlrZXnQRSM4fuUxUb+N5TCzZI9V/\nPrc2r2Lps3AB5CDPLZoyv0efBjSLqAA1tYKTvAHREi9Wfe5PNdfKiwrz6KmIwV3c\n+s2YSWcU5MMCgYEArmzvIiTnZkqsDJl2aAOMELLo64w5wuZDMHtBaxOKThLtPXj1\nyDPoNGvtUNi1UrYFiyftFrn29HhrLQGGEL4RF6pwS5yT+ou1J4X2i3gfEdYwS5Yr\nu3AyK7T8VA1pXtvwFCX3lUE1xt989aFdAEPPQv0HwAEWz5Bwo/jPGPABEkECgYBy\nvDWUikbygHuhHhXnJ49kzXjbFc+Hk77EnPferWQug4RM62QILQhGpaNNRKeZpHjw\njbrXx1MJ6ZwDMlkFDc9ucDA2jYoiCXYHjSzZiPKpFqf/VtegV+rL61RlO8b1sSkm\nENTEIEbtKkGADldtk3u6W4+zCaZ9YmiBm4zWmVpmAQKBgHvNRcbITib+sbQE+cJM\n4TtrAHFTLWtGCd+n6rKrE8gjt7ypbBOMlOau60LZ2Pbt3DrqOLtDoOalvZhYraOb\nrkoPbDAVaXAmUJ8tw2M07PPLpJuruLSFw16VrBaDiyubO8H/hvnuF4kKpRvt8ty0\nDBogMo9McFczyisRKebmANLw\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "connector/saml/testdata/gen.sh",
    "content": "#!/bin/bash -ex\n\n# Always run from the testdata directory\ncd \"$(dirname \"$0\")\"\n\n# Uncomment these commands to regenerate the CA files.\n#\n# openssl req \\\n#     -nodes \\\n#     -newkey rsa:2048 \\\n#     -keyout ca.key \\\n#     -new -x509 -days 7300 \\\n#     -extensions v3_ca \\\n#     -out ca.crt \\\n#     -subj \"/O=DEX/CN=coreos.com\"\n# \n# openssl req \\\n#     -nodes \\\n#     -newkey rsa:2048 \\\n#     -keyout bad-ca.key \\\n#     -new -x509 -days 7300 \\\n#     -extensions v3_ca \\\n#     -out bad-ca.crt \\\n#     -subj \"/O=BAD/CN=coreos.com\"\n\n# Sign these files using xmlsec1.\n#\n# Templates MUST have a <Signature> element already embedded in them so\n# xmlsec1 can know where to embed the signature.\n#\n# See: https://sgros.blogspot.com/2013/01/signing-xml-document-using-xmlsec1.html\n\nxmlsec1 --sign --privkey-pem ca.key,ca.crt --output good-resp.xml good-resp.tmpl\nxmlsec1 --sign --privkey-pem ca.key,ca.crt --output bad-status.xml bad-status.tmpl\n\n# Sign a specific sub element, not just the root.\n#\n# Values match up to the <Response URI=\"#(ID)\"> element in the documents.\nxmlsec1 --sign --privkey-pem ca.key,ca.crt  \\\n    --id-attr:ID Assertion \\\n    --output assertion-signed.xml assertion-signed.tmpl\n\nxmlsec1 --sign --privkey-pem ca.key,ca.crt \\\n    --id-attr:ID Assertion \\\n    --output two-assertions-first-signed.xml \\\n    two-assertions-first-signed.tmpl\n\n"
  },
  {
    "path": "connector/saml/testdata/good-resp.tmpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n    <SignedInfo> \n      <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n      <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n      <Reference>\n        <Transforms> \n          <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n          <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        </Transforms> \n        <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n        <DigestValue/> \n      </Reference> \n      </SignedInfo> \n    <SignatureValue/> \n    <KeyInfo> \n      <X509Data/> \n    </KeyInfo> \n  </Signature>\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/good-resp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n    <SignedInfo> \n      <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n      <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n      <Reference>\n        <Transforms> \n          <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n          <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        </Transforms> \n        <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n        <DigestValue>ew38E1LGMwYT+0gUZNq0RacD3GM=</DigestValue> \n      </Reference> \n      </SignedInfo> \n    <SignatureValue>TQ84pCaZAyEDBGkNafTMfwPUWujFvmdoXzyYMXZURIlKhA8Pv1bIZfzQ5MgbQr1W\nz2Ye99/hss24Y4ueNT9nS+53LvDekhNctFGYfgdMjrbxs8Awo3KnbvveDib5zGvk\nfWd/0/QLvlbFd/3670QGb5JQE1nD9mlAqPonyQgoufk63gEM84+tU71cAM7XKiy6\n09MC0y4s967qRAiLAtfgKbvi+46HkF/g+WsS74Wa8cu/A863URt56W0cogRjHWpQ\nB+q8/FyVeJRE0NlrOjhnsgTU2QJtvkxYYvqIpRDbMv53NLKeAFvRhOcyJxhFXtSj\nLF/oPMjbmHji4ylFiAlQWw==</SignatureValue> \n    <KeyInfo> \n      <X509Data>\n<X509Certificate>MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV\nBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z\nNzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI\n4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO\nZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD\nAB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA\nfaXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF\na7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9\nQacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud\nIwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP\noR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS\nhSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De\noX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd\nesSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j\n1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q=</X509Certificate>\n</X509Data> \n    </KeyInfo> \n  </Signature>\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/idp-cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNV\nBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNV\nBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcN\nAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3\nWjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMD\nSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEb\nMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOC\nAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3\nvNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsby\nCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1u\nuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sB\nTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4Xn\nXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQW\nBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsH\nTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3Ju\naWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMT\nCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb3\n9nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW\n6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1\nwG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUK\nFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+a\njSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor\n/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0De\nJc3men4=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/saml/testdata/idp-resp-signed-assertion.xml",
    "content": "<Response xmlns=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"http://localhost:5556/dex/callback\" ID=\"id108965453120986171998428970\" InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n  <Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n  <Status>\n    <StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </Status>\n  <Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"pfxe4534a5f-0f40-2f3a-599d-4dfd123f7d0a\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n    <Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n  <ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n    <ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n  <ds:Reference URI=\"#pfxe4534a5f-0f40-2f3a-599d-4dfd123f7d0a\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>HFNooGfpAONF7T96W3bFsXkH51k=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>dI0QBihhNT5rtRYE9iB0lEKXkE7Yr4+QueOItRH2RcKwAXJ6DA/m3D/S7qwXk00Hn8ZpHu48ZO+HJpyweEEh2UuUWJCCTwwggagKybbSoRx3UTnSuNAFTdoDWTGt89z8j4+gRMC0sepYwppF3u87vJKRVBh8HjFfrHmWsZKwNtfoeXOOFCeatwxcI1sKCoBs2fTn78683ThoAJe3pygipSHY5WPt4dfT/yAY5Ars+OPY/N02M80OfIygZXdJwND0tVPJIF3M9DaehSkvCBHs7QA7DARsRXcuXdsYY7R8wHzqDVJZ4OvcsprONamm5AgUIpql1CjT94rFwWOFyxF2tg==</ds:SignatureValue>\n<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>\n    <Subject>\n      <NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">eric.chiang+okta@coreos.com</NameID>\n      <SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <SubjectConfirmationData InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\" Recipient=\"http://localhost:5556/dex/callback\"/>\n      </SubjectConfirmation>\n    </Subject>\n    <Conditions NotBefore=\"2016-12-20T22:13:23.772Z\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\">\n      <AudienceRestriction>\n        <Audience>http://localhost:5556/dex/callback</Audience>\n      </AudienceRestriction>\n    </Conditions>\n    <AuthnStatement AuthnInstant=\"2016-12-20T22:18:23.771Z\" SessionIndex=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\">\n      <AuthnContext>\n        <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>\n      </AuthnContext>\n    </AuthnStatement>\n  </Assertion>\n</Response>\n"
  },
  {
    "path": "connector/saml/testdata/idp-resp-signed-assertion0.xml",
    "content": "<Response xmlns=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"http://localhost:5556/dex/callback\" ID=\"id108965453120986171998428970\" InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n  <Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n  <Status>\n    <StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </Status>\n  <Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"pfxe4534a5f-0f40-2f3a-599d-4dfd123f7d0a\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n    <Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n  <ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n    <ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n  <ds:Reference URI=\"#pfxe4534a5f-0f40-2f3a-599d-4dfd123f7d0a\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>HFNooGfpAONF7T96W3bFsXkH51k=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>dI0QBihhNT5rtRYE9iB0lEKXkE7Yr4+QueOItRH2RcKwAXJ6DA/m3D/S7qwXk00Hn8ZpHu48ZO+HJpyweEEh2UuUWJCCTwwggagKybbSoRx3UTnSuNAFTdoDWTGt89z8j4+gRMC0sepYwppF3u87vJKRVBh8HjFfrHmWsZKwNtfoeXOOFCeatwxcI1sKCoBs2fTn78683ThoAJe3pygipSHY5WPt4dfT/yAY5Ars+OPY/N02M80OfIygZXdJwND0tVPJIF3M9DaehSkvCBHs7QA7DARsRXcuXdsYY7R8wHzqDVJZ4OvcsprONamm5AgUIpql1CjT94rFwWOFyxF2tg==</ds:SignatureValue>\n<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>\n    <Subject>\n      <NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">eric.chiang+okta@coreos.com</NameID>\n      <SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <SubjectConfirmationData InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\" Recipient=\"http://localhost:5556/dex/callback\"/>\n      </SubjectConfirmation>\n    </Subject>\n    <Conditions NotBefore=\"2016-12-20T22:13:23.772Z\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\">\n      <AudienceRestriction>\n        <Audience>http://localhost:5556/dex/callback</Audience>\n      </AudienceRestriction>\n    </Conditions>\n    <AuthnStatement AuthnInstant=\"2016-12-20T22:18:23.771Z\" SessionIndex=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\">\n      <AuthnContext>\n        <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>\n      </AuthnContext>\n    </AuthnStatement>\n  </Assertion>\n  <!-- Attacker, unsigned assertion below -->\n  <Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id10896545312129779529177535\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n    <Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n    <Subject>\n      <NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">eric.chiang+attacker@coreos.com</NameID>\n      <SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <SubjectConfirmationData InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\" Recipient=\"http://localhost:5556/dex/callback\"/>\n      </SubjectConfirmation>\n    </Subject>\n    <Conditions NotBefore=\"2016-12-20T22:13:23.772Z\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\">\n      <AudienceRestriction>\n        <Audience>http://localhost:5556/dex/callback</Audience>\n      </AudienceRestriction>\n    </Conditions>\n    <AuthnStatement AuthnInstant=\"2016-12-20T22:18:23.771Z\" SessionIndex=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\">\n      <AuthnContext>\n        <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>\n      </AuthnContext>\n    </AuthnStatement>\n    <AttributeStatement>\n      <Attribute Name=\"user\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\">\n        <AttributeValue xsi:type=\"xs:string\">attacker</AttributeValue>\n      </Attribute>\n      <Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\">\n        <AttributeValue xsi:type=\"xs:string\">eric.chiang+attacker@coreos.com</AttributeValue>\n      </Attribute>\n    </AttributeStatement>\n  </Assertion>\n</Response>\n"
  },
  {
    "path": "connector/saml/testdata/idp-resp-signed-message-and-assertion.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response xmlns=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"http://localhost:5556/dex/callback\" ID=\"pfxb2d65771-7c81-6391-4e1f-79211ae3a3fd\" InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\"><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n  <ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n    <ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n  <ds:Reference URI=\"#pfxb2d65771-7c81-6391-4e1f-79211ae3a3fd\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>P2k0nQ19ZcowlcaOz6do6Tyu8WI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>ytdy1qRPdMeIGnlkkaeLdblzTPtIFc0EJNm8WktsNU1Mn6G/6AmNaXEUik2BkTpk8zKabHdSf6+le8hwRiyfNWPTF84lzVdMjQ/+I8pnX/srpG534zoSAsP6ZFQvHp46AHPx31KP75H/ymqx2DNppqxh8JjUeMKQkPUEqduWUZ4kFjcsrz9H3MNVsHfxntnswibiknU/wAthtBuY2I6yOIF55RprUgYb5j2TqDd3IArF6LkxWRvHvhaw66MdhY1iiit7AFOcuHJVyPe8Attra94jwM+O1Ch+HQgoI43nX91d/jkP0vyWzWD8Xkcwb+KuRPsQflxjV22UU0+JbwrBYA==</ds:SignatureValue>\n<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>\n  <Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n  <Status>\n    <StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </Status>\n  <Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"pfxa75683e6-25ff-5639-6806-08d2a132ad4e\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n    <Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n  <ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n    <ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n  <ds:Reference URI=\"#pfxa75683e6-25ff-5639-6806-08d2a132ad4e\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>4jNCSI3tTnbpozZ8qT4FZe+EWV8=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>xtTnl92ArZyxskD3b34cIjo5LIpeE+3RjW+jgtXMXhUIZp3uGJ2RC6n1CbJ6IWuo4KmezpnVUnSWNz/fgOTZCN/1VlqsfLDpoTf790GrP+q6rKyw8CW7nd0uVS5FRYe05HTO6C5RqnaE9PmZ/YYbiWtLIDx0+kqvu/jFr+D144G/mukaVG4ydnDQ/tl21N6hWIOpi1tWaNPv50OEEgY//9VPql9Us3YuhfrxNggVugauArwY9RL4nVFVjALP1wpkZn1JzpgNMFgvXfY3MxnI1OnWg6ypJESugIKroKqj5RyqMIaLICsUOBwIKk8R4zAATrB+D+kuFV9Ec837duW/Eg==</ds:SignatureValue>\n<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>\n    <Subject>\n      <NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">eric.chiang+okta@coreos.com</NameID>\n      <SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <SubjectConfirmationData InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\" Recipient=\"http://localhost:5556/dex/callback\"/>\n      </SubjectConfirmation>\n    </Subject>\n    <Conditions NotBefore=\"2016-12-20T22:13:23.772Z\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\">\n      <AudienceRestriction>\n        <Audience>http://localhost:5556/dex/callback</Audience>\n      </AudienceRestriction>\n    </Conditions>\n    <AuthnStatement AuthnInstant=\"2016-12-20T22:18:23.771Z\" SessionIndex=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\">\n      <AuthnContext>\n        <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>\n      </AuthnContext>\n    </AuthnStatement>\n  </Assertion>\n</Response>\n"
  },
  {
    "path": "connector/saml/testdata/idp-resp-signed-message.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response xmlns=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"http://localhost:5556/dex/callback\" ID=\"pfx2801a166-e451-31a9-87a4-4cd777c16182\" InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\"><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n  <ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n    <ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n  <ds:Reference URI=\"#pfx2801a166-e451-31a9-87a4-4cd777c16182\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>Oy9CTB/hzFWyr6QF99EZ4ymIEfY=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>COKw1DUpDzNVulVNFlcrPwaalCwr8BfTye92GN5snTmx3IDKLudr1PT+WPt5i2N4xaoFcq/X1p/yEVmWtC1O+YYXNNIouFwps9Gyw/iDEMs1TtVKfikbloKWkDdYgfqgcon+mOq/lHagLVAcgz5QRBHVTIrFWcFYbnemsj1hy8q7ToIeoyHX9f5TAZBfZEEbdZcsD581xKPafNGowgfWxkgEwLBzFsJVYg/QfeoNnORTsKlsQBuswiXrsWatZNOOjWpdF9qqYn/3f9axqx2CJPD2HB38Vl0g4dTFnpmMAe45ndJq0IpXr9YJNCDJjUIvR7srdV1AW7qe2Mp6LxBBNw==</ds:SignatureValue>\n<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEUTCCAzmgAwIBAgIJAJdmunb39nFKMA0GCSqGSIb3DQEBCwUAMHgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMQwwCgYDVQQKEwNJRFAxFDASBgNVBAsTC1NTT1Byb3ZpZGVyMRMwEQYDVQQDEwpkZXYtOTY5MjQ0MRswGQYJKoZIhvcNAQkBFgxpbmZvQGlkcC5vcmcwHhcNMTcwMTI0MTczMTI3WhcNMjcwMTIyMTczMTI3WjB4MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEChMDSURQMRQwEgYDVQQLEwtTU09Qcm92aWRlcjETMBEGA1UEAxMKZGV2LTk2OTI0NDEbMBkGCSqGSIb3DQEJARYMaW5mb0BpZHAub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0X/AE1tmDmhGRROAWaJ82XSORivRfgNt9Fb4rLrf6nIJsQN3vNb1Nk4DSUEDdQuvHNaEemSVkSPgfq5qnhh37bJaghr0728J8dOyYzV5eArPvsbyCRcnhXQzpCK2zvHwjgxNJMsNJLbnYpG/U+dCdCtcOOn9JEhKO8wKn06y2tcrvC1uuVs7bodukPUNq82KJTyvCQP8jh1hEZXeR2siJFDeJj1n2FNTMeCKIqOb42J/i+sBTlyK3mV5Ni++hI/ssIYVbPwrMIBd6sKLVAgInshBHOj/7XcXW/rMf468YtBKs4XnXsE3hLoU02aWCRDlVHa4hm3jfIAqEADOUumklQIDAQABo4HdMIHaMB0GA1UdDgQWBBRjN/dQSvhZxIsHTXmDKQJkPrjp0TCBqgYDVR0jBIGiMIGfgBRjN/dQSvhZxIsHTXmDKQJkPrjp0aF8pHoweDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAoTA0lEUDEUMBIGA1UECxMLU1NPUHJvdmlkZXIxEzARBgNVBAMTCmRldi05NjkyNDQxGzAZBgkqhkiG9w0BCQEWDGluZm9AaWRwLm9yZ4IJAJdmunb39nFKMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAIqHUglIUAA+BKMW6B0Q+cqIgDr9fWlsvDwIVK7/cvUeGIH3icSsje9AVZ4nQOJpxmC/E06HfuDXmbT1wG16jNo01mPW9qaOGRJuQqlZdegCSF385o/OHcbaEKBRwyYuvLfu80EREj8wcMUKFpExoaxK7K8DS7hh3w7exLB80jyhIaDEYc1hdyAl+206XpOXSYBetsg7I622R2+ajSL7ygUxQjmKQ5DyInPdXzCFCL6Ew/BN0dwzfnBEEK223ruOWBLpj13zMC077dor/NgYyHZU6iqiDS2eYO5jhVMve/mP9734+6N34seQRmekfmsf2dJcEQhPVYr/j0DeJc3men4=</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>\n  <Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n  <Status>\n    <StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </Status>\n  <Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id10896545312129779529177535\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n    <Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n    <Subject>\n      <NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">eric.chiang+okta@coreos.com</NameID>\n      <SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <SubjectConfirmationData InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\" Recipient=\"http://localhost:5556/dex/callback\"/>\n      </SubjectConfirmation>\n    </Subject>\n    <Conditions NotBefore=\"2016-12-20T22:13:23.772Z\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\">\n      <AudienceRestriction>\n        <Audience>http://localhost:5556/dex/callback</Audience>\n      </AudienceRestriction>\n    </Conditions>\n    <AuthnStatement AuthnInstant=\"2016-12-20T22:18:23.771Z\" SessionIndex=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\">\n      <AuthnContext>\n        <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>\n      </AuthnContext>\n    </AuthnStatement>\n  </Assertion>\n</Response>\n"
  },
  {
    "path": "connector/saml/testdata/idp-resp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Response xmlns=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"http://localhost:5556/dex/callback\" ID=\"id108965453120986171998428970\" InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n  <Issuer xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n  <Status>\n    <StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </Status>\n  <Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id10896545312129779529177535\" IssueInstant=\"2016-12-20T22:18:23.771Z\" Version=\"2.0\">\n    <Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</Issuer>\n    <Subject>\n      <NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">eric.chiang+okta@coreos.com</NameID>\n      <SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <SubjectConfirmationData InResponseTo=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\" Recipient=\"http://localhost:5556/dex/callback\"/>\n      </SubjectConfirmation>\n    </Subject>\n    <Conditions NotBefore=\"2016-12-20T22:13:23.772Z\" NotOnOrAfter=\"2116-12-20T22:23:23.772Z\">\n      <AudienceRestriction>\n        <Audience>http://localhost:5556/dex/callback</Audience>\n      </AudienceRestriction>\n    </Conditions>\n    <AuthnStatement AuthnInstant=\"2016-12-20T22:18:23.771Z\" SessionIndex=\"_fd1b3ef9-ec09-44a7-a66b-0d39c250f6a0\">\n      <AuthnContext>\n        <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>\n      </AuthnContext>\n    </AuthnStatement>\n    <AttributeStatement>\n      <Attribute Name=\"user\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\">\n        <AttributeValue xsi:type=\"xs:string\">admin</AttributeValue>\n      </Attribute>\n      <Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\">\n        <AttributeValue xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</AttributeValue>\n      </Attribute>\n    </AttributeStatement>\n  </Assertion>\n</Response>\n"
  },
  {
    "path": "connector/saml/testdata/oam-ca.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIB/jCCAWegAwIBAgIBCjANBgkqhkiG9w0BAQQFADAkMSIwIAYDVQQDExlkZWFv\nYW0tZGV2MDIuanBsLm5hc2EuZ292MB4XDTE2MDYzMDA0NTQxNloXDTI2MDYyODA0\nNTQxNlowJDEiMCAGA1UEAxMZZGVhb2FtLWRldjAyLmpwbC5uYXNhLmdvdjCBnzAN\nBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAht1N4lGdwUbl7YRyHwSCrnep6/e2I3+V\neue0pSA/DGn8OuR/udM8UCja5utqlqJdq200ox4b4Mpz0Jg9kMckALtKe+1DgeES\nEIx9FpeuBdHlitYQNSbEr30HIG2nmeTOy4Vi5unBO54um3tNazcUTMA0/LJ6KQL8\nLeZSlB/IxwUCAwEAAaNAMD4wDAYDVR0TAQH/BAIwADAPBgNVHQ8BAf8EBQMDB9gA\nMB0GA1UdDgQWBBRYo1YjfrNonauLzj6/AsueWFGSszANBgkqhkiG9w0BAQQFAAOB\ngQACq7GHK/Zsg0+qC0WWa2ZjmOXE6Dqk/xuooG49QT7ihABs7k9U27Fw3xKF6MkC\n7pca1FwT82eZK1N3XKKpZe7Flu1fMKt2o/XSiBkDjWwUcChVnwGsUBe8hJFwFqg7\nolNJn1kaVBJUqZIiXF9kS0d+1H55rStOd0CNXAzp9utr2A==\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/saml/testdata/oam-resp.xml",
    "content": "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:dsig=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:enc=\"http://www.w3.org/2001/04/xmlenc#\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:x500=\"urn:oasis:names:tc:SAML:2.0:profiles:attribute:X500\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" Destination=\"http://127.0.0.1:5556/callback\" ID=\"id-IWlPTptSB-PlR80dwt8ZhVeG70mrz7nPvTVrhduK\" InResponseTo=\"_e66b3a98-831c-4c96-5706-b63fe0549624\" IssueInstant=\"2016-12-12T16:54:35Z\" Version=\"2.0\"><saml:Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">https://deaoam-dev02.jpl.nasa.gov:14101/oam/fed</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/></samlp:Status><saml:Assertion ID=\"id-rT9rTqxdQC9j34YhVeNayUWC9EbIBgym6gp-MZt-\" IssueInstant=\"2016-12-12T16:54:35Z\" Version=\"2.0\"><saml:Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">https://deaoam-dev02.jpl.nasa.gov:14101/oam/fed</saml:Issuer><dsig:Signature><dsig:SignedInfo><dsig:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/><dsig:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/><dsig:Reference URI=\"#id-rT9rTqxdQC9j34YhVeNayUWC9EbIBgym6gp-MZt-\"><dsig:Transforms><dsig:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><dsig:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></dsig:Transforms><dsig:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><dsig:DigestValue>z1HD/59hv6UOd5+jeG+ihaFWLgI=</dsig:DigestValue></dsig:Reference></dsig:SignedInfo><dsig:SignatureValue>I99oG5kiOfIgbXYa21z/TOmzftTkFnXe9ObhBNSKit9kAhT93apYROqqXv4Ax96P144Ld7ERX1hgJsytK8LC2874Pk7QrSNm4zvW3x0D4GR4lM06CvJK/EhIur3TrCUJDPigvyP7TJitheCyBejwt0x0lqNP/OzR3tMbAIMRoho=</dsig:SignatureValue></dsig:Signature><saml:Subject><saml:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\" NameQualifier=\"https://deaoam-dev02.jpl.nasa.gov:14101/oam/fed\" SPNameQualifier=\"JSAuth\">pkieu</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"_e66b3a98-831c-4c96-5706-b63fe0549624\" NotOnOrAfter=\"2016-12-12T16:59:35Z\" Recipient=\"http://127.0.0.1:5556/callback\"/></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2016-12-12T16:54:35Z\" NotOnOrAfter=\"2016-12-12T16:59:35Z\"><saml:AudienceRestriction><saml:Audience>JSAuth</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2016-12-12T16:54:10Z\" SessionIndex=\"id-l3NCbxKoBfUZcuKhlotMuIF3ydgYJgGGG6BGTTU6\" SessionNotOnOrAfter=\"2016-12-12T17:54:35Z\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement></saml:Assertion></samlp:Response>\n"
  },
  {
    "path": "connector/saml/testdata/okta-ca.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDpDCCAoygAwIBAgIGAVjgvNroMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG\nA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\nMBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NjkyNDQxHDAaBgkqhkiG9w0BCQEW\nDWluZm9Ab2t0YS5jb20wHhcNMTYxMjA4MjMxOTIzWhcNMjYxMjA4MjMyMDIzWjCBkjELMAkGA1UE\nBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV\nBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTY5MjQ0MRwwGgYJ\nKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n4dW2YlcXjZwnTmLnV7IOBq8hhrdbqlNwdjCHRyx1BizOk3RbVP56grgPdyWScTCPpJ6vZZ8rtrY0\nm1rwr+cxifNuQGKTlE33g2hReo/N9f3LFUMITlnnNH80Yium3SYuEqGeHLYerelXOnEKx6x+X5qD\neg2DRW6I9/v/mfN2KAQEDqF9aSNlNFWZWmb52kukMv3tLWw0puaevicIZ/nZrW+D3CLDVVfWHeVt\n46EF2bkLdgbIJOU3GzLoolgBOCkydX9x6xTw6knwQaqYsRGflacw6571IzWEwjmd17uJXkarnhM1\n51pqwIoksTzycbjinIg6B1rNpGFDN7Ah+9EnVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDQOtlj\n7Yk1j4GV/135zLOlsaonq8zfsu05VfY5XydNPFEkyIVcegLa8RiqALyFf+FjY/wVyhXtARw7NBUo\nu/Jh63cVya/VEoP1SVa0XQLf/ial+XuwdBBL1yc+7o2XHOfluDaXw/v2FWYpZtICf3a39giGaNWp\neCT4g2TDWM4Jf/X7/mRbLX9tQO7XRural1CXx8VIMcmbKNbUtQiO8yEtVQ+FJKOsl7KOSzkgqNiL\nrJy+Y0D9biLZVKp07KWAY2FPCEtCkBrvo8BhvWbxWMA8CVQNAiTylM27Pc6kbc64pNr7C1Jx1wuE\nmVy9Fgb4PA2N3hPeD7mBmGGp7CfDbGcy\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "connector/saml/testdata/okta-resp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"><saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/><ds:Reference URI=\"#id19906521125278359305566047\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"><ec:InclusiveNamespaces xmlns:ec=\"http://www.w3.org/2001/10/xml-exc-c14n#\" PrefixList=\"xs\"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/><ds:DigestValue>/jttoTHVc1zHB8tgDVQtiNLAhKAiL24V5Np/SNViPag=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>y1v4Q0No1l82Y32SHL8weC4mXPWRmhy0SdWPd4Dw1gMWW2aGdnMt11VGom4z1/42HxKB0EBje/UPJeagcpEgJ2JQTdb1SvZrQaxwcWexH0Qyw2Tim9kv66dgr0Uo6PRmkYw/ewcimgdNZUOieffUG1Wc+YWtllmE4nz6NR3aLT9WScbC5smbXk2HwWP9xSg3loKMYTUSH32sRePw76QdGbVExUYyFjnB+oY9mIzBHvCTjZw6JGqEaMBmzReCgLZEBdeo0BuitTWCJY5bayJEpasJCQE4iurcKsz6wMheBbEAcQBqiExSUU8dJgQPhD9y5nMBEGC4cvdq4xbvWBKfVQ==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAVjgvNroMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG\nA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\nMBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NjkyNDQxHDAaBgkqhkiG9w0BCQEW\nDWluZm9Ab2t0YS5jb20wHhcNMTYxMjA4MjMxOTIzWhcNMjYxMjA4MjMyMDIzWjCBkjELMAkGA1UE\nBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV\nBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTY5MjQ0MRwwGgYJ\nKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n4dW2YlcXjZwnTmLnV7IOBq8hhrdbqlNwdjCHRyx1BizOk3RbVP56grgPdyWScTCPpJ6vZZ8rtrY0\nm1rwr+cxifNuQGKTlE33g2hReo/N9f3LFUMITlnnNH80Yium3SYuEqGeHLYerelXOnEKx6x+X5qD\neg2DRW6I9/v/mfN2KAQEDqF9aSNlNFWZWmb52kukMv3tLWw0puaevicIZ/nZrW+D3CLDVVfWHeVt\n46EF2bkLdgbIJOU3GzLoolgBOCkydX9x6xTw6knwQaqYsRGflacw6571IzWEwjmd17uJXkarnhM1\n51pqwIoksTzycbjinIg6B1rNpGFDN7Ah+9EnVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDQOtlj\n7Yk1j4GV/135zLOlsaonq8zfsu05VfY5XydNPFEkyIVcegLa8RiqALyFf+FjY/wVyhXtARw7NBUo\nu/Jh63cVya/VEoP1SVa0XQLf/ial+XuwdBBL1yc+7o2XHOfluDaXw/v2FWYpZtICf3a39giGaNWp\neCT4g2TDWM4Jf/X7/mRbLX9tQO7XRural1CXx8VIMcmbKNbUtQiO8yEtVQ+FJKOsl7KOSzkgqNiL\nrJy+Y0D9biLZVKp07KWAY2FPCEtCkBrvo8BhvWbxWMA8CVQNAiTylM27Pc6kbc64pNr7C1Jx1wuE\nmVy9Fgb4PA2N3hPeD7mBmGGp7CfDbGcy</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\"><saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/></saml2p:Status><saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"><saml2:Issuer Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\" xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer><ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/><ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/><ds:Reference URI=\"#id199065211253338521862321146\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"><ec:InclusiveNamespaces xmlns:ec=\"http://www.w3.org/2001/10/xml-exc-c14n#\" PrefixList=\"xs\"/></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/><ds:DigestValue>W9osU1+7JHYa6E3Y3tvOCBN/jAGVTl39Wngwkq+4jKI=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>OWa1WvK9+/HOgfm37RwpzUYryJl3ryzdqRUEMlV3ZdLcCz7ZnQ26PCoZD7KxdxUjRci81Ev3OsT8pMCj+sDbFbvBhdRrVvS0sJEgqIFftgD3CYH6OaNGmahSum5Cz+p7xNow/XNQcxjEI2luAtMs+l9JlH8eNPvVVE8RSnXdq9G+y6Iu6RKBVESN9Lgwc6+L2XpqzalZv4d3D+c5dyFCjtNXLOLCJtu3F5w+dYM3KuYJ913S7fbXB2Tv7yRioc9Ssuehft5YacPY9ACsi0PZYAs9Gh4wlzu2yI+7cBfA6hn+h3T/uLu3DMWtHelKtkyOvJS2kFYJlFDio+h7uUbi9A==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIDpDCCAoygAwIBAgIGAVjgvNroMA0GCSqGSIb3DQEBBQUAMIGSMQswCQYDVQQGEwJVUzETMBEG\nA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEU\nMBIGA1UECwwLU1NPUHJvdmlkZXIxEzARBgNVBAMMCmRldi05NjkyNDQxHDAaBgkqhkiG9w0BCQEW\nDWluZm9Ab2t0YS5jb20wHhcNMTYxMjA4MjMxOTIzWhcNMjYxMjA4MjMyMDIzWjCBkjELMAkGA1UE\nBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xDTALBgNV\nBAoMBE9rdGExFDASBgNVBAsMC1NTT1Byb3ZpZGVyMRMwEQYDVQQDDApkZXYtOTY5MjQ0MRwwGgYJ\nKoZIhvcNAQkBFg1pbmZvQG9rdGEuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\n4dW2YlcXjZwnTmLnV7IOBq8hhrdbqlNwdjCHRyx1BizOk3RbVP56grgPdyWScTCPpJ6vZZ8rtrY0\nm1rwr+cxifNuQGKTlE33g2hReo/N9f3LFUMITlnnNH80Yium3SYuEqGeHLYerelXOnEKx6x+X5qD\neg2DRW6I9/v/mfN2KAQEDqF9aSNlNFWZWmb52kukMv3tLWw0puaevicIZ/nZrW+D3CLDVVfWHeVt\n46EF2bkLdgbIJOU3GzLoolgBOCkydX9x6xTw6knwQaqYsRGflacw6571IzWEwjmd17uJXkarnhM1\n51pqwIoksTzycbjinIg6B1rNpGFDN7Ah+9EnVQIDAQABMA0GCSqGSIb3DQEBBQUAA4IBAQDQOtlj\n7Yk1j4GV/135zLOlsaonq8zfsu05VfY5XydNPFEkyIVcegLa8RiqALyFf+FjY/wVyhXtARw7NBUo\nu/Jh63cVya/VEoP1SVa0XQLf/ial+XuwdBBL1yc+7o2XHOfluDaXw/v2FWYpZtICf3a39giGaNWp\neCT4g2TDWM4Jf/X7/mRbLX9tQO7XRural1CXx8VIMcmbKNbUtQiO8yEtVQ+FJKOsl7KOSzkgqNiL\nrJy+Y0D9biLZVKp07KWAY2FPCEtCkBrvo8BhvWbxWMA8CVQNAiTylM27Pc6kbc64pNr7C1Jx1wuE\nmVy9Fgb4PA2N3hPeD7mBmGGp7CfDbGcy</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature><saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\"><saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID><saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/></saml2:SubjectConfirmation></saml2:Subject><saml2:Conditions NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\"><saml2:AudienceRestriction><saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience></saml2:AudienceRestriction></saml2:Conditions><saml2:AuthnStatement AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\" xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\"><saml2:AuthnContext><saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef></saml2:AuthnContext></saml2:AuthnStatement><saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\"><saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\"><saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\"><saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue></saml2:Attribute><saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\"><saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue><saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue></saml2:Attribute></saml2:AttributeStatement></saml2:Assertion></saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/tampered-resp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n    <SignedInfo> \n      <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n      <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n      <Reference>\n        <Transforms> \n          <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n          <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        </Transforms> \n        <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n        <DigestValue>ew38E1LGMwYT+0gUZNq0RacD3GM=</DigestValue> \n      </Reference> \n      </SignedInfo> \n    <SignatureValue>TQ84pCaZAyEDBGkNafTMfwPUWujFvmdoXzyYMXZURIlKhA8Pv1bIZfzQ5MgbQr1W\nz2Ye99/hss24Y4ueNT9nS+53LvDekhNctFGYfgdMjrbxs8Awo3KnbvveDib5zGvk\nfWd/0/QLvlbFd/3670QGb5JQE1nD9mlAqPonyQgoufk63gEM84+tU71cAM7XKiy6\n09MC0y4s967qRAiLAtfgKbvi+46HkF/g+WsS74Wa8cu/A863URt56W0cogRjHWpQ\nB+q8/FyVeJRE0NlrOjhnsgTU2QJtvkxYYvqIpRDbMv53NLKeAFvRhOcyJxhFXtSj\nLF/oPMjbmHji4ylFiAlQWw==</SignatureValue> \n    <KeyInfo> \n      <X509Data>\n<X509Certificate>MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV\nBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z\nNzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI\n4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO\nZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD\nAB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA\nfaXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF\na7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9\nQacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud\nIwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP\noR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS\nhSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De\noX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd\nesSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j\n1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q=</X509Certificate>\n</X509Data> \n    </KeyInfo> \n  </Signature>\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">not<!-- comment -->eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/two-assertions-first-signed.tmpl",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <!--\n      First assertion is signed but in the wrong place (wrapped by \"AttackersElement\").\n      Previously triggered an edge case where the signature validation would find this\n      assertion and validate the signature, but latter XML parsing would grab second\n      one provided by the attacker.\n  -->\n  <AttackersElement>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n      <SignedInfo> \n        <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n        <Reference URI=\"#id199065211253338521862321146\">\n          <Transforms> \n            <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n            <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n          </Transforms> \n          <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n          <DigestValue/> \n        </Reference> \n        </SignedInfo> \n      <SignatureValue/> \n      <KeyInfo> \n        <X509Data/> \n      </KeyInfo> \n    </Signature>\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n  </AttackersElement>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"foobar\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">\n\n          I AM AN ATTACKER TRYING TO GET YOU TO USE THIS ASSERTION!!!\n\n        </saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/testdata/two-assertions-first-signed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<saml2p:Response xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" Destination=\"http://127.0.0.1:5556/dex/callback\" ID=\"id19906521125278359305566047\" InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n  <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n  <saml2p:Status xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n    <saml2p:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>\n  </saml2p:Status>\n  <!--\n      First assertion is signed but in the wrong place (wrapped by \"AttackersElement\").\n      Previously triggered an edge case where the signature validation would find this\n      assertion and validate the signature, but latter XML parsing would grab second\n      one provided by the attacker.\n  -->\n  <AttackersElement>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"id199065211253338521862321146\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <Signature xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n      <SignedInfo> \n        <CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n        <SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n        <Reference URI=\"#id199065211253338521862321146\">\n          <Transforms> \n            <Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/> \n            <Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/> \n          </Transforms> \n          <DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/> \n          <DigestValue>kDdpmKMPp7zjUeMTuntXJFpT6cs=</DigestValue> \n        </Reference> \n        </SignedInfo> \n      <SignatureValue>Q12w9zPo9Bqn3W2OT7lbFbhfIDQrp7SXd/+9ZWpY4mv3ApgaOcNX2VtpSE0EjorU\n1NyrktVOkCYueJm/HVQF7gyF85KRlPBDLBZOwxbnnmrRFoCk+U2kjIt8k8eZ7NsD\njLFGFgEveS359uvaHZR1Exbr0PBYwS7aXR3fpmjMjZ9T8f8Oe3Nt/9nWPgz/dFhb\nAa+AniWuupfq2v6YMLZ+9GLiO0sOr8UVkW8AYOm2Bin30epikXT1Axi/VxWz/fjP\nnMUkgusFnhkgmIf/YncAv4S9GY6DcaV2iEj6cL70S7pcgaeRFi3iozigl+9a+lFo\ndt3Jy8Jq/N3OAyDP2DxLEw==</SignatureValue> \n      <KeyInfo> \n        <X509Data>\n<X509Certificate>MIIDGTCCAgGgAwIBAgIJAKLbLcQajEf8MA0GCSqGSIb3DQEBCwUAMCMxDDAKBgNV\nBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNvbTAeFw0xNzA0MDQwNzAwNTNaFw0z\nNzAzMzAwNzAwNTNaMCMxDDAKBgNVBAoMA0RFWDETMBEGA1UEAwwKY29yZW9zLmNv\nbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKH3dKWbRqCIZD2m3aHI\n4lfBT+u/4DECde74Ggq9WugdTucVQzDZUTaI7wzn17JM9hdPmXvaSRG9BaB1H3uO\nZCs/fmdhBERRhPvuEVfAZaFfQfR7vn7WvUzT7zwMLLB8+EHzL3fOSGM2QnCOMeUD\nAB27Pb0fuBW43NXaTD9rwfFCHvo1UP+TBJIPnV65HMeMGIrtGLt7MZTPuPm3LnYA\nfaXLf2vWSzL5nAgnJvUgceZXmyuciBfXpt8c1jIsj4y3tBoRTRqaxuaW1Eo7WMKF\na7s6KvTBKErPKuzAoIcVB4ir6jm1ticAgB72SScKtPJJdEPemTXRNNzkiw7VbpY9\nQacCAwEAAaNQME4wHQYDVR0OBBYEFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MB8GA1Ud\nIwQYMBaAFNHyGYyY2+eZ1l7ZLPZsnc3GOtj/MAwGA1UdEwQFMAMBAf8wDQYJKoZI\nhvcNAQELBQADggEBAHVXB5QmZfki9QpKzoiBNfpQ/mo6XWhExLGBTJXEWJT3P7JP\noR4Z0+85bp0fUK338s+WjyqTn0U55Jtp0B65Qxy6ythkZat/6NPp/S7gto2De6pS\nhSGygokQioVQnoYQeK0MXl2QbtrWwNiM4HC+9yohbUfjwv8yI7opwn/rjB6X/4De\noX2YzwTBJgoIXF7zMKYFF0DrKQjbTQr/a7kfNjq4930o7VhFph9Qpdv0EWM3svTd\nesSffLKbWcabtyMtCr5QyEwZiozd567oWFWZYeHQyEtd+w6tAFmz9ZslipdQEa/j\n1xUtrScuXt19sUfOgjUvA+VUNeMLDdpHUKHNW/Q=</X509Certificate>\n</X509Data> \n      </KeyInfo> \n    </Signature>\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Eric</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n  </AttackersElement>\n  <saml2:Assertion xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" ID=\"foobar\" IssueInstant=\"2017-04-04T04:34:59.330Z\" Version=\"2.0\">\n    <saml2:Issuer xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:entity\">http://www.okta.com/exk91cb99lKkKSYoy0h7</saml2:Issuer>\n    <saml2:Subject xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">eric.chiang+okta@coreos.com</saml2:NameID>\n      <saml2:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\">\n        <saml2:SubjectConfirmationData InResponseTo=\"6zmm5mguyebwvajyf2sdwwcw6m\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\" Recipient=\"http://127.0.0.1:5556/dex/callback\"/>\n      </saml2:SubjectConfirmation>\n    </saml2:Subject>\n    <saml2:Conditions xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" NotBefore=\"2017-04-04T04:29:59.330Z\" NotOnOrAfter=\"2017-04-04T04:39:59.330Z\">\n      <saml2:AudienceRestriction>\n        <saml2:Audience>http://127.0.0.1:5556/dex/callback</saml2:Audience>\n      </saml2:AudienceRestriction>\n    </saml2:Conditions>\n    <saml2:AuthnStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2017-04-04T04:34:59.330Z\" SessionIndex=\"6zmm5mguyebwvajyf2sdwwcw6m\">\n      <saml2:AuthnContext>\n        <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>\n      </saml2:AuthnContext>\n    </saml2:AuthnStatement>\n    <saml2:AttributeStatement xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">\n      <saml2:Attribute Name=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">eric.chiang+okta@coreos.com</saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"Name\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">\n\n          I AM AN ATTACKER TRYING TO GET YOU TO USE THIS ASSERTION!!!\n\n        </saml2:AttributeValue>\n      </saml2:Attribute>\n      <saml2:Attribute Name=\"groups\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified\">\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Everyone</saml2:AttributeValue>\n        <saml2:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">Admins</saml2:AttributeValue>\n      </saml2:Attribute>\n    </saml2:AttributeStatement>\n  </saml2:Assertion>\n</saml2p:Response>\n"
  },
  {
    "path": "connector/saml/types.go",
    "content": "package saml\n\nimport (\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"time\"\n)\n\nconst timeFormat = \"2006-01-02T15:04:05Z\"\n\ntype xmlTime time.Time\n\nfunc (t xmlTime) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {\n\treturn xml.Attr{\n\t\tName:  name,\n\t\tValue: time.Time(t).UTC().Format(timeFormat),\n\t}, nil\n}\n\nfunc (t *xmlTime) UnmarshalXMLAttr(attr xml.Attr) error {\n\tgot, err := time.Parse(timeFormat, attr.Value)\n\tif err != nil {\n\t\treturn err\n\t}\n\t*t = xmlTime(got)\n\treturn nil\n}\n\ntype samlVersion struct{}\n\nfunc (s samlVersion) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {\n\treturn xml.Attr{\n\t\tName:  name,\n\t\tValue: \"2.0\",\n\t}, nil\n}\n\nfunc (s *samlVersion) UnmarshalXMLAttr(attr xml.Attr) error {\n\tif attr.Value != \"2.0\" {\n\t\treturn fmt.Errorf(`saml version expected \"2.0\" got %q`, attr.Value)\n\t}\n\treturn nil\n}\n\ntype authnRequest struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol AuthnRequest\"`\n\n\tID      string      `xml:\"ID,attr\"`\n\tVersion samlVersion `xml:\"Version,attr\"`\n\n\tProviderName string  `xml:\"ProviderName,attr,omitempty\"`\n\tIssueInstant xmlTime `xml:\"IssueInstant,attr,omitempty\"`\n\tConsent      bool    `xml:\"Consent,attr,omitempty\"`\n\tDestination  string  `xml:\"Destination,attr,omitempty\"`\n\n\tForceAuthn      bool   `xml:\"ForceAuthn,attr,omitempty\"`\n\tIsPassive       bool   `xml:\"IsPassive,attr,omitempty\"`\n\tProtocolBinding string `xml:\"ProtocolBinding,attr,omitempty\"`\n\n\tAssertionConsumerServiceURL string `xml:\"AssertionConsumerServiceURL,attr,omitempty\"`\n\n\tSubject      *subject      `xml:\"Subject,omitempty\"`\n\tIssuer       *issuer       `xml:\"Issuer,omitempty\"`\n\tNameIDPolicy *nameIDPolicy `xml:\"NameIDPolicy,omitempty\"`\n\n\t// TODO(ericchiang): Make this configurable and determine appropriate default values.\n\tRequestAuthnContext *requestAuthnContext `xml:\"RequestAuthnContext,omitempty\"`\n}\n\ntype subject struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion Subject\"`\n\n\tNameID               *nameID               `xml:\"NameID,omitempty\"`\n\tSubjectConfirmations []subjectConfirmation `xml:\"SubjectConfirmation\"`\n\n\t// TODO(ericchiang): Do we need to deal with baseID?\n}\n\ntype nameID struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion NameID\"`\n\n\tFormat string `xml:\"Format,omitempty\"`\n\tValue  string `xml:\",chardata\"`\n}\n\ntype subjectConfirmationData struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmationData\"`\n\n\tNotBefore    xmlTime `xml:\"NotBefore,attr,omitempty\"`\n\tNotOnOrAfter xmlTime `xml:\"NotOnOrAfter,attr,omitempty\"`\n\tRecipient    string  `xml:\"Recipient,attr,omitempty\"`\n\tInResponseTo string  `xml:\"InResponseTo,attr,omitempty\"`\n}\n\ntype subjectConfirmation struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion SubjectConfirmation\"`\n\n\tMethod                  string                   `xml:\"Method,attr,omitempty\"`\n\tSubjectConfirmationData *subjectConfirmationData `xml:\"SubjectConfirmationData,omitempty\"`\n}\n\ntype audience struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion Audience\"`\n\tValue   string   `xml:\",chardata\"`\n}\n\ntype audienceRestriction struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion AudienceRestriction\"`\n\n\tAudiences []audience `xml:\"Audience\"`\n}\n\ntype conditions struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion Conditions\"`\n\n\tNotBefore    xmlTime `xml:\"NotBefore,attr,omitempty\"`\n\tNotOnOrAfter xmlTime `xml:\"NotOnOrAfter,attr,omitempty\"`\n\n\tAudienceRestriction []audienceRestriction `xml:\"AudienceRestriction,omitempty\"`\n}\n\ntype statusCode struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol StatusCode\"`\n\n\tValue string `xml:\"Value,attr,omitempty\"`\n}\n\ntype statusMessage struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol StatusMessage\"`\n\n\tValue string `xml:\",chardata\"`\n}\n\ntype status struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol Status\"`\n\n\tStatusCode    *statusCode    `xml:\"StatusCode\"`\n\tStatusMessage *statusMessage `xml:\"StatusMessage,omitempty\"`\n}\n\ntype issuer struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion Issuer\"`\n\tIssuer  string   `xml:\",chardata\"`\n}\n\ntype nameIDPolicy struct {\n\tXMLName     xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol NameIDPolicy\"`\n\tAllowCreate bool     `xml:\"AllowCreate,attr,omitempty\"`\n\tFormat      string   `xml:\"Format,attr,omitempty\"`\n}\n\ntype requestAuthnContext struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol RequestAuthnContext\"`\n\n\tAuthnContextClassRefs []authnContextClassRef\n}\n\ntype authnContextClassRef struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol AuthnContextClassRef\"`\n\tValue   string   `xml:\",chardata\"`\n}\n\ntype response struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:protocol Response\"`\n\n\tID           string      `xml:\"ID,attr\"`\n\tInResponseTo string      `xml:\"InResponseTo,attr\"`\n\tVersion      samlVersion `xml:\"Version,attr\"`\n\n\tDestination string `xml:\"Destination,attr,omitempty\"`\n\n\tIssuer *issuer `xml:\"Issuer,omitempty\"`\n\n\tStatus *status `xml:\"Status\"`\n\n\t// TODO(ericchiang): How do deal with multiple assertions?\n\tAssertion *assertion `xml:\"Assertion,omitempty\"`\n}\n\ntype assertion struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion Assertion\"`\n\n\tVersion       samlVersion `xml:\"Version,attr\"`\n\tID            string      `xml:\"ID,attr\"`\n\tIssueInstance xmlTime     `xml:\"IssueInstance,attr\"`\n\n\tIssuer issuer `xml:\"Issuer\"`\n\n\tSubject *subject `xml:\"Subject,omitempty\"`\n\n\tConditions *conditions `xml:\"Conditions\"`\n\n\tAttributeStatement *attributeStatement `xml:\"AttributeStatement,omitempty\"`\n}\n\ntype attributeStatement struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion AttributeStatement\"`\n\n\tAttributes []attribute `xml:\"Attribute\"`\n}\n\nfunc (a *attributeStatement) get(name string) (s string, ok bool) {\n\tfor _, attr := range a.Attributes {\n\t\tif attr.Name == name {\n\t\t\tok = true\n\t\t\tif len(attr.AttributeValues) > 0 {\n\t\t\t\treturn attr.AttributeValues[0].Value, true\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\nfunc (a *attributeStatement) all(name string) (s []string, ok bool) {\n\tfor _, attr := range a.Attributes {\n\t\tif attr.Name == name {\n\t\t\tok = true\n\t\t\tfor _, val := range attr.AttributeValues {\n\t\t\t\ts = append(s, val.Value)\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\n// names list the names of all attributes in the attribute statement.\nfunc (a *attributeStatement) names() []string {\n\ts := make([]string, len(a.Attributes))\n\n\tfor i, attr := range a.Attributes {\n\t\ts[i] = attr.Name\n\t}\n\treturn s\n}\n\n// String is a formatter for logging an attribute statement's sub statements.\nfunc (a *attributeStatement) String() string {\n\tbuff := new(bytes.Buffer)\n\tfor i, attr := range a.Attributes {\n\t\tif i != 0 {\n\t\t\tbuff.WriteString(\", \")\n\t\t}\n\t\tbuff.WriteString(attr.String())\n\t}\n\treturn buff.String()\n}\n\ntype attribute struct {\n\tXMLName xml.Name `xml:\"urn:oasis:names:tc:SAML:2.0:assertion Attribute\"`\n\n\tName string `xml:\"Name,attr\"`\n\n\tNameFormat   string `xml:\"NameFormat,attr,omitempty\"`\n\tFriendlyName string `xml:\"FriendlyName,attr,omitempty\"`\n\n\tAttributeValues []attributeValue `xml:\"AttributeValue,omitempty\"`\n}\n\ntype attributeValue struct {\n\tXMLName xml.Name `xml:\"AttributeValue\"`\n\tValue   string   `xml:\",chardata\"`\n}\n\nfunc (a attribute) String() string {\n\tif len(a.AttributeValues) == 1 {\n\t\t// \"email\" = \"jane.doe@coreos.com\"\n\t\treturn fmt.Sprintf(\"%q = %q\", a.Name, a.AttributeValues[0].Value)\n\t}\n\tvalues := make([]string, len(a.AttributeValues))\n\tfor i, av := range a.AttributeValues {\n\t\tvalues[i] = av.Value\n\t}\n\n\t// \"groups\" = [\"engineering\", \"docs\"]\n\treturn fmt.Sprintf(\"%q = %q\", a.Name, values)\n}\n"
  },
  {
    "path": "docker-compose.override.yaml.dist",
    "content": "version: \"3.8\"\n\nservices:\n    mysql:\n        ports:\n            - \"127.0.0.1:3306:3306\"\n\n    mysql8:\n        ports:\n            - \"127.0.0.1:3307:3306\"\n\n    postgres:\n        ports:\n            - \"127.0.0.1:5432:5432\"\n\n    etcd:\n        ports:\n            - \"127.0.0.1:2379:2379\"\n\n    ldap:\n        ports:\n            - \"127.0.0.1:389:389\"\n            - \"127.0.0.1:636:636\"\n"
  },
  {
    "path": "docker-compose.test.yaml",
    "content": "version: \"3.8\"\n\nservices:\n    ldap:\n        image: osixia/openldap:1.4.0\n        # Copying is required because the entrypoint modifies the *.ldif files.\n        # For verbose output, use:  command: [\"--copy-service\", \"--loglevel\", \"debug\"]\n        command: [\"--copy-service\"]\n        environment:\n            LDAP_BASE_DN: \"dc=example,dc=org\"\n            LDAP_TLS: \"true\"\n            LDAP_TLS_VERIFY_CLIENT: try\n        ports:\n            - 3890:389\n            - 6360:636\n        volumes:\n            - ./connector/ldap/testdata/certs:/container/service/slapd/assets/certs\n            - ./connector/ldap/testdata/schema.ldif:/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "# This docker-compose file provides quick setups for testing different storage backend options.\nversion: \"3.8\"\n\nservices:\n    mysql:\n        # For using percona-xtradb you need to make strict mode permissive with:\n        # docker-compose exec mysql mysql -uroot -proot -e \"SET GLOBAL pxc_strict_mode=PERMISSIVE;\"\n        # See: https://www.percona.com/doc/percona-xtradb-cluster/5.7/features/pxc-strict-mode.html\n        # image: percona/percona-xtradb-cluster:5.7\n        # image: mariadb:10.5\n        # image: mysql:5.6\n        # image: mysql:8.0\n        image: mysql:5.7\n        environment:\n            MYSQL_DATABASE: dex\n            MYSQL_USER: mysql\n            MYSQL_PASSWORD: mysql\n            MYSQL_ROOT_PASSWORD: root\n\n    mysql8:\n        image: mysql:8.0\n        command: --default-authentication-plugin=mysql_native_password\n        environment:\n            MYSQL_DATABASE: dex\n            MYSQL_USER: mysql\n            MYSQL_PASSWORD: mysql\n            MYSQL_ROOT_PASSWORD: root\n\n    postgres:\n        image: postgres:10.15\n        environment:\n            POSTGRES_DB: dex\n            POSTGRES_USER: postgres\n            POSTGRES_PASSWORD: postgres\n\n    etcd:\n        image: gcr.io/etcd-development/etcd:v3.5.0\n        environment:\n            ETCD_LISTEN_CLIENT_URLS: http://0.0.0.0:2379\n            ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379\n\n    # For testing the Kubernetes storage backend we suggest https://kind.sigs.k8s.io/:\n    # kind create cluster\n\n    ldap:\n        image: osixia/openldap:1.4.0\n        # Copying is required because the entrypoint modifies the *.ldif files.\n        # For verbose output, use:  command: [\"--copy-service\", \"--loglevel\", \"debug\"]\n        command: [\"--copy-service\"]\n        environment:\n            LDAP_BASE_DN: \"dc=example,dc=org\"\n            LDAP_TLS: \"true\"\n            LDAP_TLS_VERIFY_CLIENT: try\n        volumes:\n            - ./connector/ldap/testdata/certs:/container/service/slapd/assets/certs\n            - ./connector/ldap/testdata/schema.ldif:/container/service/slapd/assets/config/bootstrap/ldif/99-schema.ldif\n\n    vault:\n        image: hashicorp/vault:1.21\n        environment:\n            VAULT_DEV_ROOT_TOKEN_ID: root-token\n            VAULT_DEV_LISTEN_ADDRESS: \"0.0.0.0:8200\"\n        cap_add:\n            - IPC_LOCK\n        ports:\n            - 8200:8200\n\n    openbao:\n        image: quay.io/openbao/openbao:2.5\n        environment:\n            BAO_DEV_ROOT_TOKEN_ID: root-token\n            BAO_DEV_LISTEN_ADDRESS: \"0.0.0.0:8200\"\n        cap_add:\n            - IPC_LOCK\n        ports:\n            - 8210:8200\n"
  },
  {
    "path": "docs/README.md",
    "content": "These documents have moved to the [dexidp/website repo](https://github.com/dexidp/website).\n"
  },
  {
    "path": "docs/enhancements/README.md",
    "content": "# Dex Enhancement Proposal\n\n## Why do we need it?\n\nDex Enhancement Proposal (DEP) is a design document providing information to the community, or describing a new feature for Dex.\n\nWe intend DEPs to be the primary mechanisms for proposing major new features or significant changes to existing ones.\nThis will make it easier for the community to describe, track, and look through the history of changes that affected the development of the project.\n\n## Process\n\n### Before starting\n1. Search GitHub for previous [issues](https://github.com/dexidp/dex/issues), [discussions](https://github.com/dexidp/dex/discussions) and [DEPs](https://github.com/dexidp/dex/tree/master/docs/enhancements).\n2. If a discussion does not exist, [open it](https://github.com/dexidp/dex/discussions/new?category=Ideas).\n3. Ensure that writing enhancement proposal is necessary for you change by discussing it with a community.\n\n### Writing an enhancement proposal\n\n1. Fork the repo.\n2. Copy the [`docs/enhancements/_title-YYYY-MM-DD-#issue.md`](docs/enhancements/_title-YYYY-MM-DD-#issue.md) template with the appropriate\n   name.\n3. Fill all sections according to hints in them. Provide as much information as you can.\n4. Submit your PR and discuss it with the Dex team.\n"
  },
  {
    "path": "docs/enhancements/_title-YYYY-MM-DD-#issue.md",
    "content": "# Dex Enhancement Proposal (DEP) <issue#> - <YYYY-MM-DD> - <title>\n\n## Table of Contents\n\n- [Summary](#summary)\n- [Motivation](#motivation)\n    - [Goals/Pain](#goals)\n    - [Non-Goals](#non-goals)\n- [Proposal](#proposal)\n    - [User Experience](#user-experience)\n    - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)\n    - [Risks and Mitigations](#risks-and-mitigations)\n    - [Alternatives](#alternatives)\n- [Future Improvements](#future-improvements)\n\n## Summary\n\n- Provide a one-paragraph description of the expected change here.\n\n## Context\n\n- Link to any previous issues, RFCs, discussions, or briefs.\n- Link to any ongoing or future work relevant to this change.\n\n## Motivation\n\n### Goals/Pain\n\n- List work that is assumed to be done in the scope of this enhancement.\n- Mention problems solve by this enhancement.\n\n### Non-goals\n\n- List work that is entirely out of the scope of this enhancement. Use this to define DEP borders to keep work focused.\n- All planned future enhancements should be listed in one of the following blocks - Future Improvements.\n\n## Proposal\n\n### User Experience\n\n- Explain your change as if you were describing it to end-users.\n- Explain the way users are supposed to use Dex with the proposed enhancement.\n\n### Implementation Details/Notes/Constraints\n\n- Explain your change as if you were at a development team meeting (give more technical and implementation details).\n- When possible, demonstrate with pseudo-code, not text.\n- Be specific. Be opinionated. Avoid ambiguity.\n\n### Risks and Mitigations\n\n- Mention all expected risks and migrations in detail here.\n- Do not forget to mention if the proposed enhancement is a breaking change.\n\n### Alternatives\n\n- What other approaches have been considered, and why did you not choose them?\n- What happens if this enhancement will never be accepted and implemented?\n\n## Future Improvements\n\n- List any future improvements.\n"
  },
  {
    "path": "docs/enhancements/auth-sessions-2026-02-18.md",
    "content": "# Dex Enhancement Proposal (DEP 4560) - 2026-02-18 - Auth Sessions\n\n## Table of Contents\n\n- [Summary](#summary)\n- [Motivation](#motivation)\n    - [Goals/Pain](#goalspain)\n    - [Non-Goals](#non-goals)\n- [Proposal](#proposal)\n    - [User Experience](#user-experience)\n    - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)\n    - [Risks and Mitigations](#risks-and-mitigations)\n    - [Alternatives](#alternatives)\n- [Future Improvements](#future-improvements)\n\n## Summary\n\nThis DEP introduces **auth sessions** - a persistent authentication state that enables Dex to track logged-in users across browser sessions. Currently, Dex relies entirely on refresh tokens for session management, which prevents proper implementation of OIDC conformance features like `prompt=none`, `prompt=login`, `id_token_hint`, SSO across clients, and proper logout. User Sessions will be stored server-side with a browser cookie reference, enabling these features while maintaining Dex's simplicity and compatibility with all storage backends (SQL, etcd, Kubernetes CRDs).\n\n## Context\n\n- [OIDC Core 1.0 - Authentication Request](https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest) - `prompt` parameter specification\n- [OIDC Core 1.0 - ID Token Hint](https://openid.net/specs/openid-connect-core-1_0.html#IDToken) - `id_token_hint` specification\n- [OIDC Session Management 1.0](https://openid.net/specs/openid-connect-session-1_0.html) - Session management specification\n- [OIDC RP-Initiated Logout 1.0](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) - Logout specification\n- [OIDC Front-Channel Logout 1.0](https://openid.net/specs/openid-connect-frontchannel-1_0.html) - Front-channel logout\n- [Keycloak Sessions](https://www.keycloak.org/docs/latest/server_admin/#_sessions) - Reference implementation\n- [Ory Hydra Login & Consent Flow](https://www.ory.sh/docs/hydra/concepts/login) - Reference implementation\n\nCurrent limitations:\n- No support for `prompt=none` (silent authentication)\n- No support for `prompt=login` (force re-authentication)\n- No support for `max_age` parameter\n- No support for `id_token_hint` validation\n- No SSO between clients (each client requires separate login)\n- No proper logout (only refresh token revocation)\n- No consent persistence (user must approve every time if not skipped globally)\n- No 2FA enrollment storage\n- No \"Remember Me\" functionality\n\n## Motivation\n\n### Goals/Pain\n\n1. **OIDC Conformance** - Enable proper `prompt=none`, `prompt=login`, `max_age`, and `id_token_hint` support\n2. **SSO (Single Sign-On)** - Allow users to authenticate once and access multiple clients without re-login\n3. **Remember Me** - Allow users to choose persistent vs session-based authentication\n4. **Consent Persistence** - Store user consent decisions per client/scope combination within session\n5. **Proper Logout** - Enable session termination with optional front-channel logout\n6. **Foundation for 2FA** - Enable future TOTP/WebAuthn enrollment storage\n\n### Non-Goals\n\n- **2FA Implementation** - This DEP only provides storage foundation; 2FA flow is a separate DEP\n- **Back-Channel Logout** - Server-to-server logout notifications are out of scope\n- **Session Clustering/Replication** - Storage backends handle this\n- **Admin Session Management UI** - API only, no admin UI\n- **Per-connector Session Policies** - Single global session policy initially\n- **Identity Refresh During Session** - Deferred to future DEP; initially identity is refreshed only at session termination (like Keycloak)\n- **Upstream Connector Logout** - Terminating sessions at upstream IDPs is deferred\n\n## Proposal\n\n### User Experience\n\n#### Configuration\n\nSessions are controlled by a feature flag and configuration:\n\n```yaml\n# Feature flag (environment variable)\n# DEX_SESSIONS_ENABLED=true\n\n# config.yaml\nsessions:\n  # Session cookie name (default: \"dex_session\")\n  # Other cookie settings (Secure, HttpOnly, SameSite=Lax) are not configurable\n  # and are set to secure defaults automatically\n  cookieName: \"dex_session\"\n\n  # Session lifetime settings (matches refresh token expiry naming)\n  absoluteLifetime: \"24h\"         # Maximum session lifetime, default: 24h\n  validIfNotUsedFor: \"1h\"         # Session expires if not used, default: 1h\n\n  # Default SSO sharing policy for clients without explicit ssoSharedWith config\n  # Options:\n  #   \"all\" - clients without ssoSharedWith share sessions with all other clients (Keycloak-like)\n  #   \"none\" - clients without ssoSharedWith don't share sessions (default)\n  ssoSharedWithDefault: \"none\"\n\n  # Whether \"Remember Me\" checkbox is checked by default in login/approval forms\n  # When true: checkbox is pre-checked, user can uncheck\n  # When false: checkbox is unchecked, user must check to persist session (default)\n  rememberMeCheckedByDefault: false\n```\n\n**ssoSharedWithDefault** controls the default SSO behavior:\n- `\"none\"` (default): Clients without explicit `ssoSharedWith` config don't participate in SSO\n- `\"all\"`: Clients without explicit `ssoSharedWith` config share sessions with all other clients (realm-wide SSO like Keycloak)\n\nClients with explicit `ssoSharedWith` configuration always use their configured value.\n\n**Note**: The `ssoSharedWith` option is separate from the existing `trustedPeers` option. `trustedPeers` controls which clients can issue tokens on behalf of this client (existing behavior), while `ssoSharedWith` controls which clients can reuse this client's authentication session (new behavior). These can be configured independently based on different security requirements.\n\n**rememberMeCheckedByDefault** controls the initial checkbox state in templates.\nThis value is passed to templates as `.RememberMeChecked` boolean.\n\n**SSO via ssoSharedWith**: SSO between clients is controlled by the new `ssoSharedWith` configuration on clients. The `ssoSharedWith` setting defines **which clients can USE this client's session**, not which clients this client can use.\n\nIf client B is listed in client A's `ssoSharedWith`:\n1. If user logged in via client A, client B can reuse that session\n\nThis is intentionally separate from `trustedPeers` (which controls token issuance on behalf of another client). Organizations may want different policies for session sharing vs token delegation:\n- **ssoSharedWith**: \"Can this client's login be reused by another client?\"\n- **trustedPeers**: \"Can another client issue tokens claiming to be this client?\"\n\n**Wildcard Support**: `ssoSharedWith: [\"*\"]` enables SSO with all clients. This is similar to Keycloak's default behavior where all clients in a realm share sessions.\n\n**SSO Direction**: SSO sharing is **unidirectional**. Client A sharing with client B does NOT mean client B shares with client A.\n\n```yaml\nstaticClients:\n  # Public app - allows any client to reuse its sessions\n  - id: public-app\n    name: Public App\n    ssoSharedWith: [\"*\"]\n    # trustedPeers can be configured separately for token delegation\n    # ...\n\n  # Admin app - only specific apps can reuse its sessions\n  - id: admin-app\n    name: Admin App\n    ssoSharedWith: [\"monitoring-app\"]  # Only monitoring can SSO from admin sessions\n    # ...\n\n  # Secret internal service - NO other clients can reuse its sessions\n  - id: secret-service\n    name: Secret Service\n    ssoSharedWith: []  # Empty = no SSO allowed from this client's sessions\n    # But this client CAN use sessions from other clients that share with it!\n    # ...\n\n  # Monitoring app - can SSO from admin-app (because admin-app shares with it)\n  - id: monitoring-app\n    name: Monitoring App\n    ssoSharedWith: [\"admin-app\"]  # Bidirectional sharing with admin-app\n    # ...\n```\n\n**Example Scenarios:**\n\n| User logged in via | Accessing | SSO works? | Why |\n|-------------------|-----------|------------|-----|\n| public-app | admin-app | ✅ Yes | public-app has `ssoSharedWith: [\"*\"]` |\n| admin-app | public-app | ❌ No | admin-app only shares with monitoring-app |\n| admin-app | monitoring-app | ✅ Yes | admin-app shares with monitoring-app |\n| secret-service | any client | ❌ No | secret-service has `ssoSharedWith: []` |\n| public-app | secret-service | ✅ Yes | public-app has `ssoSharedWith: [\"*\"]` |\n\n**Key Insight**: A \"secret\" client that doesn't want others to SSO into it simply doesn't list them in `ssoSharedWith`. But it can still BENEFIT from SSO by being listed in OTHER clients' `ssoSharedWith`.\n\n**Comparison with Keycloak**: In Keycloak, SSO is realm-wide by default - all clients in a realm share sessions. Dex's approach is more granular: SSO is opt-in per client via `ssoSharedWith`. Use `[\"*\"]` to achieve Keycloak-like behavior.\n\n**Comparison with trustedPeers**: The `trustedPeers` option continues to control cross-client token issuance (e.g., client B issuing tokens for client A). This is a separate security concern from session sharing. Organizations can configure these independently:\n- High SSO sharing, restricted token delegation\n- Restricted SSO sharing, high token delegation\n- Or any combination based on their security model\n\n**Cookie Security**: The session cookie is always set with secure defaults:\n- `HttpOnly: true` - Not accessible via JavaScript\n- `Secure: (issuerURL.Scheme == \"https\")` - Only sent over HTTPS; for `http` (commonly used on localhost in dev) this is disabled\n- `SameSite: Lax` - CSRF protection\n- `Path: <issuerURL.Path>` - Derived from issuer URL (e.g., `/dex` for `https://example.com/dex`)\n\nThese settings are not configurable to prevent security misconfigurations.\n\n#### Authentication Flow with Sessions\n\n```\n┌─────────┐       ┌─────────┐       ┌───────────┐       ┌───────────┐\n│ Browser │       │  Dex    │       │  Storage  │       │ Connector │\n└────┬────┘       └────┬────┘       └─────┬─────┘       └─────┬─────┘\n     │                 │                  │                   │\n     │  GET /auth      │                  │                   │\n     │ (no session)    │                  │                   │\n     ├────────────────>│                  │                   │\n     │                 │                  │                   │\n     │                 │  Check session   │                   │\n     │                 │  cookie          │                   │\n     │                 ├─────────────────>│                   │\n     │                 │  (not found)     │                   │\n     │                 │<─────────────────│                   │\n     │                 │                  │                   │\n     │  Redirect to    │                  │                   │\n     │  connector      │                  │                   │\n     │<────────────────│                  │                   │\n     │                 │                  │                   │\n     │        ... connector auth flow ... │                   │\n     │                 │                  │                   │\n     │  Callback with  │                  │                   │\n     │  identity       │                  │                   │\n     ├────────────────>│                  │                   │\n     │                 │                  │                   │\n     │                 │  Create/update   │                   │\n     │                 │  AuthSession     │                   │\n     │                 │  (ALWAYS)        │                   │\n     │                 ├─────────────────>│                   │\n     │                 │                  │                   │\n     │  Set-Cookie:    │                  │                   │\n     │  - Session cookie (no MaxAge)      │                   │\n     │    if Remember Me unchecked        │                   │\n     │  - Persistent cookie (with MaxAge) │                   │\n     │    if Remember Me checked          │                   │\n     │  + redirect to /approval           │                   │\n     │<────────────────│                  │                   │\n     │                 │                  │                   │\n```\n\n**Key Point**: AuthSession is always created on successful authentication. The \"Remember Me\" checkbox only controls whether the cookie is a session cookie (deleted on browser close) or a persistent cookie (survives browser restart). This is consistent with Keycloak's behavior.\n\n#### SSO Flow (Returning User)\n\n```\n┌─────────┐       ┌─────────┐       ┌───────────┐\n│ Browser │       │  Dex    │       │  Storage  │\n└────┬────┘       └────┬────┘       └─────┬─────┘\n     │                 │                  │\n     │  GET /auth      │                  │\n     │ (with cookie)   │                  │\n     │  client_id=B    │                  │\n     ├────────────────>│                  │\n     │                 │                  │\n     │                 │  Get session     │\n     │                 ├─────────────────>│\n     │                 │  (valid session) │\n     │                 │<─────────────────│\n     │                 │                  │\n     │                 │  Check SSO       │\n     │                 │  policy for      │\n     │                 │  client B        │\n     │                 │                  │\n     │                 │  Check consent   │\n     │                 │  for client B    │\n     │                 ├─────────────────>│\n     │                 │                  │\n     │  If consented:  │                  │\n     │  redirect with  │                  │\n     │  code           │                  │\n     │<────────────────│                  │\n     │                 │                  │\n     │  If not:        │                  │\n     │  show approval  │                  │\n     │<────────────────│                  │\n     │                 │                  │\n```\n\n#### prompt=none Flow\n\n```\n┌─────────┐       ┌─────────┐       ┌───────────┐\n│ Browser │       │  Dex    │       │  Storage  │\n└────┬────┘       └────┬────┘       └─────┬─────┘\n     │                 │                  │\n     │  GET /auth      │                  │\n     │  prompt=none    │                  │\n     ├────────────────>│                  │\n     │                 │                  │\n     │                 │  Get session     │\n     │                 ├─────────────────>│\n     │                 │                  │\n     │  If valid session + consent:       │\n     │  redirect with code                │\n     │<────────────────│                  │\n     │                 │                  │\n     │  If no session or no consent:      │\n     │  redirect with error=login_required│\n     │  or error=consent_required         │\n     │<────────────────│                  │\n     │                 │                  │\n```\n\n#### Logout Flow\n\n```\n┌─────────┐       ┌─────────┐       ┌───────────┐\n│ Browser │       │  Dex    │       │  Storage  │\n└────┬────┘       └────┬────┘       └─────┬─────┘\n     │                 │                  │\n     │  GET /logout    │                  │\n     │  id_token_hint= │                  │\n     ├────────────────>│                  │\n     │                 │                  │\n     │                 │  Validate        │\n     │                 │  id_token_hint   │\n     │                 │                  │\n     │                 │  Get identity    │\n     │                 │  by session ID   │\n     │                 ├─────────────────>│\n     │                 │                  │\n     │                 │  Deactivate      │\n     │                 │  (Active=false)  │\n     │                 ├─────────────────>│\n     │                 │                  │\n     │                 │  Revoke refresh  │\n     │                 │  tokens          │\n     │                 ├─────────────────>│\n     │                 │                  │\n     │  Clear cookie + │                  │\n     │  redirect or    │                  │\n     │  show logout    │                  │\n     │  confirmation   │                  │\n     │<────────────────│                  │\n     │                 │                  │\n```\n\n### Implementation Details/Notes/Constraints\n\n#### Feature Flag\n\n```go\n// pkg/featureflags/set.go\nvar (\n    // ...existing flags...\n\n    // SessionsEnabled enables user sessions feature\n    SessionsEnabled = newFlag(\"sessions_enabled\", false)\n)\n```\n\n#### New Storage Entities\n\n\nTwo entities are required to properly handle the case where a user might be logged into different clients as different identities in the same browser:\n\n###### AuthSession\n\n```go\n// storage/storage.go\n\n// AuthSession represents a browser's authentication state.\n// One per browser, referenced by session cookie.\n// Key: SessionID (random 32-byte string, stored in cookie)\ntype AuthSession struct {\n    // ID is the session identifier stored in cookie\n    ID string\n\n    // ClientStates maps clientID → authentication state for that client\n    // Allows different users/identities per client in same browser\n    //\n    // Design note: This map-based approach is consistent with how OfflineSessions\n    // stores refresh tokens per client (OfflineSessions.Refresh map). Given that\n    // the number of OAuth clients in a typical deployment is bounded and relatively\n    // small (tens to hundreds, not thousands), the serialized size of this map\n    // will not exceed practical storage limits for any supported backend.\n    ClientStates map[string]*ClientAuthState\n\n    // CreatedAt is when this browser session started\n    CreatedAt time.Time\n\n    // LastActivity is when any client was last accessed\n    LastActivity time.Time\n\n    // IPAddress at session creation (for audit)\n    IPAddress string\n\n    // UserAgent at session creation (for audit)\n    UserAgent string\n}\n\n// ClientAuthState represents authentication state for a specific client within an auth session.\n// Expiration follows OIDC conventions with both absolute and idle timeout:\n// - ExpiresAt enforces absolute lifetime (sessions.absoluteLifetime)\n// - LastActivity + sessions.validIfNotUsedFor enforces idle timeout\n// A client state is considered expired if EITHER condition is met.\ntype ClientAuthState struct {\n    // UserID + ConnectorID identify which UserIdentity is authenticated for this client\n    UserID      string\n    ConnectorID string\n\n    // Active indicates if authentication is active for this client\n    Active bool\n\n    // ExpiresAt is the absolute expiration time for this client session.\n    // Set to time.Now() + absoluteLifetime at session creation.\n    // Cannot be extended - hard upper bound on session duration.\n    ExpiresAt time.Time\n\n    // LastActivity is when this client session was last used (token issued, SSO check, etc.)\n    // Used with validIfNotUsedFor to enforce idle timeout.\n    // Updated on each request that touches this client state.\n    LastActivity time.Time\n\n    // LastTokenIssuedAt is when a token was last issued for this client.\n    // Used for logout notifications and audit.\n    LastTokenIssuedAt time.Time\n}\n```\n\n###### UserIdentity\n\n```go\n// storage/storage.go\n\n// UserIdentity represents a user's persistent identity data.\n// Stores data that persists across sessions:\n// - Consent decisions\n// - Future: 2FA enrollment\n//\n// Key: composite of UserID + ConnectorID (one per user per connector)\ntype UserIdentity struct {\n    // UserID is the subject identifier from the connector\n    UserID string\n\n    // ConnectorID is the connector that authenticated the user\n    ConnectorID string\n\n    // Claims holds the user's identity claims\n    // Updated on:\n    // 1. Each login (from connector callback)\n    // 2. Each refresh token usage (from RefreshConnector.Refresh)\n    // This ensures claims stay in sync with OfflineSessions and upstream IDP\n    Claims Claims\n\n    // Consents stores user consent per client: map[clientID][]scopes\n    // Persists across sessions so user doesn't need to re-consent\n    Consents map[string][]string\n\n    // CreatedAt is when this identity was first created\n    CreatedAt time.Time\n\n    // LastLogin is when the user last authenticated (used for auth_time claim)\n    LastLogin time.Time\n\n    // BlockedUntil is set when user is blocked from logging in\n    BlockedUntil time.Time\n\n    // Future: 2FA fields\n    // TOTPSecret string\n    // WebAuthnCredentials []WebAuthnCredential\n}\n```\n\n**Two-Entity Design Rationale**\n\n| Entity | Purpose | Lifecycle | Key |\n|--------|---------|-----------|-----|\n| AuthSession | Browser binding, per-client auth state | Short-lived (session timeout) | SessionID (cookie) |\n| UserIdentity | User data, consents, 2FA | Long-lived (persists) | UserID + ConnectorID |\n\n**How It Works: Different Users in Different Clients**\n\n```\nAuth Session (cookie: dex_session=abc123)\n├── ClientStates[\"client-A\"]:\n│   └── UserID: \"alice\", ConnectorID: \"google\", Active: true\n├── ClientStates[\"client-B\"]:\n│   └── UserID: \"bob\", ConnectorID: \"ldap\", Active: true\n└── ClientStates[\"client-C\"]:\n    └── (empty - never authenticated)\n\nUserIdentity (alice + google):\n├── Claims: {email: alice@example.com, ...}\n├── Consents: {\"client-A\": [\"openid\", \"email\"]}\n└── LastLogin: 2024-01-01\n\nUserIdentity (bob + ldap):\n├── Claims: {email: bob@corp.com, ...}\n├── Consents: {\"client-B\": [\"openid\", \"groups\"]}\n└── LastLogin: 2024-01-02\n```\n\n**How SSO Works**\n\nWhen user accesses client-B with existing session:\n\n1. Get `AuthSession` by cookie\n2. Check `ClientStates[\"client-B\"]`:\n   - If exists and active → user already authenticated for this client\n3. If not, check SSO:\n   - Find any `ClientStates[X]` where client-X has `ssoSharedWith` containing \"client-B\"\n   - If found → SSO! Copy auth state to `ClientStates[\"client-B\"]`\n   - If not found → require authentication\n\n**SSO Session Lookup Algorithm**\n\n```go\n// findSSOSession searches for a valid SSO source session for the target client\nfunc (s *Server) findSSOSession(authSession *AuthSession, targetClientID string) (*ClientAuthState, *UserIdentity) {\n    targetClient, err := s.storage.GetClient(ctx, targetClientID)\n    if err != nil {\n        return nil, nil\n    }\n\n    // Iterate through all active client states in this browser session\n    for sourceClientID, state := range authSession.ClientStates {\n        // Skip inactive or expired states\n        if !state.Active || time.Now().After(state.ExpiresAt) {\n            continue\n        }\n\n        // Get the source client configuration\n        sourceClient, err := s.storage.GetClient(ctx, sourceClientID)\n        if err != nil {\n            continue\n        }\n\n        // Check if source client shares its session with the target client\n        // SSO is allowed if:\n        // 1. Source client has ssoSharedWith: [\"*\"] (shares with everyone)\n        // 2. Source client has targetClientID in its ssoSharedWith list\n        if !s.clientSharesSessionWith(sourceClient, targetClientID) {\n            continue\n        }\n\n        // Found a valid SSO source! Get the user identity\n        identity, err := s.storage.GetUserIdentity(ctx, state.UserID, state.ConnectorID)\n        if err != nil {\n            continue\n        }\n\n        // Check if user is not blocked\n        if identity.BlockedUntil.After(time.Now()) {\n            continue\n        }\n\n        return state, identity\n    }\n\n    return nil, nil\n}\n\n// clientSharesSessionWith checks if sourceClient shares its session with targetClientID\nfunc (s *Server) clientSharesSessionWith(sourceClient Client, targetClientID string) bool {\n    for _, peer := range sourceClient.SSOSharedWith {\n        if peer == \"*\" || peer == targetClientID {\n            return true\n        }\n    }\n    return false\n}\n```\n\n**SSO Lookup Flow Diagram**\n\n```\nUser accesses client-B with existing session\n                │\n                ▼\n┌─────────────────────────────────┐\n│ Get AuthSession from cookie     │\n└─────────────────────────────────┘\n                │\n                ▼\n┌─────────────────────────────────┐\n│ Check ClientStates[\"client-B\"]  │\n│ exists and active?              │\n└─────────────────────────────────┘\n        │               │\n       Yes              No\n        │               │\n        ▼               ▼\n┌──────────────┐  ┌─────────────────────────────────┐\n│ Use existing │  │ For each ClientStates[X]:       │\n│ session      │  │   - Is state active?            │\n└──────────────┘  │   - Get client-X config         │\n                  │   - Does client-X share with B? │\n                  │     (X.ssoSharedWith has B or *)│\n                  └─────────────────────────────────┘\n                          │               │\n                    Found match       No match\n                          │               │\n                          ▼               ▼\n                  ┌──────────────┐  ┌──────────────┐\n                  │ SSO! Copy    │  │ Require      │\n                  │ state to B   │  │ authentication│\n                  └──────────────┘  └──────────────┘\n```\n\n**Example: SSO Flow**\n\n```\n1. User logs into client-A as alice\n   AuthSession.ClientStates[\"client-A\"] = {UserID: \"alice\", Active: true}\n\n2. User accesses client-B\n   - client-A.ssoSharedWith includes \"client-B\" ✓\n   - SSO! Copy: ClientStates[\"client-B\"] = {UserID: \"alice\", Active: true}\n   - Issue tokens for alice to client-B\n```\n\n**Example: No SSO, Different User**\n\n```\n1. User logged into client-A as alice\n   AuthSession.ClientStates[\"client-A\"] = {UserID: \"alice\", Active: true}\n\n2. User accesses client-B (client-A does NOT share with client-B)\n   - No SSO available\n   - Redirect to connector for authentication\n\n3. User logs in as bob (different account)\n   AuthSession.ClientStates[\"client-B\"] = {UserID: \"bob\", Active: true}\n\nSame browser, two different users, no conflict!\n```\n\n**Claims Synchronization with Refresh Tokens**\n\nWhen a refresh token is used:\n1. `RefreshConnector.Refresh()` returns updated claims\n2. Update `OfflineSessions.ConnectorData` (existing behavior)\n3. **NEW**: Also update `UserIdentity.Claims`:\n\n```go\n// In refresh token handler\nfunc (s *Server) handleRefreshToken(...) {\n    // ...existing refresh logic...\n\n    newIdentity, err := refreshConn.Refresh(ctx, scopes, oldIdentity)\n    if err != nil {\n        // Handle refresh failure\n    }\n\n    // Update OfflineSessions (existing)\n    s.storage.UpdateOfflineSessions(...)\n\n    // Update UserIdentity claims (NEW)\n    if s.sessionsEnabled {\n        s.storage.UpdateUserIdentity(ctx, newIdentity.UserID, connectorID,\n            func(u UserIdentity) (UserIdentity, error) {\n                u.Claims = storage.Claims{\n                    UserID:    newIdentity.UserID,\n                    Username:  newIdentity.Username,\n                    Email:     newIdentity.Email,\n                    Groups:    newIdentity.Groups,\n                    // ...\n                }\n                return u, nil\n            })\n    }\n}\n```\n\nThis ensures `UserIdentity.Claims` stays synchronized with:\n- Connector's current user data\n- `OfflineSessions.ConnectorData`\n- Actual refresh token claims\n\n**Why UserIdentity instead of AuthSession?**\n\nThe name `UserIdentity` is chosen because this entity stores more than just session state:\n1. **Persistent data**: Consent decisions survive session expiration\n2. **Future 2FA**: TOTP secrets and WebAuthn credentials will be stored here\n3. **One per user/connector**: Unlike sessions which could be per-browser, this is per-identity\n\n**Session ID Regeneration**\n\nThe `AuthSession.ID` is regenerated when:\n- User logs in from a new browser (new session created)\n- Security concern requires new session (e.g., after password change)\n\nIndividual `ClientStates` can be invalidated without changing the auth session ID.\n\n**Multiple Users in Same Browser**\n\nWith the two-entity design:\n- `AuthSession` tracks which user is authenticated for which client\n- Different clients can have different users (if no SSO trust)\n- Same user can be authenticated for multiple clients (SSO or separate logins)\n\n**SSO and Different Users**\n\nWith SSO enabled between clients, the same user is used for all sharing clients:\n- User logs in to client-A as \"alice@example.com\"\n- User accesses client-B (client-A shares with client-B) → automatically authenticated as \"alice@example.com\"\n- SSO reuses the identity from the sharing client\n\nIf user needs to login as different identity to a sharing client:\n- Use `prompt=login` to force re-authentication\n- This creates new ClientState for that client with potentially different user\n\nWithout SSO, user can be different identities in different clients (see examples above).\n\n#### Storage Interface Extensions\n\nTwo new entities require CRUD operations:\n\n```go\n// storage/storage.go\n\ntype Storage interface {\n    // ...existing methods...\n\n    // AuthSession management\n    CreateAuthSession(ctx context.Context, s AuthSession) error\n    GetAuthSession(ctx context.Context, sessionID string) (AuthSession, error)\n    UpdateAuthSession(ctx context.Context, sessionID string, updater func(s AuthSession) (AuthSession, error)) error\n    DeleteAuthSession(ctx context.Context, sessionID string) error\n\n    // UserIdentity management\n    CreateUserIdentity(ctx context.Context, u UserIdentity) error\n    GetUserIdentity(ctx context.Context, userID, connectorID string) (UserIdentity, error)\n    UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u UserIdentity) (UserIdentity, error)) error\n    DeleteUserIdentity(ctx context.Context, userID, connectorID string) error\n\n    // List for admin API\n    ListUserIdentities(ctx context.Context) ([]UserIdentity, error)\n}\n```\n\n**Garbage Collection**\n\n```go\ntype GCResult struct {\n    // ...existing fields...\n    AuthSessions int64  // NEW: expired auth sessions cleaned up\n}\n```\n\n`AuthSession` objects are garbage collected when:\n- `LastActivity + validIfNotUsedFor` exceeded (inactivity)\n- All `ClientStates` have expired\n\n`UserIdentity` objects are NOT garbage collected (preserve consents, future 2FA).\n\n#### Session Expiration\n\n**AuthSession expiration:**\n- Entire session expires when `LastActivity + validIfNotUsedFor` is reached (idle timeout)\n- On expiration, `AuthSession` is deleted by GC\n- User must re-authenticate for all clients\n\n**ClientAuthState expiration (per-client within AuthSession):**\n\nEach client state enforces **both** absolute lifetime and idle timeout, consistent with standard OIDC session semantics:\n\n```go\nfunc (s *Server) isClientStateValid(state *ClientAuthState) bool {\n    now := time.Now()\n\n    // 1. Check absolute lifetime - hard upper bound, cannot be extended\n    if now.After(state.ExpiresAt) {\n        return false\n    }\n\n    // 2. Check idle timeout - session unused for too long\n    if now.After(state.LastActivity.Add(s.sessionsConfig.validIfNotUsedFor)) {\n        return false\n    }\n\n    // 3. Check explicit deactivation (admin revoked)\n    if !state.Active {\n        return false\n    }\n\n    return true\n}\n```\n\nWhen a client state expires:\n- Other clients in same auth session remain active\n- User must re-authenticate only for the expired client\n- On successful re-authentication, a new `ClientAuthState` is created with fresh `ExpiresAt`\n\n**Admin can force re-authentication:**\n- Delete `AuthSession` → user must re-auth for all clients\n- Set `ClientStates[clientID].Active = false` → user must re-auth for that client only\n\n#### Deletion Risks\n\n**Deleting AuthSession:**\n- User must re-authenticate for all clients\n- No data loss (consents preserved in UserIdentity)\n- Safe operation for logout\n\n**Deleting UserIdentity:**\n\n| What's Lost | Impact |\n|-------------|--------|\n| Consent decisions | User must re-approve scopes for all clients |\n| Future: 2FA enrollment | User must re-enroll TOTP/WebAuthn |\n\n**When to delete UserIdentity:**\n- User explicitly requests account deletion (GDPR)\n- Admin cleanup of stale identities\n- User removed from upstream identity provider\n\n**When NOT to delete (delete AuthSession instead):**\n- Regular logout - delete AuthSession or set ClientState.Active = false\n- Session expiration - GC handles AuthSession cleanup\n- Security concern - delete AuthSession to force re-auth\n\n#### Session Cookie Format\n\nThe session cookie contains only the session ID (not the session data):\n\n```\nCookie: dex_session=<session_id>; Path=<issuer_path>; Secure; HttpOnly; SameSite=Lax\n```\n\n**Cookie Path**: Derived from the issuer URL path (`issuerURL.Path`). For example:\n- Issuer: `https://dex.example.com/` → `Path=/`\n- Issuer: `https://example.com/dex` → `Path=/dex`\n\nThis is consistent with how Dex already handles routing - all endpoints are prefixed with the issuer path.\n\n**Session Creation vs Cookie Persistence (Keycloak-like behavior)**\n\nUnlike some implementations where \"Remember Me\" controls session creation, we follow Keycloak's approach:\n\n- **AuthSession is ALWAYS created** on successful authentication\n- **\"Remember Me\" controls cookie persistence**:\n  - Unchecked: Session cookie (expires when browser closes)\n  - Checked: Persistent cookie (expires at `absoluteLifetime`)\n\nThis approach is better because:\n1. SSO works within a browser session even without \"Remember Me\"\n2. Consent decisions are preserved during the browser session\n3. `prompt=none` works correctly within browser session\n4. More intuitive: \"Remember Me\" = \"remember me after I close the browser\"\n\n```go\nfunc (s *Server) setSessionCookie(w http.ResponseWriter, sessionID string, rememberMe bool) {\n    cookie := &http.Cookie{\n        Name:     s.sessionsConfig.CookieName,\n        Value:    sessionID,\n        Path:     s.issuerURL.Path,\n        HttpOnly: true,\n        Secure:   s.issuerURL.Scheme == \"https\",\n        SameSite: http.SameSiteLaxMode,\n    }\n\n    if rememberMe {\n        // Persistent cookie - survives browser restart\n        cookie.MaxAge = int(s.sessionsConfig.absoluteLifetime.Seconds())\n    }\n    // else: Session cookie - no MaxAge, browser deletes on close\n\n    http.SetCookie(w, cookie)\n}\n```\n\nSession ID generation:\n```go\nfunc NewSessionID() string {\n    return newSecureID(32) // 256-bit random value\n}\n```\n\n#### Client Configuration Extension\n\nA new client configuration field is introduced for SSO control:\n\n```go\n// storage/storage.go\n\ntype Client struct {\n    // ...existing fields...\n\n    // TrustedPeers are a list of peers which can issue tokens on this client's behalf.\n    // This is used for cross-client token issuance (existing behavior).\n    TrustedPeers []string `json:\"trustedPeers\" yaml:\"trustedPeers\"`\n\n    // SSOSharedWith defines which other clients can reuse this client's authentication session.\n    // When a user is authenticated for this client, clients listed here can skip authentication.\n    // This is separate from TrustedPeers - organizations may want different policies for\n    // session sharing vs token delegation.\n    // Special value \"*\" means share with all clients (Keycloak-like realm-wide SSO).\n    // nil means use ssoSharedWithDefault from sessions config.\n    // Empty slice [] means explicitly share with no one.\n    SSOSharedWith []string `json:\"ssoSharedWith,omitempty\" yaml:\"ssoSharedWith,omitempty\"`\n}\n```\n\n#### Connector Logout (Future)\n\nLogout URLs should be configured on connectors, not clients. A new connector interface will be added:\n\n```go\n// connector/connector.go\n\n// LogoutConnector is an optional interface for connectors that support\n// terminating upstream sessions on logout.\ntype LogoutConnector interface {\n    // Logout terminates the user's session at the upstream identity provider.\n    // Returns a URL to redirect the user to for upstream logout, or empty string\n    // if no redirect is needed.\n    Logout(ctx context.Context, connectorData []byte) (logoutURL string, err error)\n}\n```\n\nConnectors that implement this interface (e.g., OIDC with `end_session_endpoint`, SAML with SLO):\n- Are called during Dex logout flow\n- Can redirect user to upstream for complete logout\n- Implementation details are connector-specific\n\nThis is tracked as a future improvement.\n\n#### Server Configuration Extension\n\n```go\n// cmd/dex/config.go\n\ntype Sessions struct {\n    // CookieName is the session cookie name (default: \"dex_session\")\n    CookieName string `json:\"cookieName\"`\n\n    // AbsoluteLifetime is the maximum session lifetime (default: \"24h\")\n    AbsoluteLifetime string `json:\"absoluteLifetime\"`\n\n    // ValidIfNotUsedFor is the inactivity timeout (default: \"1h\")\n    ValidIfNotUsedFor string `json:\"validIfNotUsedFor\"`\n\n    // SSOSharedWithDefault is the default SSO sharing policy\n    // \"all\" = share with all clients, \"none\" = share with no one (default: \"none\")\n    SSOSharedWithDefault string `json:\"ssoSharedWithDefault\"`\n\n    // RememberMeCheckedByDefault controls the initial checkbox state in templates\n    // true = pre-checked, false = unchecked (default: false)\n    RememberMeCheckedByDefault bool `json:\"rememberMeCheckedByDefault\"`\n}\n```\n\n**Using ssoSharedWithDefault in SSO logic:**\n\n```go\nfunc (s *Server) clientSharesSessionWith(sourceClient Client, targetClientID string) bool {\n    ssoSharedWith := sourceClient.SSOSharedWith\n\n    // If client has no explicit ssoSharedWith, use default\n    if ssoSharedWith == nil {\n        switch s.sessionsConfig.SSOSharedWithDefault {\n        case \"all\":\n            return true // Share with everyone by default\n        default: // \"none\"\n            return false // Share with no one by default\n        }\n    }\n\n    // Explicit configuration: empty slice means explicitly share with no one\n    // This is different from nil (not configured)\n    if len(ssoSharedWith) == 0 {\n        return false\n    }\n\n    // Check explicit sharing list\n    for _, peer := range ssoSharedWith {\n        if peer == \"*\" || peer == targetClientID {\n            return true\n        }\n    }\n    return false\n}\n```\n\n**Three states for ssoSharedWith:**\n1. `nil` (not configured) → use `ssoSharedWithDefault`\n2. `[]` (empty slice) → explicitly share with no one\n3. `[\"client-a\", ...]` or `[\"*\"]` → explicit sharing list\n\n#### Prompt Parameter Handling\n\nDex will support the following `prompt` values per OIDC Core specification:\n- `none` - Silent authentication, no UI displayed\n- `login` - Force re-authentication\n- `consent` - Force consent screen\n- Empty (default) - Normal flow with session reuse\n\nThe `select_account` value is not supported initially (would require account linking feature).\n\n```go\nfunc (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {\n    // ...existing parsing...\n\n    prompt := r.Form.Get(\"prompt\")\n    maxAge := r.Form.Get(\"max_age\")\n    idTokenHint := r.Form.Get(\"id_token_hint\")\n    clientID := r.Form.Get(\"client_id\")\n\n    // Get auth session from cookie\n    authSession, err := s.getAuthSessionFromCookie(r)\n\n    // Get client auth state for this specific client\n    var clientState *ClientAuthState\n    var userIdentity *UserIdentity\n    if authSession != nil {\n        clientState = authSession.ClientStates[clientID]\n        if clientState != nil && clientState.Active {\n            userIdentity, _ = s.storage.GetUserIdentity(ctx, clientState.UserID, clientState.ConnectorID)\n        }\n    }\n\n    // Handle max_age parameter (OIDC Core 3.1.2.1)\n    if maxAge != \"\" && userIdentity != nil {\n        maxAgeSeconds, err := strconv.Atoi(maxAge)\n        if err == nil && maxAgeSeconds >= 0 {\n            authAge := time.Since(userIdentity.LastLogin)\n            if authAge > time.Duration(maxAgeSeconds)*time.Second {\n                // Session is too old, force re-authentication\n                clientState = nil\n                userIdentity = nil\n            }\n        }\n    }\n\n    switch prompt {\n    case \"none\":\n        // Silent authentication - must have valid session and consent\n        if clientState == nil || userIdentity == nil {\n            s.authErr(w, r, redirectURI, \"login_required\", state)\n            return\n        }\n        // Check consent in identity\n        consentedScopes, hasConsent := userIdentity.Consents[clientID]\n        if !hasConsent || !s.scopesCovered(consentedScopes, requestedScopes) {\n            s.authErr(w, r, redirectURI, \"consent_required\", state)\n            return\n        }\n        // Issue tokens without UI\n\n    case \"login\":\n        // Force re-authentication - ignore existing session for this client\n        clientState = nil\n        userIdentity = nil\n        // Continue to connector login\n\n    case \"consent\":\n        // Force consent screen even if previously consented\n        // Continue but don't check consent\n\n    default: // \"\" - normal flow\n        // Check for SSO from trusted clients if no direct session\n        if clientState == nil && authSession != nil {\n            clientState, userIdentity = s.findSSOSession(authSession, clientID)\n        }\n    }\n\n    // Validate id_token_hint if provided\n    if idTokenHint != \"\" {\n        claims, err := s.validateIDTokenHint(idTokenHint)\n        if err != nil {\n            s.authErr(w, r, redirectURI, \"invalid_request\", state)\n            return\n        }\n        if userIdentity != nil && userIdentity.UserID != claims.Subject {\n            // Identity user doesn't match hint\n            if prompt == \"none\" {\n                s.authErr(w, r, redirectURI, \"login_required\", state)\n                return\n            }\n            // Force re-login for different user\n            clientState = nil\n            userIdentity = nil\n        }\n    }\n\n    // ...continue with flow...\n}\n\n// findSSOSession looks for a valid SSO session from a sharing client\nfunc (s *Server) findSSOSession(authSession *AuthSession, targetClientID string) (*ClientAuthState, *UserIdentity) {\n    for sourceClientID, state := range authSession.ClientStates {\n        if !state.Active {\n            continue\n        }\n        sourceClient, _ := s.storage.GetClient(ctx, sourceClientID)\n        if sourceClient == nil {\n            continue\n        }\n        // Check if source client shares its session with target client\n        if s.clientSharesSessionWith(sourceClient, targetClientID) {\n            identity, _ := s.storage.GetUserIdentity(ctx, state.UserID, state.ConnectorID)\n            if identity != nil {\n                return state, identity\n            }\n        }\n    }\n    return nil, nil\n}\n```\n\n**max_age Parameter**\n\nThe `max_age` parameter is supported per OIDC Core specification:\n- Specifies the maximum authentication age in seconds\n- If the identity's last authentication time (`LastLogin`) exceeds `max_age`, force re-authentication\n- When `max_age` is used, the `auth_time` claim MUST be included in the ID token\n\n#### New Endpoints\n\n```\nPOST /logout\nGET  /logout\n```\n\nLogout endpoint following the OpenID RP-Initiated Logout specification ([OpenID spec](https://openid.net/specs/openid-connect-rpinitiated-1_0.html)):\n\n```go\nfunc (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) {\n    idTokenHint := r.FormValue(\"id_token_hint\")\n    postLogoutRedirectURI := r.FormValue(\"post_logout_redirect_uri\")\n    state := r.FormValue(\"state\")\n    clientID := r.FormValue(\"client_id\") // Optional: logout from specific client\n\n    // Get auth session from cookie\n    authSession, _ := s.getAuthSessionFromCookie(r)\n\n    // Validate id_token_hint if provided\n    var hintUserID, hintConnectorID string\n    if idTokenHint != \"\" {\n        claims, err := s.validateIDTokenHint(idTokenHint)\n        if err == nil {\n            hintUserID = claims.Subject\n            // Extract connector from token if possible\n        }\n    }\n\n    if authSession != nil {\n        if clientID != \"\" {\n            // Logout from specific client only\n            delete(authSession.ClientStates, clientID)\n            s.storage.UpdateAuthSession(ctx, authSession.ID, ...)\n        } else {\n            // Logout from all clients - delete entire auth session\n            s.storage.DeleteAuthSession(ctx, authSession.ID)\n        }\n\n        // Revoke refresh tokens for logged-out clients\n        // ...\n    }\n\n    // Clear cookie and redirect\n    s.clearSessionCookie(w)\n\n    // Show logout confirmation or redirect\n    if postLogoutRedirectURI != \"\" && s.isValidPostLogoutURI(postLogoutRedirectURI, idTokenHint) {\n        u, _ := url.Parse(postLogoutRedirectURI)\n        if state != \"\" {\n            q := u.Query()\n            q.Set(\"state\", state)\n            u.RawQuery = q.Encode()\n        }\n        http.Redirect(w, r, u.String(), http.StatusFound)\n        return\n    }\n\n    // Show logout confirmation page\n    s.templates.logout(w, r)\n}\n```\n\n**Future: Upstream Connector Logout**\n\nFor CallbackConnectors (OIDC, OAuth, SAML), the upstream identity provider may also have an active session. Future work should include:\n- Implement `LogoutConnector` interface (see above)\n- OIDC connectors use `end_session_endpoint` from discovery\n- SAML connectors use Single Logout (SLO)\n- Redirect user to upstream after Dex logout\n\nThis is tracked as a future improvement.\n\n#### Discovery Updates\n\n```go\nfunc (s *Server) constructDiscovery(ctx context.Context) discovery {\n    d := discovery{\n        // ...existing fields...\n    }\n\n    if s.sessionsEnabled {\n        d.EndSessionEndpoint = s.absURL(\"/logout\")\n    }\n\n    return d\n}\n```\n\n#### Login Template Updates\n\nWhen sessions are enabled, add \"Remember Me\" checkbox to authentication flow.\n\n**Template Data**\n\nThe server passes these values to templates:\n\n```go\ntype templateData struct {\n    // ...existing fields...\n\n    // SessionsEnabled indicates if sessions feature is active\n    SessionsEnabled bool\n\n    // RememberMeChecked is the default checkbox state\n    // Set from config: sessions.rememberMeCheckedByDefault\n    RememberMeChecked bool\n}\n```\n\n**For PasswordConnector (login form exists in Dex):**\n\n```html\n<!-- templates/password.html -->\n<form method=\"post\">\n    <!-- existing fields -->\n\n    {{ if .SessionsEnabled }}\n    <div class=\"remember-me\">\n        <input type=\"checkbox\" id=\"remember_me\" name=\"remember_me\" value=\"true\"\n               {{ if .RememberMeChecked }}checked{{ end }}>\n        <label for=\"remember_me\">Remember me</label>\n    </div>\n    {{ end }}\n\n    <button type=\"submit\">Login</button>\n</form>\n```\n\n**For CallbackConnector (no login form in Dex):**\n\nFor OAuth/OIDC/SAML connectors, the user is redirected to upstream IDP and there's no Dex login form.\n\n**Show on Approval Page** (recommended): Add \"Remember Me\" checkbox to the approval/consent page. User sees it after returning from upstream IDP, before granting consent.\n\n```html\n<!-- templates/approval.html -->\n<form method=\"post\">\n    <!-- existing scope approval fields -->\n\n    {{ if .SessionsEnabled }}\n    <div class=\"remember-me\">\n        <input type=\"checkbox\" id=\"remember_me\" name=\"remember_me\" value=\"true\"\n               {{ if .RememberMeChecked }}checked{{ end }}>\n        <label for=\"remember_me\">Remember me on this device</label>\n    </div>\n    {{ end }}\n\n    <button type=\"submit\" name=\"approval\" value=\"approve\">Grant Access</button>\n</form>\n```\n\n**When skipApprovalScreen is true**: If approval screen is skipped, the `rememberMeCheckedByDefault` config determines cookie persistence:\n- `false` (default): Session cookie (deleted on browser close)\n- `true`: Persistent cookie (survives browser restart)\n\n**Remember Me Behavior** (Keycloak-like):\n- **AuthSession is ALWAYS created** on successful authentication regardless of checkbox\n- **Checkbox controls cookie persistence only**:\n  - **Unchecked**: Session cookie - expires when browser closes. SSO works within browser session.\n  - **Checked**: Persistent cookie - survives browser restart until `absoluteLifetime` expires.\n\n#### Connector Type Considerations\n\n**CallbackConnector** (OIDC, OAuth, SAML, GitHub, etc.):\n- Session created after successful callback\n- Upstream tokens stored in refresh token's ConnectorData (not in session)\n- Identity refresh via RefreshConnector when refresh token is used\n\n**PasswordConnector** (LDAP, local passwords):\n- Session created after successful password verification\n- No upstream tokens\n- Identity refresh re-validates against password backend when refresh token is used\n\nBoth types work the same way with sessions - the connector type only affects:\n1. Initial authentication flow (redirect vs password form)\n2. How identity refresh works (via refresh tokens, not sessions)\n\n#### Connector Configuration Changes\n\nSessions reference a `ConnectorID`, but connector configuration may change after session creation (e.g., OIDC issuer URL changes, LDAP server replaced, connector removed entirely).\n\n**Behavior**: Dex does NOT automatically invalidate sessions when connector configuration changes. This is by design - Dex has no mechanism to detect configuration changes at runtime, and connectors are typically reconfigured during planned maintenance.\n\n**Administrator responsibility**: When connector configuration changes in a way that invalidates existing user identities (e.g., connector removed, upstream IdP replaced), administrators should:\n1. Terminate affected sessions via gRPC admin API (future: `DexSessions.TerminateByConnector(connectorID)`)\n2. Or wait for sessions to expire naturally\n3. Or restart Dex with `DEX_SESSIONS_ENABLED=false` temporarily to force re-authentication\n\nIf a session references a connector that no longer exists, the session will fail gracefully at the next use: `GetConnector()` will return an error, and the user will be redirected to authenticate again.\n\n### Risks and Mitigations\n\n#### Security Risks\n\n| Risk | Mitigation |\n|------|------------|\n| Session hijacking | Secure cookie flags (HttpOnly, Secure, SameSite), short idle timeout |\n| Session fixation | Generate new session ID after authentication (see below) |\n| CSRF on logout | GET shows confirmation page, POST performs logout |\n| Cookie theft | Bind session to fingerprint (IP range, partial user agent) - optional |\n| Storage exposure | Session IDs are random 256-bit values, no sensitive data in cookie |\n\n**Session Fixation Protection**\n\nSession fixation attacks occur when an attacker sets a known session ID in a victim's browser before authentication, then hijacks the session after the victim logs in.\n\nReferences:\n- [OWASP Session Fixation](https://owasp.org/www-community/attacks/Session_fixation)\n- [OWASP Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)\n\n**Mitigations implemented:**\n\n1. **Regenerate session ID on authentication**: When a user successfully authenticates, ALWAYS generate a new `AuthSession.ID` even if a session already exists. Never reuse a pre-authentication session ID.\n\n```go\n// This is not the real method signature, but the implementation example of a specific behavior.\nfunc (s *Server) onSuccessfulAuthentication(w http.ResponseWriter, userID, connectorID, clientID string, rememberMe bool) {\n    // ALWAYS generate new session ID - prevents session fixation\n    newSessionID := NewSessionID()\n\n    // Create or update AuthSession with NEW ID\n    authSession := &AuthSession{\n        ID:           newSessionID,  // Always new, never reuse\n        ClientStates: make(map[string]*ClientAuthState),\n        CreatedAt:    time.Now(),\n        // ...\n    }\n\n    // Set cookie with new session ID\n    s.setSessionCookie(w, newSessionID, rememberMe)\n}\n```\n\n2. **Don't accept session IDs from URL parameters**: Session IDs are ONLY accepted from cookies, never from query parameters or POST data.\n\n3. **Strict cookie settings**: `HttpOnly`, `Secure`, `SameSite=Lax` prevent common session theft vectors.\n\n4. **Session binding (optional future enhancement)**: Bind session to client characteristics (IP range, user agent) to detect stolen cookies.\n\n**Handling existing sessions during authentication:**\n\nWhen a user authenticates and an existing `AuthSession` is found:\n1. Generate a completely new session ID\n2. Copy relevant state from old session to new session (if any)\n3. Delete the old `AuthSession` from storage\n4. Set cookie with new session ID\n\nThis ensures that even if an attacker set a session cookie before authentication, they cannot use it after the victim logs in.\n\n#### Operational Risks\n\n| Risk | Mitigation |\n|------|------------|\n| Storage growth | AuthSessions are GC'd on inactivity; UserIdentities are per-user like OfflineSessions; admin API allows cleanup |\n| Storage performance | Additional read per request to resolve session cookie. Impact depends on backend — see note below |\n| Migration complexity | Feature flag allows gradual rollout, no breaking changes |\n\n**Storage Performance Note**\n\nEnabling sessions introduces an additional storage read on each authorization request (to resolve the session cookie to an `AuthSession`). The actual performance impact depends on the storage backend:\n\n- **SQL (Postgres, MySQL, SQLite)**: Session lookup by primary key is a single indexed read — negligible overhead\n- **etcd**: Single key-value lookup — negligible overhead\n- **Kubernetes CRDs**: GET by resource name — slightly higher latency than SQL/etcd but still within acceptable bounds (may require [priority&fairness](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/) tuning)\n- **Memory**: In-process map lookup — no overhead\n\nAt this stage, we do not have production metrics to quantify the exact impact. The storage access pattern is identical to existing `OfflineSessions` lookups (single record by key), which are already proven in production. It is recommended to monitor storage latency after enabling sessions and adjusting `validIfNotUsedFor` if the GC frequency needs tuning.\n\n#### Breaking Changes\n\n**None** - Sessions are opt-in via feature flag and configuration. Existing deployments continue to work without changes.\n\n#### Rollback Plan\n\nSessions are fully controlled by the `DEX_SESSIONS_ENABLED` feature flag. Rollback is straightforward:\n\n1. **Disable feature flag**: Set `DEX_SESSIONS_ENABLED=false` (or remove it)\n2. **Immediate effect**: Dex stops creating, reading, and validating sessions. All authorization requests proceed as before sessions were introduced — connector authentication on every request, no SSO, no session cookies\n3. **Cookie cleanup**: Existing session cookies in browsers become inert — Dex ignores them when sessions are disabled. They expire naturally per their MaxAge or when the browser is closed\n4. **Storage cleanup**: `AuthSession` and `UserIdentity` records remain in storage but are unused. They can be cleaned up manually or left to accumulate no further growth\n5. **No downtime required**: Feature flag can be toggled without restart if environment variable reload is supported; otherwise, a rolling restart is sufficient\n\n**Key guarantee**: Disabling the feature flag returns Dex to its pre-sessions behavior with zero side effects. No existing functionality (refresh tokens, connector authentication, token issuance) depends on sessions. Additional tables in the database cost nothing when the feature flag is disabled: they remain unused schema objects and can be deleted later if desired.\n\n#### Migration Path\n\n1. Deploy new Dex version - storage migrations create `AuthSession` and `UserIdentity` tables/resources automatically (no feature flag needed for schema)\n2. Enable feature flag `DEX_SESSIONS_ENABLED=true` when ready to use sessions\n3. Add `sessions:` configuration block\n4. Sessions start being created for all new logins; \"Remember Me\" controls cookie persistence (session vs persistent cookie)\n5. Existing refresh tokens continue to work\n\n**Note**: Storage schema changes (new tables/CRDs) are applied on startup regardless of feature flag. The feature flag only controls whether sessions are actually created and used. This simplifies deployment - you can deploy the new version, then enable sessions later without another deployment.\n\n### Alternatives\n\n#### 1. Stateless Sessions (JWT in Cookie)\n\n**Approach**: Store session data directly in a signed/encrypted JWT cookie.\n\n**Pros**:\n- No server-side storage required\n- Scales horizontally without shared state\n\n**Cons**:\n- Cannot revoke sessions without blocklist\n- Cookie size limits (~4KB)\n- Cannot store consent history or client tracking for logout\n- No server-side session list for logout\n\n**Decision**: Rejected. Server-side sessions are required for proper logout and SSO.\n\n#### 2. Extend OfflineSessions\n\n**Approach**: Add session data to existing OfflineSessions entity.\n\n**Pros**:\n- Reuses existing storage\n- Simpler migration\n\n**Cons**:\n- OfflineSessions are per-connector, not per-browser\n- Different lifecycle (refresh token vs browser session)\n- Would complicate existing OfflineSessions logic\n\n**Decision**: Rejected. Clean separation is better for maintainability.\n\n#### 3. External Session Store (Redis)\n\n**Approach**: Use Redis for session storage instead of existing backends.\n\n**Pros**:\n- Built-in TTL support\n- Fast reads/writes\n- Proven session store\n\n**Cons**:\n- Adds infrastructure dependency\n- Against Dex's simplicity philosophy\n- Doesn't work with Kubernetes CRD backend\n\n**Decision**: Rejected. Must work with existing storage backends.\n\n#### 4. Do Nothing\n\n**Approach**: Keep using refresh tokens as implicit sessions.\n\n**Cons**:\n- Cannot implement OIDC conformance features\n- No proper SSO\n- No proper logout\n- Blocks future features (2FA, etc.)\n\n**Decision**: Rejected. These features are essential for enterprise adoption.\n\n## Future Improvements\n\n1. **Identity Refresh for Long-Lived Sessions**\n   - Periodic refresh of user identity from connector during active session\n   - Configurable refresh interval\n   - Refresh on token request option\n   - Handle connector revocation (terminate session)\n\n2. **Upstream Connector Logout**\n   - Redirect to upstream IDP logout endpoint after Dex logout\n   - Support RP-Initiated Logout towards upstream OIDC providers\n   - SAML Single Logout (SLO) support\n   - Configurable per-connector logout URLs\n\n3. **Session Introspection Endpoint**\n   - Implement session check endpoint similar to [RFC 7662 Token Introspection](https://datatracker.ietf.org/doc/html/rfc7662)\n   - Could enable replacing OAuth2 Proxy in some deployments\n   - Endpoint: `GET /session/introspect` or similar\n   - Returns session validity and user claims\n   - Useful for reverse proxies to validate session cookies directly\n\n4. **Front-Channel Logout**\n   - Implement [OIDC Front-Channel Logout 1.0](https://openid.net/specs/openid-connect-frontchannel-1_0.html)\n   - Notify client applications when user logs out via iframes\n   - Requires client `logoutURL` configuration\n\n5. **2FA/MFA Support**\n   - Store TOTP secrets in user profile\n   - Add MFA enrollment flow\n   - Step-up authentication for sensitive operations\n   - WebAuthn/Passkey support\n\n6. **Session Management API**\n   - List active sessions via gRPC API\n   - Revoke sessions via gRPC API\n   - Session activity audit log\n\n7. **Back-Channel Logout**\n   - Implement [OIDC Back-Channel Logout](https://openid.net/specs/openid-connect-backchannel-1_0.html)\n   - Server-to-server logout notifications\n\n8. **Account Linking**\n   - Link multiple connector identities to single user\n   - Switch between linked identities\n\n9. **Device/Session Fingerprinting**\n   - Optional session binding to client characteristics\n   - Anomaly detection for session theft\n\n10. **Per-Connector Session Policies**\n    - Different session lifetimes per connector\n    - Different SSO policies per connector\n\n11. **Session Impersonation for Admin**\n    - Admin can impersonate user sessions for debugging\n    - Audit logging for impersonation\n\n12. **Consent Management UI**\n    - User-facing page to view/revoke consents\n    - GDPR compliance features\n\n"
  },
  {
    "path": "docs/enhancements/cel-expressions-2026-02-28.md",
    "content": "# Dex Enhancement Proposal (DEP) - 2026-02-28 - CEL (Common Expression Language) Integration\n\n## Table of Contents\n\n- [Summary](#summary)\n- [Context](#context)\n- [Motivation](#motivation)\n    - [Goals/Pain](#goalspain)\n    - [Non-Goals](#non-goals)\n- [Proposal](#proposal)\n    - [User Experience](#user-experience)\n    - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)\n        - [Phase 1: pkg/cel - Core CEL Library](#phase-1-pkgcel---core-cel-library)\n        - [Phase 2: Authentication Policies](#phase-2-authentication-policies)\n        - [Phase 3: Token Policies](#phase-3-token-policies)\n        - [Phase 4: OIDC Connector Claim Mapping](#phase-4-oidc-connector-claim-mapping)\n    - [Policy Application Flow](#policy-application-flow)\n    - [Risks and Mitigations](#risks-and-mitigations)\n    - [Alternatives](#alternatives)\n- [Future Improvements](#future-improvements)\n\n## Summary\n\nThis DEP proposes integrating [CEL (Common Expression Language)][cel-spec] into Dex as a first-class\nexpression engine for policy evaluation, claim mapping, and token customization. A new reusable\n`pkg/cel` package will provide a safe, sandboxed CEL environment with Kubernetes-grade compatibility\nguarantees, cost budgets, and a curated set of extension libraries. Subsequent phases will leverage\nthis package to implement authentication policies, token policies, advanced claim mapping in\nconnectors, and per-client/global access rules — replacing the need for ad-hoc configuration fields\nand external policy engines.\n\n[cel-spec]: https://github.com/google/cel-spec\n\n## Context\n\n- [#1583 Add allowedGroups option for clients config][#1583] — a long-standing request for a\n  configuration option to allow a client to specify a list of allowed groups.\n- [#1635 Connector Middleware][#1635] — long-standing request for a policy/middleware layer between\n  connectors and the server for claim transformations and access control.\n- [#1052 Allow restricting connectors per client][#1052] — frequently requested feature to restrict\n  which connectors are available to specific OAuth2 clients.\n- [#2178 Custom claims in ID tokens][#2178] — requests for including additional payload in issued tokens.\n- [#2812 Token Exchange DEP][dep-token-exchange] — mentions CEL/Rego as future improvement for\n  policy-based assertions on exchanged tokens.\n- The OIDC connector already has a growing set of ad-hoc claim mutation options\n  (`ClaimMapping`, `ClaimMutations.NewGroupFromClaims`, `FilterGroupClaims`, `ModifyGroupNames`)\n  that would benefit from a unified expression language.\n- Previous community discussions explored OPA/Rego and JMESPath, but CEL offers a better fit\n  (see [Alternatives](#alternatives)).\n\n[#1583]: https://github.com/dexidp/dex/pull/1583\n[#1635]: https://github.com/dexidp/dex/issues/1635\n[#1052]: https://github.com/dexidp/dex/issues/1052\n[#2178]: https://github.com/dexidp/dex/issues/2178\n[dep-token-exchange]: /docs/enhancements/token-exchange-2023-02-03-%232812.md\n\n## Motivation\n\n### Goals/Pain\n\n1. **Complex query/filter capabilities** — Dex needs a way to express complex validations and\n   mutations in multiple places (authentication flow, token issuance, claim mapping). Today each\n   feature requires new Go code, new config fields, and a new release cycle. CEL allows operators\n   to express these rules declaratively without code changes.\n\n2. **Authentication policies** — Operators want to control _who_ can log in based on rich\n   conditions: restrict specific connectors to specific clients, require group membership for\n   certain clients, deny login based on email domain, enforce MFA claims, etc. Currently there is\n   no unified mechanism; users rely on downstream applications or external proxies.\n\n3. **Token policies** — Operators want to customize issued tokens: add extra claims to ID tokens,\n   restrict scopes per client, modify `aud` claims, include upstream connector metadata, etc.\n   Today this requires forking Dex or using a reverse proxy.\n\n4. **Claim mapping in OIDC connector** — The OIDC connector has accumulated multiple ad-hoc config\n   options for claim mapping and group mutations (`ClaimMapping`, `NewGroupFromClaims`,\n   `FilterGroupClaims`, `ModifyGroupNames`). A single CEL expression field would replace all of\n   these with a more powerful and composable approach.\n\n5. **Per-client and global policies** — One of the most frequent requests is allowing different\n   connectors for different clients and restricting group-based access per client. CEL policies at\n   the global and per-client level address this cleanly.\n\n6. **CNCF ecosystem alignment** — CEL has massive adoption across the CNCF ecosystem:\n\n   | Project                   | CEL Usage                                                                                                                                                  | Evidence |\n   |---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|\n   | **Kubernetes**            | ValidatingAdmissionPolicy, CRD validation rules (`x-kubernetes-validations`), AuthorizationPolicy, field selectors, CEL-based match conditions in webhooks | [KEP-3488][k8s-cel-kep], [CRD Validation Rules][k8s-crd-cel], [AuthorizationPolicy KEP-3221][k8s-authz-cel] |\n   | **Kyverno**               | CEL expressions in validation/mutation policies (v1.12+), preconditions                                                                                    | [Kyverno CEL docs][kyverno-cel] |\n   | **OPA Gatekeeper**        | Partially added support for CEL in constraint templates                                                                                                    | [Gatekeeper CEL][gatekeeper-cel] |\n   | **Istio**                 | AuthorizationPolicy conditions, request routing, telemetry                                                                                                 | [Istio CEL docs][istio-cel] |\n   | **Envoy / Envoy Gateway** | RBAC filter, ext_authz, rate limiting, route matching, access logging                                                                                      | [Envoy CEL docs][envoy-cel] |\n   | **Tekton**                | Pipeline when expressions, CEL custom tasks                                                                                                                | [Tekton CEL Interceptor][tekton-cel] |\n   | **Knative**               | Trigger filters using CEL expressions                                                                                                                      | [Knative CEL filters][knative-cel] |\n   | **Google Cloud**          | IAM Conditions, Cloud Deploy, Security Command Center                                                                                                      | [Google IAM CEL][gcp-cel] |\n   | **Cert-Manager**          | CertificateRequestPolicy approval using CEL                                                                                                                | [cert-manager approver-policy CEL][cert-manager-cel] |\n   | **Cilium**                | Hubble CEL filter logic                                                                                                                                    | [Cilium CEL docs][cilium-cel] |\n   | **Crossplane**            | Composition functions with CEL-based patch transforms                                                                                                      | [Crossplane CEL transforms][crossplane-cel] |\n   | **Kube-OVN**              | Network policy extensions using CEL                                                                                                                        | [Kube-OVN CEL][kube-ovn-cel] |\n\n   [k8s-cel-kep]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3488-cel-admission-control\n   [k8s-crd-cel]: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules\n   [k8s-authz-cel]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-auth/3221-structured-authorization-configuration\n   [kyverno-cel]: https://kyverno.io/docs/writing-policies/cel/\n   [gatekeeper-cel]: https://open-policy-agent.github.io/gatekeeper/website/docs/validating-admission-policy/#policy-updates-to-add-vap-cel\n   [istio-cel]: https://istio.io/latest/docs/reference/config/security/conditions/\n   [envoy-cel]: https://www.envoyproxy.io/docs/envoy/latest/xds/type/v3/cel.proto\n   [tekton-cel]: https://tekton.dev/docs/triggers/cel_expressions/\n   [knative-cel]: https://github.com/knative/eventing/blob/main/docs/broker/filtering.md#add-cel-expression-filter\n   [gcp-cel]: https://cloud.google.com/iam/docs/conditions-overview\n   [cert-manager-cel]: https://cert-manager.io/docs/policy/approval/approver-policy/#validations\n   [cilium-cel]: https://docs.cilium.io/en/stable/_api/v1/flow/README/#flowfilter-experimental\n   [crossplane-cel]: https://github.com/crossplane-contrib/function-cel-filter\n   [kube-ovn-cel]: https://kubeovn.github.io/docs/stable/en/advance/cel-expression/\n\n   By choosing CEL, Dex operators who already use Kubernetes or other CNCF tools can reuse their\n   existing knowledge of the expression language.\n\n### Non-Goals\n\n- **Full policy engine** — This DEP does not aim to replace dedicated external policy engines\n  (OPA, Kyverno). CEL in Dex is scoped to identity and token operations.\n- **Breaking changes to existing configuration** — All existing config fields (`ClaimMapping`,\n  `ClaimMutations`, etc.) will continue to work. CEL expressions are additive/opt-in.\n- **Authorization (beyond Dex scope)** — Dex is an identity provider; downstream authorization\n  decisions remain the responsibility of relying parties. CEL policies in Dex are limited to\n  authentication and token issuance concerns.\n- **Multi-phase CEL in a single DEP** — Only Phase 1 (`pkg/cel` package) is targeted for\n  immediate implementation. Phases 2-4 are included here for design context and will have their\n  own implementation PRs.\n- **Multi-step logic** — CEL in Dex is scoped to single-expression evaluation. Each expression\n  is a standalone, stateless computation with no intermediate variables, chaining, or\n  multi-step transformations. If a use case requires sequential logic or conditionally chained\n  expressions, it belongs outside Dex (e.g. in an external policy engine or middleware).\n  This boundary protects the design from scope creep that pushes CEL beyond what it's good at.\n\n## Proposal\n\n### User Experience\n\n#### Authentication Policy (Phase 2)\n\nOperators can define global and per-client authentication policies in the Dex config:\n\n```yaml\n# Global authentication policy — each expression evaluates to bool.\n# If true — the request is denied. Evaluated in order; first match wins.\nauthPolicy:\n  - expression: \"!identity.email.endsWith('@example.com')\"\n    message: \"'Login restricted to example.com domain'\"\n  - expression: \"!identity.email_verified\"\n    message: \"'Email must be verified'\"\n\nstaticClients:\n  - id: admin-app\n    name: Admin Application\n    secret: ...\n    redirectURIs: [...]\n    # Per-client policy — same structure as global\n    authPolicy:\n      - expression: \"!(request.connector_id in ['okta', 'ldap'])\"\n        message: \"'This application requires Okta or LDAP login'\"\n      - expression: \"!('admin' in identity.groups)\"\n        message: \"'Admin group membership required'\"\n```\n\n#### Token Policy (Phase 3)\n\nOperators can add extra claims or mutate token contents:\n\n```yaml\ntokenPolicy:\n  # Global mutations applied to all ID tokens\n  claims:\n    # Add a custom claim based on group membership\n    - key: \"'role'\"\n      value: \"identity.groups.exists(g, g == 'admin') ? 'admin' : 'user'\"\n    # Include connector ID as a claim\n    - key: \"'idp'\"\n      value: \"request.connector_id\"\n    # Add department from upstream claims (only if present)\n    - key: \"'department'\"\n      value: \"identity.extra['department']\"\n      condition: \"'department' in identity.extra\"\n\nstaticClients:\n  - id: internal-api\n    name: Internal API\n    secret: ...\n    redirectURIs: [...]\n    tokenPolicy:\n      claims:\n        - key: \"'custom-claim.company.com/team'\"\n          value: \"identity.extra['team'].orValue('engineering')\"\n        # Only add on-call claim for ops group members\n        - key: \"'on_call'\"\n          value: \"true\"\n          condition: \"identity.groups.exists(g, g == 'ops')\"\n      # Restrict scopes\n      filter:\n        expression: \"request.scopes.all(s, s in ['openid', 'email', 'profile'])\"\n        message: \"'Unsupported scope requested'\"\n```\n\n#### OIDC Connector Claim Mapping (Phase 4)\n\nReplace ad-hoc claim mapping with CEL:\n\n```yaml\nconnectors:\n  - type: oidc\n    id: corporate-idp\n    name: Corporate IdP\n    config:\n      issuer: https://idp.example.com\n      clientID: dex-client\n      clientSecret: ...\n      # CEL-based claim mapping — replaces claimMapping and claimModifications\n      claimMappingExpressions:\n        username: \"claims.preferred_username.orValue(claims.email)\"\n        email: \"claims.email\"\n        groups: >\n          claims.groups\n            .filter(g, g.startsWith('dex:'))\n            .map(g, g.trimPrefix('dex:'))\n        emailVerified: \"claims.email_verified.orValue(true)\"\n        # Extra claims to pass through to token policies\n        extra:\n          department: \"claims.department.orValue('unknown')\"\n          cost_center: \"claims.cost_center.orValue('')\"\n```\n\n### Implementation Details/Notes/Constraints\n\n### Phase 1: `pkg/cel` — Core CEL Library\n\nThis is the foundation that all subsequent phases build upon. The package provides a safe,\nreusable CEL environment with Kubernetes-grade guarantees.\n\n#### Package Structure\n\n```\npkg/\n  cel/\n    cel.go              # Core Environment, compilation, evaluation\n    types.go            # CEL type declarations (Identity, Request, etc.)\n    cost.go             # Cost estimation and budgeting\n    doc.go              # Package documentation\n    library/\n      email.go          # Email-related CEL functions\n      groups.go         # Group-related CEL functions\n```\n\n#### Dependencies\n\n```\ngithub.com/google/cel-go v0.27.0\n```\n\nThe `cel-go` library is the canonical Go implementation maintained by Google, used by Kubernetes\nand all major CNCF projects. It follows semantic versioning and provides strong backward\ncompatibility guarantees.\n\n#### Core API Design\n\n**Public types:**\n\n```go\n// CompilationResult holds a compiled CEL program ready for evaluation.\ntype CompilationResult struct {\n    Program    cel.Program\n    OutputType *cel.Type\n    Expression string\n}\n\n// Compiler compiles CEL expressions against a specific environment.\ntype Compiler struct { /* ... */ }\n\n// CompilerOption configures a Compiler.\ntype CompilerOption func(*compilerConfig)\n```\n\n**Compilation pipeline:**\n\nEach `Compile*` call performs these steps sequentially:\n1. Reject expressions exceeding `MaxExpressionLength` (10,240 chars).\n2. Compile and type-check the expression via `cel-go`.\n3. Validate output type matches the expected type (for typed variants).\n4. Estimate cost using `defaultCostEstimator` with size hints — reject if estimated max cost\n   exceeds the cost budget.\n5. Create an optimized `cel.Program` with runtime cost limit.\n\nPresence tests (`has(field)`, `'key' in map`) have zero cost, matching Kubernetes CEL behavior.\n\n#### Variable Declarations\n\nVariables are declared via `VariableDeclaration{Name, Type}` and registered with `NewCompiler`.\nHelper constructors provide pre-defined variable sets:\n\n**`IdentityVariables()`** — the `identity` variable (from `connector.Identity`),\ntyped as `cel.ObjectType`:\n\n| Field | CEL Type | Source |\n|-------|----------|--------|\n| `identity.user_id` | `string` | `connector.Identity.UserID` |\n| `identity.username` | `string` | `connector.Identity.Username` |\n| `identity.preferred_username` | `string` | `connector.Identity.PreferredUsername` |\n| `identity.email` | `string` | `connector.Identity.Email` |\n| `identity.email_verified` | `bool` | `connector.Identity.EmailVerified` |\n| `identity.groups` | `list(string)` | `connector.Identity.Groups` |\n\n**`RequestVariables()`** — the `request` variable (from `RequestContext`),\ntyped as `cel.ObjectType`:\n\n| Field | CEL Type |\n|-------|----------|\n| `request.client_id` | `string` |\n| `request.connector_id` | `string` |\n| `request.scopes` | `list(string)` |\n| `request.redirect_uri` | `string` |\n\n**`ClaimsVariable()`** — the `claims` variable for raw upstream claims as `map(string, dyn)`.\n\n**Typing strategy:**\n\n`identity` and `request` use `cel.ObjectType` with explicitly declared fields. This gives\ncompile-time type checking: a typo like `identity.emial` is rejected at config load time\nrather than silently evaluating to null in production — critical for an auth system where a\nmisconfigured policy could lock users out.\n\n`claims` remains `map(string, dyn)` because its shape is genuinely unknown — it carries\narbitrary upstream IdP data.\n\n#### Compatibility Guarantees\n\nFollowing the Kubernetes CEL compatibility model\n([KEP-3488: CEL for Admission Control][kep-3488], [Kubernetes CEL Migration Guide][k8s-cel-compat]):\n\n1. **Environment versioning** — The CEL environment is versioned. When new functions or variables\n   are added, they are introduced under a new environment version. Existing expressions compiled\n   against an older version continue to work.\n\n   ```go\n   // EnvironmentVersion represents the version of the CEL environment.\n   // New variables, functions, or libraries are introduced in new versions.\n   type EnvironmentVersion uint32\n\n   const (\n       // EnvironmentV1 is the initial CEL environment.\n       EnvironmentV1 EnvironmentVersion = 1\n   )\n\n   // WithVersion sets the target environment version for the compiler.\n   func WithVersion(v EnvironmentVersion) CompilerOption\n   ```\n\n   This is directly modeled on `k8s.io/apiserver/pkg/cel/environment`.\n\n2. **Library stability** — Custom functions in the `pkg/cel/library` subpackage follow these rules:\n   - Functions MUST NOT be removed once released.\n   - Function signatures MUST NOT change once released.\n   - New functions MUST be added under a new `EnvironmentVersion`.\n   - If a function needs to be replaced, the old one is deprecated but kept forever.\n\n3. **Type stability** — CEL types (`Identity`, `Request`, `Claims`) follow the same rules:\n   - Fields MUST NOT be removed.\n   - Field types MUST NOT change.\n   - New fields are added in a new `EnvironmentVersion`.\n\n4. **Semantic versioning of `cel-go`** — The `cel-go` dependency follows semver. Dex pins to a\n   minor version range and updates are tested for behavioral changes. This is exactly the approach\n   Kubernetes takes: `k8s.io/apiextensions-apiserver` pins `cel-go` and gates new features behind\n   environment versions.\n\n5. **Feature gates** — New CEL-powered features are gated behind Dex feature flags (using the\n   existing `pkg/featureflags` mechanism) during their alpha phase.\n\n[kep-3488]: https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/3488-cel-admission-control\n[k8s-cel-compat]: https://kubernetes.io/docs/reference/using-api/cel/\n\n#### Cost Estimation and Budgets\n\nLike Kubernetes, Dex CEL expressions must be bounded to prevent denial-of-service.\n\n**Constants:**\n\n| Constant | Value | Description |\n|----------|-------|-------------|\n| `DefaultCostBudget` | `10_000_000` | Max cost units per evaluation (aligned with Kubernetes) |\n| `MaxExpressionLength` | `10_240` | Max expression string length in characters |\n| `DefaultStringMaxLength` | `256` | Estimated max string size for cost estimation |\n| `DefaultListMaxLength` | `100` | Estimated max list size for cost estimation |\n\n**How it works:**\n\nA `defaultCostEstimator` (implementing `checker.CostEstimator`) provides size hints for known\nvariables (`identity`, `request`, `claims`) so the `cel-go` cost estimator doesn't assume\nunbounded sizes. It also provides call cost estimates for custom Dex functions\n(`dex.emailDomain`, `dex.emailLocalPart`, `dex.groupMatches`, `dex.groupFilter`).\n\nExpressions are validated at three levels:\n1. **Length check** — reject expressions exceeding `MaxExpressionLength`.\n2. **Compile-time cost estimation** — reject expressions whose estimated max cost exceeds\n   the cost budget.\n3. **Runtime cost limit** — abort evaluation if actual cost exceeds the budget.\n\n#### Extension Libraries\n\nThe `pkg/cel` environment includes these cel-go standard extensions (same set as Kubernetes):\n\n| Library | Description | Examples |\n|---------|-------------|---------|\n| `ext.Strings()` | Extended string functions | `\"hello\".upperAscii()`, `\"foo:bar\".split(':')`, `s.trim()`, `s.replace('a','b')` |\n| `ext.Encoders()` | Base64 encoding/decoding | `base64.encode(bytes)`, `base64.decode(str)` |\n| `ext.Lists()` | Extended list functions | `list.slice(1, 3)`, `list.flatten()` |\n| `ext.Sets()` | Set operations on lists | `sets.contains(a, b)`, `sets.intersects(a, b)`, `sets.equivalent(a, b)` |\n| `ext.Math()` | Math functions | `math.greatest(a, b)`, `math.least(a, b)` |\n\nPlus custom Dex libraries in the `pkg/cel/library` subpackage, each implementing the\n`cel.Library` interface:\n\n**`library.Email`** — email-related helpers:\n\n| Function | Signature | Description |\n|----------|-----------|-------------|\n| `dex.emailDomain` | `(string) -> string` | Returns the domain portion of an email address. `dex.emailDomain(\"user@example.com\") == \"example.com\"` |\n| `dex.emailLocalPart` | `(string) -> string` | Returns the local part of an email address. `dex.emailLocalPart(\"user@example.com\") == \"user\"` |\n\n**`library.Groups`** — group-related helpers:\n\n| Function | Signature | Description |\n|----------|-----------|-------------|\n| `dex.groupMatches` | `(list(string), string) -> list(string)` | Returns groups matching a glob pattern. `dex.groupMatches(identity.groups, \"team:*\")` |\n| `dex.groupFilter` | `(list(string), list(string)) -> list(string)` | Returns only groups present in the allowed list. `dex.groupFilter(identity.groups, [\"admin\", \"ops\"])` |\n\n#### Example: Compile and Evaluate\n\n```go\n// 1. Create a compiler with identity and request variables\ncompiler, _ := cel.NewCompiler(\n    append(cel.IdentityVariables(), cel.RequestVariables()...),\n)\n\n// 2. Compile a policy expression (type-checked, cost-estimated)\nprog, _ := compiler.CompileBool(\n    `identity.email.endsWith('@example.com') && 'admin' in identity.groups`,\n)\n\n// 3. Evaluate against real data\nresult, _ := cel.EvalBool(ctx, prog, map[string]any{\n    \"identity\": cel.IdentityFromConnector(connectorIdentity),\n    \"request\":  cel.RequestFromContext(cel.RequestContext{...}),\n})\n// result == true\n```\n\n### Phase 2: Authentication Policies\n\n**Config Model:**\n\n```go\n// AuthPolicy is a list of deny expressions evaluated after a user\n// authenticates with a connector. Each expression evaluates to bool.\n// If true — the request is denied. Evaluated in order; first match wins.\ntype AuthPolicy []PolicyExpression\n\n// PolicyExpression is a CEL expression with an optional human-readable message.\ntype PolicyExpression struct {\n    // Expression is a CEL expression that evaluates to bool.\n    Expression string `json:\"expression\"`\n    // Message is a CEL expression that evaluates to string (displayed to the user on deny).\n    // If empty, a generic message is shown.\n    Message string `json:\"message,omitempty\"`\n}\n```\n\n**Evaluation point:** After `connector.CallbackConnector.HandleCallback()` or\n`connector.PasswordConnector.Login()` returns an identity, and before the auth request is\nfinalized. Implemented in `server/handlers.go` at `handleConnectorCallback`.\n\n**Available CEL variables:** `identity` (from connector), `request` (client_id, connector_id,\nscopes, redirect_uri).\n\n**Compilation:** All policy expressions are compiled once at config load time (in\n`cmd/dex/serve.go`) and stored in the `Server` struct. This ensures:\n- Syntax/type errors are caught at startup, not at runtime.\n- No compilation overhead per request.\n- Cost estimation can warn operators about expensive expressions at startup.\n\n**Evaluation flow:**\n\n```\nUser authenticates via connector\n         │\n         v\nconnector.HandleCallback() returns Identity\n         │\n         v\nEvaluate global authPolicy (in order)\n  - For each expression: evaluate → bool\n  - If true → deny with message, HTTP 403\n         │\n         v\nEvaluate per-client authPolicy (in order)\n  - Same logic as global\n         │\n         v\nContinue normal flow (approval screen or redirect)\n```\n\n### Phase 3: Token Policies\n\n**Config Model:**\n\n```go\n// TokenPolicy defines policies for token issuance.\ntype TokenPolicy struct {\n    // Claims adds or overrides claims in the issued ID token.\n    Claims []ClaimExpression `json:\"claims,omitempty\"`\n    // Filter validates the token request. If expression evaluates to false,\n    // the request is denied.\n    Filter *PolicyExpression `json:\"filter,omitempty\"`\n}\n\ntype ClaimExpression struct {\n    // Key is a CEL expression evaluating to string — the claim name.\n    Key string `json:\"key\"`\n    // Value is a CEL expression evaluating to dyn — the claim value.\n    Value string `json:\"value\"`\n    // Condition is an optional CEL expression evaluating to bool.\n    // When set, the claim is only included in the token if the condition\n    // evaluates to true. If omitted, the claim is always included.\n    Condition string `json:\"condition,omitempty\"`\n}\n```\n\n**Evaluation point:** In `server/oauth2.go` during ID token construction, after standard\nclaims are built but before JWT signing.\n\n**Available CEL variables:** `identity`, `request`, `existing_claims` (the standard claims already\ncomputed as `map(string, dyn)`).\n\n**Claim merge order:**\n1. Standard Dex claims (sub, iss, aud, email, groups, etc.)\n2. Global `tokenPolicy.claims` evaluated and merged\n3. Per-client `tokenPolicy.claims` evaluated and merged (overrides global)\n\n**Reserved (forbidden) claim names:**\n\nCertain claim names are reserved and MUST NOT be set or overridden by CEL token policy\nexpressions. Attempting to use a reserved claim key will result in a config validation error at\nstartup. This prevents operators from accidentally breaking the OIDC/OAuth2 contract or\nundermining Dex's security guarantees.\n\n```go\n// ReservedClaimNames is the set of claim names that CEL token policy\n// expressions are forbidden from setting. These are core OIDC/OAuth2 claims\n// managed exclusively by Dex.\nvar ReservedClaimNames = map[string]struct{}{\n    \"iss\":       {},  // Issuer — always set by Dex to its own issuer URL\n    \"sub\":       {},  // Subject — derived from connector identity, must not be spoofed\n    \"aud\":       {},  // Audience — determined by the OAuth2 client, not policy\n    \"exp\":       {},  // Expiration — controlled by Dex token TTL configuration\n    \"iat\":       {},  // Issued At — set by Dex at signing time\n    \"nbf\":       {},  // Not Before — set by Dex at signing time\n    \"jti\":       {},  // JWT ID — generated by Dex for token revocation/uniqueness\n    \"auth_time\": {},  // Authentication Time — set by Dex from the auth session\n    \"nonce\":     {},  // Nonce — echoed from the client's authorization request\n    \"at_hash\":   {},  // Access Token Hash — computed by Dex from the access token\n    \"c_hash\":    {},  // Code Hash — computed by Dex from the authorization code\n}\n```\n\nThe reserved list is enforced in two places:\n1. **Config load time** — When compiling token policy `ClaimExpression` entries, Dex statically\n   evaluates the `Key` expression (which must be a string literal or constant-foldable) and rejects\n   it if the result is in `ReservedClaimNames`.\n2. **Runtime (defense in depth)** — Before merging evaluated claims into the ID token, Dex checks\n   each key against `ReservedClaimNames` and logs a warning + skips the claim if it matches. This\n   guards against dynamic key expressions that couldn't be statically checked.\n\n### Phase 4: OIDC Connector Claim Mapping\n\n**Config Model:**\n\nIn `connector/oidc/oidc.go`:\n\n```go\ntype Config struct {\n    // ... existing fields ...\n\n    // ClaimMappingExpressions provides CEL-based claim mapping.\n    // When set, these take precedence over ClaimMapping and ClaimMutations.\n    ClaimMappingExpressions *ClaimMappingExpression `json:\"claimMappingExpressions,omitempty\"`\n}\n\ntype ClaimMappingExpression struct {\n    // Username is a CEL expression evaluating to string.\n    // Available variable: 'claims' (map of upstream claims).\n    Username string `json:\"username,omitempty\"`\n    // Email is a CEL expression evaluating to string.\n    Email string `json:\"email,omitempty\"`\n    // Groups is a CEL expression evaluating to list(string).\n    Groups string `json:\"groups,omitempty\"`\n    // EmailVerified is a CEL expression evaluating to bool.\n    EmailVerified string `json:\"emailVerified,omitempty\"`\n    // Extra is a map of claim names to CEL expressions evaluating to dyn.\n    // These are carried through to token policies.\n    Extra map[string]string `json:\"extra,omitempty\"`\n}\n```\n\n**Available CEL variable:** `claims` — `map(string, dyn)` containing all raw upstream claims from\nthe ID token and/or UserInfo endpoint.\n\nThis replaces the need for `ClaimMapping`, `NewGroupFromClaims`, `FilterGroupClaims`, and\n`ModifyGroupNames` with a single, more powerful mechanism.\n\n**Backward compatibility:** When `claimMappingExpressions` is nil, the existing `ClaimMapping` and\n`ClaimMutations` logic is used unchanged. When `claimMappingExpressions` is set, a startup warning is\nlogged if legacy mapping fields are also configured.\n\n### Policy Application Flow\n\nThe following diagram shows the order in which CEL policies are applied.\nEach step is optional — if not configured, it is skipped.\n\n```\nConnector Authentication\n  │\n  │ upstream claims → connector.Identity\n  │\n  v\nAuthentication Policies\n  │\n  │  Global authPolicy\n  │  Per-client authPolicy\n  │\n  v\nToken Issuance\n  │\n  │  Global tokenPolicy.filter\n  │  Per-client tokenPolicy.filter\n  │\n  │  Global tokenPolicy.claims\n  │  Per-client tokenPolicy.claims\n  │\n  │  Sign JWT\n  │\n  v\nToken Response\n```\n\n| Step | Policy | Scope | Action on match |\n|------|--------|-------|-----------------|\n| 2 | `authPolicy` (global) | Global | Expression → `true` = DENY login |\n| 3 | `authPolicy` (per-client) | Per-client | Expression → `true` = DENY login |\n| 4 | `tokenPolicy.filter` (global) | Global | Expression → `false` = DENY token |\n| 5 | `tokenPolicy.filter` (per-client) | Per-client | Expression → `false` = DENY token |\n| 6 | `tokenPolicy.claims` (global) | Global | Adds/overrides claims (with optional condition) |\n| 7 | `tokenPolicy.claims` (per-client) | Per-client | Adds/overrides claims (overrides global) |\n\n### Risks and Mitigations\n\n| Risk | Mitigation |\n|------|------------|\n| **CEL expression complexity / DoS** | Cost budgets with configurable limits (default aligned with Kubernetes). Expressions are validated at config load time. Runtime evaluation is aborted if cost exceeds budget. |\n| **Learning curve for operators** | CEL has excellent documentation, playground ([cel.dev](https://cel.dev)), and massive CNCF adoption. Dex docs will include a dedicated CEL guide with examples. Most operators already know CEL from Kubernetes. |\n| **`cel-go` dependency size** | `cel-go` adds ~5MB to binary. This is acceptable for the functionality provided. Kubernetes, Istio, Envoy all accept this trade-off. |\n| **Breaking changes in `cel-go`** | Pin to semver minor range. Environment versioning ensures existing expressions continue to work across upgrades. |\n| **Security: CEL expression injection** | CEL expressions are defined by operators in the server config, not by end users. No CEL expression is ever constructed from user input at runtime. |\n| **Config migration** | Old config fields (`ClaimMapping`, `ClaimMutations`) continue to work. CEL expressions are opt-in. If both are specified, CEL takes precedence with a config-time warning. |\n| **Error messages exposing internals** | CEL deny `message` expressions are controlled by the operator. Default messages are generic. Evaluation errors are logged server-side, not exposed to end users. |\n| **Performance** | Expressions are compiled once at startup. Evaluation is sub-millisecond for typical identity operations. Cost budgets prevent pathological cases. Benchmarks will be included in `pkg/cel` tests. |\n\n### Alternatives\n\n#### OPA/Rego\n\nOPA was previously considered ([#1635], token exchange DEP). While powerful, it has significant\ndrawbacks for Dex:\n\n- **Separate daemon** — OPA typically runs as a sidecar or daemon; adds operational complexity.\n  Even the embedded Go library (`github.com/open-policy-agent/opa/rego`) is significantly\n  heavier than `cel-go`.\n- **Rego learning curve** — Rego is a Datalog-derived language unfamiliar to most developers.\n  CEL syntax is closer to C/Java/Go and is immediately readable.\n- **Overkill** — Dex needs simple expression evaluation, not a full policy engine with data\n  loading, bundles, and partial evaluation.\n- **No inline expressions** — Rego policies are typically separate files, not inline config\n  expressions. This makes the config harder to understand and deploy.\n- **Smaller CNCF footprint for embedding** — While OPA is a graduated CNCF project, CEL has\n  broader adoption as an _embedded_ language (Kubernetes, Istio, Envoy, Kyverno, etc.).\n\n#### JMESPath\n\nJMESPath was proposed for claim mapping. Drawbacks:\n\n- **Query-only** — JMESPath is a JSON query language. It cannot express boolean conditions,\n  mutations, or string operations naturally.\n- **Limited type system** — No type checking at compile time. Errors are only caught at runtime.\n- **Small ecosystem** — Limited adoption compared to CEL. No CNCF projects use JMESPath for\n  policy evaluation.\n- **No cost estimation** — No way to bound execution time.\n\n#### Hardcoded Go Logic\n\nThe current approach: each feature requires new Go structs, config fields, and code. This is\nunsustainable:\n- `ClaimMapping`, `NewGroupFromClaims`, `FilterGroupClaims`, `ModifyGroupNames` are each separate\n  features that could be one CEL expression.\n- Every new policy need requires a Dex code change and release.\n- Combinatorial explosion of config options.\n\n#### No Change\n\nWithout CEL or an equivalent:\n- Operators continue to request per-client connector restrictions, custom claims, claim\n  transformations, and access policies — issues remain open indefinitely.\n- Dex accumulates more ad-hoc config fields, increasing maintenance burden.\n- Complex use cases require external reverse proxies, forking Dex, or middleware.\n\n## Future Improvements\n\n- **CEL in other connectors** — Extend CEL claim mapping beyond OIDC to LDAP (attribute mapping),\n  SAML (assertion mapping), and other connectors with complex attribute mapping needs.\n- **Policy testing framework** — Unit test framework for operators to validate their CEL\n  expressions against fixture data before deployment.\n- **Connector selection via CEL** — Replace the static connector-per-client mapping with a CEL\n  expression that dynamically determines which connectors to show based on request attributes.\n\n\n"
  },
  {
    "path": "docs/enhancements/id-jag-2026-03-02#4600.md",
    "content": "# Dex Enhancement Proposal (DEP) 4600 - 2026-03-02 - Identity Assertion JWT Authorization Grant (ID-JAG)\n\n## Table of Contents\n\n- [Dex Enhancement Proposal (DEP) 4600 - 2026-03-02 - Identity Assertion JWT Authorization Grant (ID-JAG)](#dex-enhancement-proposal-dep-4600---2026-03-02---identity-assertion-jwt-authorization-grant-id-jag)\n  - [Table of Contents](#table-of-contents)\n  - [Summary](#summary)\n  - [Context](#context)\n  - [Motivation](#motivation)\n    - [Goals/Pain](#goalspain)\n    - [Non-goals](#non-goals)\n  - [Proposal](#proposal)\n    - [User Experience](#user-experience)\n    - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)\n    - [Observability](#observability)\n    - [Risks and Mitigations](#risks-and-mitigations)\n    - [Alternatives](#alternatives)\n  - [Future Improvements](#future-improvements)\n\n## Summary\n\n[draft-ietf-oauth-identity-assertion-authz-grant-02] specifies a mechanism\nfor an application to use an identity assertion to obtain an access token\nfor a third-party API by coordinating through a common enterprise identity\nprovider using Token Exchange [RFC 8693] and JWT Profile for OAuth 2.0\nAuthorization Grants [RFC 7523].\n\nThis DEP proposes to extend Dex's existing Token Exchange implementation\nto support issuing Identity Assertion JWT Authorization Grants (ID-JAGs),\nenabling cross-domain access managed by the enterprise IdP.\n\n[draft-ietf-oauth-identity-assertion-authz-grant-02]: https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-assertion-authz-grant/\n\n## Context\n\n- [#2812 DEP for RFC 8693 OAuth 2 Token Exchange]\n  established the Token Exchange foundation that ID-JAG builds upon.\n- [draft-ietf-oauth-identity-assertion-authz-grant-02]\n  is the IETF Standards Track specification this DEP implements.\n- [draft-ietf-oauth-identity-chaining]\n  is the broader identity chaining specification that ID-JAG profiles.\n\nThe specification is authored by A. Parecki (Okta), K. McGuinness, and\nB. Campbell (Ping Identity). It is actively being developed within the\nIETF OAuth Working Group.\n\nUse cases:\n\n- LLM agents accessing enterprise APIs on behalf of users (Appendix A.3 of the spec)\n- Enterprise applications embedding content from third-party apps\n- Email/calendaring applications accessing cross-domain resources\n\nReal-world adoption:\n\n- [Okta Cross App Access] is GA, implementing ID-JAG for SaaS-to-SaaS and\n  AI agent scenarios with a developer tutorial available.\n- [Okta AI Agent Token Exchange] (Early Access) uses ID-JAG for AI agents\n  accessing enterprise APIs on behalf of authenticated users.\n- [Keycloak #43971] tracks ID-JAG support as a feature request.\n- The upcoming MCP (Model Context Protocol) specification references ID-JAG\n  for AI agent authorization flows.\n\n[#2812 DEP for RFC 8693 OAuth 2 Token Exchange]: https://github.com/dexidp/dex/pull/2812\n[draft-ietf-oauth-identity-chaining]: https://datatracker.ietf.org/doc/draft-ietf-oauth-identity-chaining/\n[Okta Cross App Access]: https://developer.okta.com/blog/2026/02/10/xaa-client\n[Okta AI Agent Token Exchange]: https://developer.okta.com/docs/guides/ai-agent-token-exchange/authserver/main/\n[Keycloak #43971]: https://github.com/keycloak/keycloak/issues/43971\n\n## Motivation\n\n### Goals/Pain\n\nIn enterprise environments, applications are configured for SSO through\na common IdP. When one application needs to access a user's data at another\napplication, the current approach requires either:\n\n1. A direct OAuth flow between apps (bypassing the IdP's visibility and policy)\n2. Static API keys or service accounts (security risk)\n\nID-JAG solves this by letting the IdP broker cross-domain access, maintaining\nvisibility and policy control.\n\n**Specific goals:**\n\n- Issue ID-JAG tokens via Token Exchange (`requested_token_type=urn:ietf:params:oauth:token-type:id-jag`)\n- Support `audience` and `resource` parameters per the specification\n- Validate subject token audience against requesting client\n- Support configurable policy evaluation for token exchange requests\n\n### Non-goals\n\n- Implementing the Resource Authorization Server role (JWT Bearer Grant / RFC 7523)\n  is out of scope for this initial DEP. It may be addressed in a follow-up.\n- SAML assertion support as `subject_token_type` is deferred.\n- Step-up authentication flow is deferred.\n\n## Proposal\n\n### User Experience\n\nEnd-to-end flow:\n\n```mermaid\nsequenceDiagram\n    participant C as Client (Wiki App)\n    participant Dex as Dex (IdP AS)\n    participant RAS as Resource AS (Chat AS)\n    participant RS as Resource Server (Chat API)\n\n    C->>Dex: 1. OIDC Authentication (authorization_code flow)\n    Dex-->>C: ID Token + optional Refresh Token\n\n    C->>Dex: 2. Token Exchange (grant_type=token-exchange)<br/>subject_token=ID Token<br/>requested_token_type=id-jag<br/>audience=https://acme.chat.example/<br/>connector_id=google\n    Note over Dex: Validate subject_token aud == client_id<br/>Evaluate policy (clientID → allowed audiences)<br/>Issue ID-JAG JWT (typ: oauth-id-jag+jwt)\n    Dex-->>C: ID-JAG (access_token, token_type=N_A, expires_in=300)\n\n    C->>RAS: 3. JWT Bearer Grant (RFC 7523)<br/>grant_type=jwt-bearer, assertion=ID-JAG\n    Note over RAS: Validate ID-JAG signature (Dex JWKS)<br/>Validate aud == RAS issuer<br/>Issue access token\n    RAS-->>C: Access Token\n\n    C->>RS: 4. API Request with Access Token\n    RS-->>C: Protected Resource\n```\n\nClients can request ID-JAG tokens from Dex's `/token` endpoint by specifying\n`requested_token_type=urn:ietf:params:oauth:token-type:id-jag` in a\nToken Exchange request. ID-JAG support is enabled by adding\n`urn:ietf:params:oauth:token-type:id-jag` to `oauth2.tokenExchange.tokenTypes`.\nWhen not listed, requests with this `requested_token_type` are rejected,\nensuring no change in behavior for existing deployments.\n\nThe request parameters (extending existing Token Exchange):\n\n- `grant_type`: REQUIRED - `urn:ietf:params:oauth:grant-type:token-exchange`\n- `subject_token`: REQUIRED - the identity assertion (OpenID Connect ID Token)\n- `subject_token_type`: REQUIRED - `urn:ietf:params:oauth:token-type:id_token`.\n  SAML 2.0 (`urn:ietf:params:oauth:token-type:saml2`) is deferred (see Non-goals).\n- `requested_token_type`: REQUIRED - `urn:ietf:params:oauth:token-type:id-jag`\n- `audience`: REQUIRED - the Issuer URL of the Resource Authorization Server.\n  **Note**: The existing Token Exchange implementation uses a Dex-specific `connector_id`\n  parameter (not part of RFC 8693) for connector selection. The `audience` parameter was\n  not used in the current implementation despite DEP #2812 originally proposing it for\n  connector identification. ID-JAG introduces `audience` with its standard RFC 8693\n  meaning (target Resource AS). This is purely additive and does not affect existing\n  Token Exchange requests.\n- `connector_id`: REQUIRED (Dex extension) - the ID of the Dex connector to verify the\n  subject token against. The connector validates the token (issuer, signature, etc.),\n  so a mismatched token is rejected. This parameter already exists in the current\n  Token Exchange implementation and is reused as-is.\n- `resource`: OPTIONAL - the Resource Identifier of the Resource Server\n- `scope`: OPTIONAL - the requested scopes at the Resource Server\n\nThe response:\n\n- `access_token`: the ID-JAG JWT (named `access_token` for RFC 8693 compatibility)\n- `issued_token_type`: `urn:ietf:params:oauth:token-type:id-jag`\n- `token_type`: `N_A` (this is not an OAuth access token)\n- `expires_in`: lifetime in seconds (default: 300, configurable independently of ID token\n  lifetime via `expiry.idJAGTokens`)\n- `scope`: OPTIONAL if the issued scope is identical to the requested scope; REQUIRED\n  otherwise. Per Section 4.3.2 of the specification, policy evaluation at the IdP may\n  result in different scopes being issued than were requested.\n\nComplete configuration example:\n\n```yaml\noauth2:\n  grantTypes:\n    - authorization_code\n    - urn:ietf:params:oauth:grant-type:token-exchange\n  tokenExchange:\n    # List of token types enabled for exchange. Adding id-jag enables ID-JAG support.\n    # Omitting it (default) disables ID-JAG without affecting other token exchange flows.\n    # SAML2 (urn:ietf:params:oauth:token-type:saml2) may be added in a future release.\n    tokenTypes:\n      - urn:ietf:params:oauth:token-type:id_token\n      - urn:ietf:params:oauth:token-type:id-jag\n\nexpiry:\n  idTokens: \"24h\"\n  idJAGTokens: \"5m\"  # default: 5m; independent of idTokens\n\nstaticClients:\n  - id: wiki-app\n    name: \"Wiki Application\"\n    secret: \"wiki-secret\"\n    redirectURIs:\n      - \"https://wiki.example/callback\"\n    # Per-client ID-JAG policy. Clients without this section cannot obtain ID-JAG tokens\n    # (default-deny). Only audiences and scopes listed here may be requested.\n    idJAGPolicies:\n      allowedAudiences:\n        - \"https://chat.example/\"\n        - \"https://calendar.example/\"\n      allowedScopes:\n        - \"chat.read\"\n        - \"calendar.read\"\n\n  - id: supermarket-app\n    name: \"Supermarket Application\"\n    secret: \"supermarket-secret\"\n    redirectURIs:\n      - \"https://supermarket.example/callback\"\n    idJAGPolicies:\n      allowedAudiences:\n        - \"https://grocery.store.1/\"\n        - \"https://grocery.store.2/\"\n      allowedScopes:\n        - \"eat.bananas\"\n        - \"eat.apples\"\n```\n\n### Implementation Details/Notes/Constraints\n\n- A new `id-jag` branch is added to the existing Token Exchange flow, issuing a signed JWT\n  per Section 3 of the specification (header `typ: \"oauth-id-jag+jwt\"`, claims including\n  `iss`, `sub`, `aud`, `client_id`, `jti`, `exp`, `iat`).\n\n- Per-client `idJAGPolicies` in `staticClients` control which audiences and scopes a\n  given client may request in an ID-JAG. Clients without `idJAGPolicies` are denied\n  by default. Dynamically registered clients are currently unsupported for ID-JAG policies;\n  support via CEL expressions (building on the CEL infrastructure from #4601) is future work.\n\n- OIDC discovery is extended with `identity_chaining_requested_token_types_supported` per\n  Section 7 of the specification. When ID-JAG is enabled, Dex includes\n  `urn:ietf:params:oauth:token-type:id-jag` in this metadata property.\n\n- ID-JAG support is enabled by listing `urn:ietf:params:oauth:token-type:id-jag` in\n  `oauth2.tokenExchange.tokenTypes`. When not listed (default), requests are rejected,\n  ensuring no change in behavior for existing deployments.\n\n### Observability\n\n- Every ID-JAG token exchange request (issued or rejected) emits a structured log entry\n  with `client_id`, `connector_id`, `audience`, `resource` (if present), requested and\n  granted `scope` (these may differ after policy evaluation), `sub`, `jti` (if issued),\n  and the policy decision (`approved`/`denied` with reason like `audience_not_allowed`\n  or `client_has_no_policy`).\n\n- The following Prometheus counters are exposed:\n  - `dex_id_jag_requests_total` (labels: `result`) — issued vs rejected\n  - `dex_id_jag_policy_rejections_total` (labels: `reason`) —\n    breakdown by denial reason, useful for spotting misconfigurations or abuse\n  - `dex_id_jag_scope_modifications_total` — cases where policy reduced the requested scopes\n\n### Risks and Mitigations\n\n- **Lateral movement risk**: Same as existing Token Exchange. Mitigated by\n  not issuing refresh tokens, short expiry (5 min recommended), and\n  policy-based audience restrictions.\n- **Token confusion**: The `typ: \"oauth-id-jag+jwt\"` header and distinct\n  `issued_token_type` prevent confusion with ID Tokens or access tokens.\n- **Replay attack risk**: Server-side `jti` tracking is deferred, so a stolen ID-JAG\n  can be replayed within its 5-minute lifetime. Short `expires_in` is the only Dex-side\n  mitigation; Resource Authorization Servers should implement `jti` caching independently.\n- **Public client misuse**: Per Section 8.1 of the specification, ID-JAG SHOULD only be\n  used by confidential clients. Public clients should use the standard authorization code\n  flow with interactive user consent at the Resource Authorization Server. Dex will enforce\n  this by rejecting ID-JAG requests from public clients (clients without a secret).\n- **Breaking changes**: None. This is purely additive to the existing\n  Token Exchange implementation. The `audience` parameter is newly introduced\n  (not previously used in the implementation despite DEP #2812's original proposal),\n  and `connector_id` already exists.\n\n### Alternatives\n\n- **Wait for spec finalization**: The draft is Standards Track and stable enough\n  to implement. Okta and Ping Identity (the spec authors) already ship implementations,\n  and the spec has been adopted by the IETF OAuth WG.\n- **External policy engine (OPA/CEL)**: Config-based policies are sufficient for now.\n  The CEL infrastructure (#4601) is merged; ID-JAG policy evaluation via CEL is future work.\n\n## Future Improvements\n\n- Resource Authorization Server role (JWT Bearer Grant / RFC 7523)\n  accepting ID-JAGs from external IdPs\n- SAML 2.0 assertion support as `subject_token_type`\n  (`urn:ietf:params:oauth:token-type:saml2`)\n- CEL-based ID-JAG policy evaluation (building on #4601) enabling dynamic policies for\n  DB-managed clients, including runtime policy changes without restart\n- Step-up authentication when authentication context is insufficient\n- `actor_token` support for delegation scenarios\n- Server-side `jti` tracking to prevent ID-JAG replay attacks\n"
  },
  {
    "path": "docs/enhancements/token-exchange-2023-02-03-#2812.md",
    "content": "# Dex Enhancement Proposal (DEP) 2812 - 2023-02-03 - Token Exchange\n\n## Table of Contents\n\n- [Summary](#summary)\n- [Motivation](#motivation)\n    - [Goals/Pain](#goals)\n    - [Non-Goals](#non-goals)\n- [Proposal](#proposal)\n    - [User Experience](#user-experience)\n    - [Implementation Details/Notes/Constraints](#implementation-detailsnotesconstraints)\n    - [Risks and Mitigations](#risks-and-mitigations)\n    - [Alternatives](#alternatives)\n- [Future Improvements](#future-improvements)\n\n## Summary\n\n[RFC 8693] specifies a new OAuth2 `grant_type` of `urn:ietf:params:oauth:grant-type:token-exchange`.\nUsing this grant type, when clients start an authentication flow with Dex,\nin lieu of being redirected to their upstream IDP for authentication on demand,\nclients can present an independently obtained, valid token from their IDP to Dex.\nThis is primarily useful in fully automated environments with job/machine identities,\nwhere there is no human in the loop to handle browser-based login flows.\nThis DEP proposes to implement the new grant type for Dex.\n\n[RFC 8693]: https://www.rfc-editor.org/rfc/rfc8693.html\n\n## Context\n\n- [#1668 Question: non-web based clients?]\n  was closed with no real resolution\n- [#1484 Token exchange for external tokens]\n  mentions that Keycloak has a similar capability\n- [#2657 Get OIDC token issued by Dex using a token issued by one of the connectors] \n  is similar to the previous issue, but this time links to the new (January 2020) [RFC 8693].\n\nI believe the context for all of these are similar:\na downstream project using Dex as its only IDP wants to grant access to programmatic clients\nwithout issuing long lived API tokens.\n\nExamples of downstream issues:\n\n- [argoproj/argo-cd#11632 ArgoCD SSO login via Azure AD Auth using OIDC not work for cli sso login]\n\nOther related Dex issues:\n\n- [#2450 Non-OIDC JWT Connector] is a functionally similar request, but expanded to arbitrary JWTs\n- [#1225 GitHub Non-Web application flow support] also asks for an exchange, but for an opaque GitHub PAT\n\nMore broadly, this fits into recent movements to issue machine identities:\n\n- [GCP Service Identity](https://cloud.google.com/run/docs/securing/service-identity)\n- [AWS Execution Role](https://docs.aws.amazon.com/lambda/latest/dg/lambda-intro-execution-role.html)\n- [GitHub Actions OIDC](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)\n- [CircleCI OIDC](https://circleci.com/docs/openid-connect-tokens/)\n- [Kubernetes Service Accounts](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/)\n- [SPIFFE](https://spiffe.io/)\n\nand granting access to resources based on trusting federated identities:\n\n- [GCP Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation)\n- [AWS STS AssumeRoleWithWebIdentity](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html)\n\n[#1484 Token exchange for external tokens]: https://github.com/dexidp/dex/issues/1484\n[#1668 Question: non-web based clients?]: https://github.com/dexidp/dex/issues/1668\n[#2657 Get OIDC token issued by Dex using a token issued by one of the connectors]: https://github.com/dexidp/dex/issues/2657\n[argoproj/argo-cd#11632 ArgoCD SSO login via Azure AD Auth using OIDC not work for cli sso login]: https://github.com/argoproj/argo-cd/issues/11632\n[#2450 Non-OIDC JWT Connector]: https://github.com/dexidp/dex/issues/2450\n[#1225 GitHub Non-Web application flow support]: https://github.com/dexidp/dex/issues/1225\n\nAn initial attempt is at [#2806](https://github.com/dexidp/dex/pull/2806)\n\n## Motivation\n\n### Goals/Pain\n\nThe goal is to allow programmatic access to Dex-protected resources \nwithout the use of static/long-lived secret tokens (API keys, username/password)\nor web-based redirect flows.\nSuch scenarios are common in CI/CD workflows,\nand in general automation of common tasks.\n\n### Non-goals\n\n- Work will be scoped to just the OIDC connector\n- [RFC 8693 Section 2.1.1. Relationship between Resource, Audience, and Scope]\n  details more complex authorization checks based on targeted resources.\n  This is considered out of scope.\n\n[RFC 8693 Section 2.1.1. Relationship between Resource, Audience, and Scope]: https://www.rfc-editor.org/rfc/rfc8693.html#name-relationship-between-resour\n\n## Proposal\n\n### User Experience\n\nClients can make `POST` requests with `application/x-www-form-urlencoded` \nparameters as specified by [RFC 8693] to Dex's `/token` endpoint.\nIf successful, an access token will be returned,\nallowing direct authentication with Dex.\nNo refresh tokens will be issued,\nperform a new exchange (possibly with refreshed upstream tokens) to obtain a new access token.\n\nThe request parameters from [RFC 8693 Section 2.1](https://www.rfc-editor.org/rfc/rfc8693.html#name-request):\n\n- `grant_type`: REQUIRED - `urn:ietf:params:oauth:grant-type:token-exchange`\n- `resource`: OPTIONAL - the `audience` in the issued Dex token\n- `audience`: REQUIRED (RFC OPTIONAL) - the connector to verify the provided token against\n- `scope`: OPTIONAL - the `scope` in the issued Dex token\n- `requested_token_type`: OPTIONAL - one of `urn:ietf:params:oauth:token-type:access_token` or `urn:ietf:params:oauth:token-type:id_token`, defaulting to access token\n- `subject_token`: REQUIRED - the token issued by the upstream IDP\n- `subject_token_type`: REQUIRED - `urn:ietf:params:oauth:token-type:id_token` or `urn:ietf:params:oauth:token-type:access_token` if `getUserInfo` is `true`.\n- `actor_token`: OPTIONAL - unused\n- `actor_token_type`: OPTIONAL - unused\n\nThe response parameters from [RFC 8693 Section 2.2](https://www.rfc-editor.org/rfc/rfc8693.html#name-response):\n\n- `access_token`: the issued token, the field is called `access_token` for legacy reasons\n- `issued_token_type`: the actual type of the issued token\n- `token_type`: the value `Bearer`\n- `expires_in`: validity lifetime in seconds\n- `scope`: the requested scope\n- `refresh_token`: unused\n\nThe connector only needs to be configured with an issuer,\nno client ID / client secrets are necessary\n\n```yaml\nconnectors:\n- type: oidc\n  id: my-platform\n  name: My Platform\n  config:\n    issuer: https://oidc.my-platform.example/\n```\n\nWe expose a global and connector setting, \n`allowedGrantTypes: []string` defaulting to all implemented types.\n\n### Implementation Details/Notes/Constraints\n\n- Connectors expose a new interface `TokenIdentity` that will verify the given token and return the associated identity.\n  A Dex access/id token is then minted for the given identity.\n\n- `actor_token` and `actor_token_type` are \"MUST ... if the actor token is present, \n  also perform the appropriate validation procedures for its indicated token type\".\n  We will ignore these fields for the initial implementation.\n\n\n### Risks and Mitigations\n\nWith token exchanges (sometimes known as identity impersonation), \nis they allow for easier lateral movement if an attacker gains access to an upstream token.\nWe limit the potential impact by not issuing refresh tokens, preventing persistent access.\nCombined with short token lifetimes, it should limit the period of time between authentication to upstream IDPs.\nAdditionally, a new `allowedGrantTypes` would allow for disabling exchanges if the functionality isn't needed.\n\n### Alternatives\n\n- Continue to use static keys - \n  this is a secret management nightmare \n  and quite painful when client storage of keys is [breached](https://circleci.com/blog/january-4-2023-security-alert/)\n\n## Future Improvements\n\n- Other connectors may wish to implement the same capability under Oauth\n- The password connector could be switch to support this new endpoint, submitting passwords as access tokens,\n  allowing for multiple password connectors to be configured\n- The `audience` field could be made optional if there is a single connector or the id token is inspected for issuer url\n- The `actor_token` and `actor_token_type` can be checked / validated if a suitable use case is determined.\n- A policy language like [cel] or [rego] as mentioned on [#1635 Connector Middleware] \n  would allow for stronger assertions of the provided identity against requested resource access.\n\n[cel]: https://github.com/google/cel-go\n[rego]: https://www.openpolicyagent.org/docs/latest/policy-language/\n[#1635 Connector Middleware]: https://github.com/dexidp/dex/issues/1635\n"
  },
  {
    "path": "examples/.gitignore",
    "content": "*.db\n"
  },
  {
    "path": "examples/config-ad-kubelogin.yaml",
    "content": "# Active Directory and kubelogin Integration sample\nissuer: https://dex.example.com:32000/dex\nstorage:\n  type: sqlite3\n  config:\n    file: examples/dex.db\nweb:\n  https: 0.0.0.0:32000\n  tlsCert: openid-ca.pem\n  tlsKey: openid-key.pem\n\nconnectors:\n- type: ldap\n  name: OpenLDAP\n  id: ldap\n  config:\n    host: localhost:636\n\n    # No TLS for this setup.\n    insecureNoSSL: false\n    insecureSkipVerify: true\n\n    # This would normally be a read-only user.\n    bindDN: cn=Administrator,cn=users,dc=example,dc=com\n    bindPW: admin0!\n\n    usernamePrompt: Email Address\n\n    userSearch:\n      baseDN: cn=Users,dc=example,dc=com\n      filter: \"(objectClass=person)\"\n      username: userPrincipalName\n      # \"DN\" (case sensitive) is a special attribute name. It indicates that\n      # this value should be taken from the entity's DN not an attribute on\n      # the entity.\n      idAttr: DN\n      emailAttr: userPrincipalName\n      nameAttr: cn\n\n    groupSearch:\n      baseDN: cn=Users,dc=example,dc=com\n      filter: \"(objectClass=group)\"\n\n      userMatchers:\n      # A user is a member of a group when their DN matches\n      # the value of a \"member\" attribute on the group entity.\n      - userAttr: DN\n        groupAttr: member\n\n      # The group name should be the \"cn\" value.\n      nameAttr: cn\n\nstaticClients:\n- id: kubernetes\n  redirectURIs:\n  - 'http://localhost:8000'\n  name: 'Kubernetes'\n  secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n\n"
  },
  {
    "path": "examples/config-dev.yaml",
    "content": "# DEPRECATED: use config.yaml.dist and config.dev.yaml examples in the repository root.\n# TODO: keep this until all references are updated.\n\n# The base path of dex and the external name of the OpenID Connect service.\n# This is the canonical URL that all clients MUST use to refer to dex. If a\n# path is provided, dex's HTTP service will listen at a non-root URL.\nissuer: http://127.0.0.1:5556/dex\n\n# The storage configuration determines where dex stores its state. Supported\n# options include SQL flavors and Kubernetes third party resources.\n#\n# See the documentation (https://dexidp.io/docs/storage/) for further information.\nstorage:\n  type: sqlite3\n  config:\n    file: examples/dex.db\n\n  # type: mysql\n  # config:\n  #   host: localhost\n  #   port: 3306\n  #   database: dex\n  #   user: mysql\n  #   password: mysql\n  #   ssl:\n  #     mode: \"false\"\n\n  # type: postgres\n  # config:\n  #   host: localhost\n  #   port: 5432\n  #   database: dex\n  #   user: postgres\n  #   password: postgres\n  #   ssl:\n  #     mode: disable\n\n  # type: etcd\n  # config:\n  #   endpoints:\n  #     - http://localhost:2379\n  #   namespace: dex/\n\n  # type: kubernetes\n  # config:\n  #   kubeConfigFile: $HOME/.kube/config\n\n# Configuration for the HTTP endpoints.\nweb:\n  http: 0.0.0.0:5556\n  # Uncomment for HTTPS options.\n  # https: 127.0.0.1:5554\n  # tlsCert: /etc/dex/tls.crt\n  # tlsKey: /etc/dex/tls.key\n  # headers:\n  #   X-Frame-Options: \"DENY\"\n  #   X-Content-Type-Options: \"nosniff\"\n  #   X-XSS-Protection: \"1; mode=block\"\n  #   Content-Security-Policy: \"default-src 'self'\"\n  #   Strict-Transport-Security: \"max-age=31536000; includeSubDomains\"\n  # clientRemoteIP:\n  #   header: X-Forwarded-For\n  #   trustedProxies:\n  #   - 10.0.0.0/8\n\n# Configuration for dex appearance\n# frontend:\n#   issuer: dex\n#   logoURL: theme/logo.png\n#   dir: web/\n#   Allowed values: light, dark\n#   theme: light\n\n# Configuration for telemetry\ntelemetry:\n  http: 0.0.0.0:5558\n  # enableProfiling: true\n\n# Uncomment this block to enable the gRPC API. This values MUST be different\n# from the HTTP endpoints.\n# grpc:\n#   addr: 127.0.0.1:5557\n#   tlsCert: examples/grpc-client/server.crt\n#   tlsKey: examples/grpc-client/server.key\n#   tlsClientCA: examples/grpc-client/ca.crt\n\n# Uncomment this block to enable configuration for the expiration time durations.\n# Is possible to specify units using only s, m and h suffixes.\n# expiry:\n#   deviceRequests: \"5m\"\n#   signingKeys: \"6h\"\n#   idTokens: \"24h\"\n#   refreshTokens:\n#     reuseInterval: \"3s\"\n#     validIfNotUsedFor: \"2160h\" # 90 days\n#     absoluteLifetime: \"3960h\" # 165 days\n\n# Authentication sessions configuration.\n# Requires DEX_SESSIONS_ENABLED=true feature flag.\n# sessions:\n#   cookieName: \"dex_session\"\n#   absoluteLifetime: \"24h\"\n#   validIfNotUsedFor: \"1h\"\n#   rememberMeCheckedByDefault: false\n\n# Options for controlling the logger.\n# logger:\n#   level: \"debug\"\n#   format: \"text\" # can also be \"json\"\n\n# Default values shown below\n# oauth2:\n#   # grantTypes determines the allowed set of authorization flows.\n#   grantTypes:\n#     - \"authorization_code\"\n#     - \"client_credentials\"\n#     - \"refresh_token\"\n#     - \"implicit\"\n#     - \"password\"\n#     - \"urn:ietf:params:oauth:grant-type:device_code\"\n#     - \"urn:ietf:params:oauth:grant-type:token-exchange\"\n#   # responseTypes determines the allowed response contents of a successful authorization flow.\n#   # use [\"code\", \"token\", \"id_token\"] to enable implicit flow for web-only clients.\n#   responseTypes: [ \"code\" ] # also allowed are \"token\" and \"id_token\"\n#   # By default, Dex will ask for approval to share data with application\n#   # (approval for sharing data from connected IdP to Dex is separate process on IdP)\n#   skipApprovalScreen: false\n#   # If only one authentication method is enabled, the default behavior is to\n#   # go directly to it. For connected IdPs, this redirects the browser away\n#   # from application to upstream provider such as the Google login page\n#   alwaysShowLoginScreen: false\n#   # Uncomment the passwordConnector to use a specific connector for password grants\n#   passwordConnector: local\n#   # PKCE (Proof Key for Code Exchange) configuration\n#   pkce:\n#     # If true, PKCE is required for all authorization code flows (OAuth 2.1).\n#     enforce: false\n#     # Supported code challenge methods. Defaults to [\"S256\", \"plain\"].\n#     codeChallengeMethodsSupported: [\"S256\", \"plain\"]\n\n# Multi-factor authentication configuration.\n# Requires DEX_SESSIONS_ENABLED=true feature flag.\n# mfa:\n#   authenticators:\n#   - id: totp-1\n#     type: TOTP\n#     config:\n#       issuer: \"dex-1\"\n#     # Optional: limit this authenticator to specific connector types (e.g., ldap, oidc, saml).\n#     # If omitted or empty, applies to all connector types.\n#     # It is recommended to use this option to prevent MFA from being used for connectors\n#     # with their own MFA mechanisms, e.g., OIDC, Google, etc. (but technically, it is possible).\n#     connectorTypes:\n#     - mockCallback\n#   # Default MFA chain applied to clients that don't specify their own mfaChain.\n#   # If omitted or empty, no MFA is required by default.\n#   defaultMFAChain:\n#   - totp-1\n\n# Instead of reading from an external storage, use this list of clients.\n#\n# If this option isn't chosen clients may be added through the gRPC API.\nstaticClients:\n- id: example-app\n  redirectURIs:\n  - 'http://127.0.0.1:5555/callback'\n  - '/dex/device/callback'\n  name: 'Example App'\n  secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n  # Optional: restrict which connectors this client can use for authentication.\n  # If omitted or empty, all connectors are allowed.\n  # allowedConnectors:\n  # - mock\n  # Optional: ordered list of MFA authenticator IDs the user must complete during login.\n  # References authenticator IDs from mfa.authenticators.\n  # If omitted, mfa.defaultMFAChain is used.\n  # mfaChain:\n  # - totp-1\n\n# Example using environment variables\n# Set DEX_CLIENT_ID and DEX_SECURE_CLIENT_SECRET before starting Dex\n# - idEnv: DEX_CLIENT_ID\n#   secretEnv: DEX_CLIENT_SECRET\n#   redirectURIs:\n#   - 'http://127.0.0.1:5556/callback'\n#   name: 'Secure Example App'\n\n#  - id: example-device-client\n#    redirectURIs:\n#      - /device/callback\n#    name: 'Static Client for Device Flow'\n#    public: true\n\nconnectors:\n- type: mockCallback\n  id: mock\n  name: Example\n  # grantTypes restricts which grant types can use this connector.\n  # If not specified, all grant types are allowed.\n  # Supported values:\n  #   - \"authorization_code\"\n  #   - \"implicit\"\n  #   - \"refresh_token\"\n  #   - \"password\"\n  #   - \"urn:ietf:params:oauth:grant-type:device_code\"\n  #   - \"urn:ietf:params:oauth:grant-type:token-exchange\"\n#  grantTypes:\n#  - \"authorization_code\"\n#  - \"refresh_token\"\n# - type: google\n#   id: google\n#   name: Google\n#   config:\n#     issuer: https://accounts.google.com\n#     # Connector config values starting with a \"$\" will read from the environment.\n#     clientID: $GOOGLE_CLIENT_ID\n#     clientSecret: $GOOGLE_CLIENT_SECRET\n#     redirectURI: http://127.0.0.1:5556/dex/callback\n#     hostedDomains:\n#     - $GOOGLE_HOSTED_DOMAIN\n\n# Let dex keep a list of passwords which can be used to login to dex.\nenablePasswordDB: true\n\n# A static list of passwords to login the end user. By identifying here, dex\n# won't look in its underlying storage for passwords.\n#\n# If this option isn't chosen users may be added through the gRPC API.\nstaticPasswords:\n- email: \"admin@example.com\"\n  # bcrypt hash of the string \"password\": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)\n  hash: \"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W\"\n  username: \"admin\"\n  name: \"Admin User\"\n  emailVerified: true\n  preferredUsername: \"admin\"\n  groups:\n  - \"team-a\"\n  - \"team-a/admins\"\n  userID: \"08a8684b-db88-4b73-90a9-3cd1661f5466\"\n\n# Settings for signing JWT tokens. Available options:\n# - \"local\": use local keys (only RSA keys supported)\n# - \"vault\": use Vault Transit backend (RSA and EC keys supported)\nsigner:\n  type: local\n  config:\n    keysRotationPeriod: \"6h\"\n# signer\n#   type: vault\n#   config:\n#     addr: http://127.0.0.1:8200\n#     token: root\n#     keyName: dex-key\n"
  },
  {
    "path": "examples/example-app/handlers.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"golang.org/x/oauth2\"\n)\n\nfunc (a *app) handleIndex(w http.ResponseWriter, r *http.Request) {\n\trenderIndex(w, indexPageData{\n\t\tScopesSupported: a.scopesSupported,\n\t\tLogoURI:         dexLogoDataURI,\n\t})\n}\n\nfunc (a *app) handleLogin(w http.ResponseWriter, r *http.Request) {\n\tif err := r.ParseForm(); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"failed to parse form: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Only use scopes that are checked in the form\n\tscopes := r.Form[\"extra_scopes\"]\n\tcrossClients := r.Form[\"cross_client\"]\n\n\t// Build complete scope list with audience scopes\n\tscopes = buildScopes(scopes, crossClients)\n\n\tconnectorID := \"\"\n\tif id := r.FormValue(\"connector_id\"); id != \"\" {\n\t\tconnectorID = id\n\t}\n\n\tauthCodeURL := \"\"\n\n\tvar authCodeOptions []oauth2.AuthCodeOption\n\n\tif a.pkce {\n\t\tauthCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam(\"code_challenge\", codeChallenge))\n\t\tauthCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam(\"code_challenge_method\", \"S256\"))\n\t}\n\n\t// Check if offline_access scope is present to determine offline access mode\n\thasOfflineAccess := false\n\tfor _, scope := range scopes {\n\t\tif scope == \"offline_access\" {\n\t\t\thasOfflineAccess = true\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif hasOfflineAccess && !a.offlineAsScope {\n\t\t// Provider uses access_type=offline instead of offline_access scope\n\t\tauthCodeOptions = append(authCodeOptions, oauth2.AccessTypeOffline)\n\t\t// Remove offline_access from scopes as it's not supported\n\t\tfilteredScopes := make([]string, 0, len(scopes))\n\t\tfor _, scope := range scopes {\n\t\t\tif scope != \"offline_access\" {\n\t\t\t\tfilteredScopes = append(filteredScopes, scope)\n\t\t\t}\n\t\t}\n\t\tscopes = filteredScopes\n\t}\n\n\tauthCodeURL = a.oauth2Config(scopes).AuthCodeURL(exampleAppState, authCodeOptions...)\n\n\t// Parse the auth code URL and safely add connector_id parameter if provided\n\tu, err := url.Parse(authCodeURL)\n\tif err != nil {\n\t\thttp.Error(w, \"Failed to parse auth URL\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tif connectorID != \"\" {\n\t\tquery := u.Query()\n\t\tquery.Set(\"connector_id\", connectorID)\n\t\tu.RawQuery = query.Encode()\n\t}\n\n\thttp.Redirect(w, r, u.String(), http.StatusSeeOther)\n}\n\nfunc (a *app) handleCallback(w http.ResponseWriter, r *http.Request) {\n\tvar (\n\t\terr   error\n\t\ttoken *oauth2.Token\n\t)\n\n\tctx := oidc.ClientContext(r.Context(), a.client)\n\toauth2Config := a.oauth2Config(nil)\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\t// Authorization redirect callback from OAuth2 auth flow.\n\t\tif errMsg := r.FormValue(\"error\"); errMsg != \"\" {\n\t\t\thttp.Error(w, errMsg+\": \"+r.FormValue(\"error_description\"), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tcode := r.FormValue(\"code\")\n\t\tif code == \"\" {\n\t\t\thttp.Error(w, fmt.Sprintf(\"no code in request: %q\", r.Form), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif state := r.FormValue(\"state\"); state != exampleAppState {\n\t\t\thttp.Error(w, fmt.Sprintf(\"expected state %q got %q\", exampleAppState, state), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tvar authCodeOptions []oauth2.AuthCodeOption\n\t\tif a.pkce {\n\t\t\tauthCodeOptions = append(authCodeOptions, oauth2.SetAuthURLParam(\"code_verifier\", codeVerifier))\n\t\t}\n\n\t\ttoken, err = oauth2Config.Exchange(ctx, code, authCodeOptions...)\n\tcase http.MethodPost:\n\t\t// Form request from frontend to refresh a token.\n\t\trefresh := r.FormValue(\"refresh_token\")\n\t\tif refresh == \"\" {\n\t\t\thttp.Error(w, fmt.Sprintf(\"no refresh_token in request: %q\", r.Form), http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tt := &oauth2.Token{\n\t\t\tRefreshToken: refresh,\n\t\t\tExpiry:       time.Now().Add(-time.Hour),\n\t\t}\n\t\ttoken, err = oauth2Config.TokenSource(ctx, t).Token()\n\tdefault:\n\t\thttp.Error(w, fmt.Sprintf(\"method not implemented: %s\", r.Method), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"failed to get token: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tparseAndRenderToken(w, r, a, token)\n}\n"
  },
  {
    "path": "examples/example-app/handlers_device.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"golang.org/x/oauth2\"\n)\n\nfunc (a *app) handleDeviceLogin(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\t// Parse request body to get options\n\tvar reqBody struct {\n\t\tScopes       []string `json:\"scopes\"`\n\t\tCrossClients []string `json:\"cross_clients\"`\n\t\tConnectorID  string   `json:\"connector_id\"`\n\t}\n\n\tif err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"failed to parse request body: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Build complete scope list with audience scopes (same as handleLogin)\n\tscopes := buildScopes(reqBody.Scopes, reqBody.CrossClients)\n\n\t// Build scope string\n\tscopeStr := strings.Join(scopes, \" \")\n\n\t// Get device authorization endpoint\n\t// Properly construct the device code endpoint URL\n\tauthURL := a.provider.Endpoint().AuthURL\n\tdeviceAuthURL := strings.TrimSuffix(authURL, \"/auth\") + \"/device/code\"\n\n\t// Request device code\n\tdata := url.Values{}\n\tdata.Set(\"client_id\", a.clientID)\n\tdata.Set(\"client_secret\", a.clientSecret)\n\tdata.Set(\"scope\", scopeStr)\n\n\t// Add connector_id if specified\n\tif reqBody.ConnectorID != \"\" {\n\t\tdata.Set(\"connector_id\", reqBody.ConnectorID)\n\t}\n\n\tresp, err := a.client.PostForm(deviceAuthURL, data)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to request device code: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody := new(bytes.Buffer)\n\t\tbody.ReadFrom(resp.Body)\n\t\thttp.Error(w, fmt.Sprintf(\"Device code request failed: %s\", body.String()), resp.StatusCode)\n\t\treturn\n\t}\n\n\tvar deviceResp struct {\n\t\tDeviceCode              string `json:\"device_code\"`\n\t\tUserCode                string `json:\"user_code\"`\n\t\tVerificationURI         string `json:\"verification_uri\"`\n\t\tVerificationURIComplete string `json:\"verification_uri_complete\"`\n\t\tExpiresIn               int    `json:\"expires_in\"`\n\t\tInterval                int    `json:\"interval\"`\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(&deviceResp); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to decode device response: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Store device flow data with new session\n\tsessionID := generateSessionID()\n\n\ta.deviceFlowMutex.Lock()\n\ta.deviceFlowData.sessionID = sessionID\n\ta.deviceFlowData.deviceCode = deviceResp.DeviceCode\n\ta.deviceFlowData.userCode = deviceResp.UserCode\n\ta.deviceFlowData.verificationURI = deviceResp.VerificationURI\n\ta.deviceFlowData.pollInterval = deviceResp.Interval\n\tif a.deviceFlowData.pollInterval == 0 {\n\t\ta.deviceFlowData.pollInterval = 5\n\t}\n\ta.deviceFlowData.token = nil\n\ta.deviceFlowMutex.Unlock()\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]interface{}{\n\t\t\"status\":     \"ok\",\n\t\t\"session_id\": sessionID,\n\t})\n}\n\nfunc (a *app) handleDevicePage(w http.ResponseWriter, r *http.Request) {\n\ta.deviceFlowMutex.Lock()\n\tdata := devicePageData{\n\t\tSessionID:       a.deviceFlowData.sessionID,\n\t\tDeviceCode:      a.deviceFlowData.deviceCode,\n\t\tUserCode:        a.deviceFlowData.userCode,\n\t\tVerificationURI: a.deviceFlowData.verificationURI,\n\t\tPollInterval:    a.deviceFlowData.pollInterval,\n\t\tLogoURI:         dexLogoDataURI,\n\t}\n\ta.deviceFlowMutex.Unlock()\n\n\tif data.DeviceCode == \"\" {\n\t\thttp.Error(w, \"No device flow in progress\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\trenderDevice(w, data)\n}\n\nfunc (a *app) handleDevicePoll(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\tvar req struct {\n\t\tDeviceCode string `json:\"device_code\"`\n\t\tSessionID  string `json:\"session_id\"`\n\t}\n\n\tif err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n\t\thttp.Error(w, \"Invalid request\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\ta.deviceFlowMutex.Lock()\n\tstoredSessionID := a.deviceFlowData.sessionID\n\tstoredDeviceCode := a.deviceFlowData.deviceCode\n\texistingToken := a.deviceFlowData.token\n\ta.deviceFlowMutex.Unlock()\n\n\t// Check if this session has been superseded by a new one\n\tif req.SessionID != storedSessionID {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(http.StatusGone)\n\t\tjson.NewEncoder(w).Encode(map[string]interface{}{\n\t\t\t\"error\":             \"session_expired\",\n\t\t\t\"error_description\": \"This device flow session has been superseded by a new one\",\n\t\t})\n\t\treturn\n\t}\n\n\tif req.DeviceCode != storedDeviceCode {\n\t\thttp.Error(w, \"Invalid device code\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// If we already have a token, return success\n\tif existingToken != nil {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(map[string]string{\n\t\t\t\"status\": \"complete\",\n\t\t})\n\t\treturn\n\t}\n\n\t// Poll the token endpoint\n\ttokenURL := a.provider.Endpoint().TokenURL\n\n\tdata := url.Values{}\n\tdata.Set(\"grant_type\", \"urn:ietf:params:oauth:grant-type:device_code\")\n\tdata.Set(\"device_code\", req.DeviceCode)\n\tdata.Set(\"client_id\", a.clientID)\n\tdata.Set(\"client_secret\", a.clientSecret)\n\n\ttokenResp, err := a.client.PostForm(tokenURL, data)\n\tif err != nil {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(map[string]string{\n\t\t\t\"status\": \"pending\",\n\t\t})\n\t\treturn\n\t}\n\tdefer tokenResp.Body.Close()\n\n\tif tokenResp.StatusCode == http.StatusOK {\n\t\t// Success! We got the token\n\t\t// Parse the full response including id_token\n\t\tvar tokenData struct {\n\t\t\tAccessToken  string `json:\"access_token\"`\n\t\t\tTokenType    string `json:\"token_type\"`\n\t\t\tRefreshToken string `json:\"refresh_token\"`\n\t\t\tExpiresIn    int    `json:\"expires_in\"`\n\t\t\tIDToken      string `json:\"id_token\"`\n\t\t}\n\n\t\tif err := json.NewDecoder(tokenResp.Body).Decode(&tokenData); err != nil {\n\t\t\thttp.Error(w, \"Failed to decode token\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\t// Create oauth2.Token with all fields\n\t\ttoken := &oauth2.Token{\n\t\t\tAccessToken:  tokenData.AccessToken,\n\t\t\tTokenType:    tokenData.TokenType,\n\t\t\tRefreshToken: tokenData.RefreshToken,\n\t\t}\n\n\t\t// Add id_token to Extra\n\t\ttoken = token.WithExtra(map[string]interface{}{\n\t\t\t\"id_token\": tokenData.IDToken,\n\t\t})\n\n\t\t// Store the token\n\t\ta.deviceFlowMutex.Lock()\n\t\ta.deviceFlowData.token = token\n\t\ta.deviceFlowMutex.Unlock()\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tjson.NewEncoder(w).Encode(map[string]string{\n\t\t\t\"status\": \"complete\",\n\t\t})\n\t\treturn\n\t}\n\n\t// Check for errors\n\tvar errorResp struct {\n\t\tError            string `json:\"error\"`\n\t\tErrorDescription string `json:\"error_description\"`\n\t}\n\n\tif err := json.NewDecoder(tokenResp.Body).Decode(&errorResp); err == nil {\n\t\tif errorResp.Error == \"authorization_pending\" {\n\t\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t\tjson.NewEncoder(w).Encode(map[string]string{\n\t\t\t\t\"status\": \"pending\",\n\t\t\t})\n\t\t\treturn\n\t\t}\n\n\t\t// Other errors\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.WriteHeader(tokenResp.StatusCode)\n\t\tjson.NewEncoder(w).Encode(map[string]interface{}{\n\t\t\t\"error\":             errorResp.Error,\n\t\t\t\"error_description\": errorResp.ErrorDescription,\n\t\t})\n\t\treturn\n\t}\n\n\t// Unknown response\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(map[string]string{\n\t\t\"status\": \"pending\",\n\t})\n}\n\nfunc (a *app) handleDeviceResult(w http.ResponseWriter, r *http.Request) {\n\ta.deviceFlowMutex.Lock()\n\ttoken := a.deviceFlowData.token\n\ta.deviceFlowMutex.Unlock()\n\n\tif token == nil {\n\t\thttp.Error(w, \"No token available\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tparseAndRenderToken(w, r, a, token)\n}\n"
  },
  {
    "path": "examples/example-app/handlers_userinfo.go",
    "content": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\nfunc (a *app) handleUserInfo(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\t// Parse form to get access token\n\tif err := r.ParseForm(); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to parse form: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\taccessToken := r.FormValue(\"access_token\")\n\tif accessToken == \"\" {\n\t\thttp.Error(w, \"access_token is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Get UserInfo endpoint from provider\n\tuserInfoEndpoint := a.provider.Endpoint().AuthURL\n\tif len(userInfoEndpoint) > 5 {\n\t\t// Replace /auth with /userinfo\n\t\tuserInfoEndpoint = userInfoEndpoint[:len(userInfoEndpoint)-5] + \"/userinfo\"\n\t}\n\n\t// Create request to UserInfo endpoint\n\treq, err := http.NewRequestWithContext(r.Context(), \"GET\", userInfoEndpoint, nil)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to create request: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Add Authorization header with access token\n\treq.Header.Set(\"Authorization\", \"Bearer \"+accessToken)\n\n\t// Make the request\n\tresp, err := a.client.Do(req)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to fetch userinfo: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, _ := io.ReadAll(resp.Body)\n\t\thttp.Error(w, fmt.Sprintf(\"UserInfo request failed: %s\", string(body)), resp.StatusCode)\n\t\treturn\n\t}\n\n\t// Parse and return the userinfo\n\tvar userInfo map[string]interface{}\n\tif err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to decode userinfo: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(userInfo)\n}\n"
  },
  {
    "path": "examples/example-app/main.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"sync\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"github.com/spf13/cobra\"\n\t\"golang.org/x/oauth2\"\n)\n\nconst exampleAppState = \"I wish to wash my irish wristwatch\"\n\nvar (\n\tcodeVerifier  string\n\tcodeChallenge string\n)\n\nfunc init() {\n\tcodeVerifier = oauth2.GenerateVerifier()\n\tcodeChallenge = oauth2.S256ChallengeFromVerifier(codeVerifier)\n}\n\ntype app struct {\n\tclientID     string\n\tclientSecret string\n\tpkce         bool\n\tredirectURI  string\n\n\tverifier        *oidc.IDTokenVerifier\n\tprovider        *oidc.Provider\n\tscopesSupported []string\n\n\t// Does the provider use \"offline_access\" scope to request a refresh token\n\t// or does it use \"access_type=offline\" (e.g. Google)?\n\tofflineAsScope bool\n\n\tclient *http.Client\n\n\t// Device flow state\n\t// Only one session is possible at a time\n\t// Since it is an example, we don't bother locking', this is a simplicity tradeoff\n\tdeviceFlowMutex sync.Mutex\n\tdeviceFlowData  struct {\n\t\tsessionID       string // Unique ID for current flow session\n\t\tdeviceCode      string\n\t\tuserCode        string\n\t\tverificationURI string\n\t\tpollInterval    int\n\t\ttoken           *oauth2.Token\n\t}\n}\n\nfunc cmd() *cobra.Command {\n\tvar (\n\t\ta         app\n\t\tissuerURL string\n\t\tlisten    string\n\t\ttlsCert   string\n\t\ttlsKey    string\n\t\trootCAs   string\n\t\tdebug     bool\n\t)\n\tc := cobra.Command{\n\t\tUse:   \"example-app\",\n\t\tShort: \"An example OpenID Connect client\",\n\t\tLong:  \"\",\n\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\tif len(args) != 0 {\n\t\t\t\treturn errors.New(\"surplus arguments provided\")\n\t\t\t}\n\n\t\t\tu, err := url.Parse(a.redirectURI)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"parse redirect-uri: %v\", err)\n\t\t\t}\n\t\t\tlistenURL, err := url.Parse(listen)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"parse listen address: %v\", err)\n\t\t\t}\n\n\t\t\tif rootCAs != \"\" {\n\t\t\t\tclient, err := httpClientForRootCAs(rootCAs)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\ta.client = client\n\t\t\t}\n\n\t\t\tif debug {\n\t\t\t\tif a.client == nil {\n\t\t\t\t\ta.client = &http.Client{\n\t\t\t\t\t\tTransport: debugTransport{http.DefaultTransport},\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\ta.client.Transport = debugTransport{a.client.Transport}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif a.client == nil {\n\t\t\t\ta.client = http.DefaultClient\n\t\t\t}\n\n\t\t\t// TODO(ericchiang): Retry with backoff\n\t\t\tctx := oidc.ClientContext(context.Background(), a.client)\n\t\t\tprovider, err := oidc.NewProvider(ctx, issuerURL)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to query provider %q: %v\", issuerURL, err)\n\t\t\t}\n\n\t\t\tvar s struct {\n\t\t\t\t// What scopes does a provider support?\n\t\t\t\t//\n\t\t\t\t// See: https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata\n\t\t\t\tScopesSupported []string `json:\"scopes_supported\"`\n\t\t\t}\n\t\t\tif err := provider.Claims(&s); err != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to parse provider scopes_supported: %v\", err)\n\t\t\t}\n\n\t\t\tif len(s.ScopesSupported) == 0 {\n\t\t\t\t// scopes_supported is a \"RECOMMENDED\" discovery claim, not a required\n\t\t\t\t// one. If missing, assume that the provider follows the spec and has\n\t\t\t\t// an \"offline_access\" scope.\n\t\t\t\ta.offlineAsScope = true\n\t\t\t} else {\n\t\t\t\t// See if scopes_supported has the \"offline_access\" scope.\n\t\t\t\ta.offlineAsScope = func() bool {\n\t\t\t\t\tfor _, scope := range s.ScopesSupported {\n\t\t\t\t\t\tif scope == oidc.ScopeOfflineAccess {\n\t\t\t\t\t\t\treturn true\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn false\n\t\t\t\t}()\n\t\t\t}\n\n\t\t\ta.provider = provider\n\t\t\ta.verifier = provider.Verifier(&oidc.Config{ClientID: a.clientID})\n\t\t\ta.scopesSupported = s.ScopesSupported\n\n\t\t\thttp.Handle(\"/static/\", http.StripPrefix(\"/static/\", staticHandler))\n\t\t\thttp.HandleFunc(\"/\", a.handleIndex)\n\t\t\thttp.HandleFunc(\"/login\", a.handleLogin)\n\t\t\thttp.HandleFunc(\"/device/login\", a.handleDeviceLogin)\n\t\t\thttp.HandleFunc(\"/device\", a.handleDevicePage)\n\t\t\thttp.HandleFunc(\"/device/poll\", a.handleDevicePoll)\n\t\t\thttp.HandleFunc(\"/device/result\", a.handleDeviceResult)\n\t\t\thttp.HandleFunc(\"/userinfo\", a.handleUserInfo)\n\t\t\thttp.HandleFunc(u.Path, a.handleCallback)\n\n\t\t\tswitch listenURL.Scheme {\n\t\t\tcase \"http\":\n\t\t\t\tlog.Printf(\"listening on %s\", listen)\n\t\t\t\treturn http.ListenAndServe(listenURL.Host, nil)\n\t\t\tcase \"https\":\n\t\t\t\tlog.Printf(\"listening on %s\", listen)\n\t\t\t\treturn http.ListenAndServeTLS(listenURL.Host, tlsCert, tlsKey, nil)\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"listen address %q is not using http or https\", listen)\n\t\t\t}\n\t\t},\n\t}\n\tc.Flags().StringVar(&a.clientID, \"client-id\", \"example-app\", \"OAuth2 client ID of this application.\")\n\tc.Flags().StringVar(&a.clientSecret, \"client-secret\", \"ZXhhbXBsZS1hcHAtc2VjcmV0\", \"OAuth2 client secret of this application.\")\n\tc.Flags().BoolVar(&a.pkce, \"pkce\", true, \"Use PKCE flow for the code exchange.\")\n\tc.Flags().StringVar(&a.redirectURI, \"redirect-uri\", \"http://127.0.0.1:5555/callback\", \"Callback URL for OAuth2 responses.\")\n\tc.Flags().StringVar(&issuerURL, \"issuer\", \"http://127.0.0.1:5556/dex\", \"URL of the OpenID Connect issuer.\")\n\tc.Flags().StringVar(&listen, \"listen\", \"http://127.0.0.1:5555\", \"HTTP(S) address to listen at.\")\n\tc.Flags().StringVar(&tlsCert, \"tls-cert\", \"\", \"X509 cert file to present when serving HTTPS.\")\n\tc.Flags().StringVar(&tlsKey, \"tls-key\", \"\", \"Private key for the HTTPS cert.\")\n\tc.Flags().StringVar(&rootCAs, \"issuer-root-ca\", \"\", \"Root certificate authorities for the issuer. Defaults to host certs.\")\n\tc.Flags().BoolVar(&debug, \"debug\", false, \"Print all request and responses from the OpenID Connect issuer.\")\n\treturn &c\n}\n\nfunc main() {\n\tif err := cmd().Execute(); err != nil {\n\t\tfmt.Fprintf(os.Stderr, \"error: %v\\n\", err)\n\t\tos.Exit(2)\n\t}\n}\n"
  },
  {
    "path": "examples/example-app/static/app.js",
    "content": "(function() {\n    const crossClientInput = document.getElementById(\"cross_client_input\");\n    const crossClientList = document.getElementById(\"cross-client-list\");\n    const addClientBtn = document.getElementById(\"add-cross-client\");\n    const scopesList = document.getElementById(\"scopes-list\");\n    const customScopeInput = document.getElementById(\"custom_scope_input\");\n    const addCustomScopeBtn = document.getElementById(\"add-custom-scope\");\n\n    // Default scopes that should be checked by default\n    const defaultScopes = [\"openid\", \"profile\", \"email\", \"offline_access\"];\n\n    // Check default scopes on page load\n    document.addEventListener(\"DOMContentLoaded\", function() {\n        const checkboxes = scopesList.querySelectorAll('input[type=\"checkbox\"]');\n        checkboxes.forEach(cb => {\n            if (defaultScopes.includes(cb.value)) {\n                cb.checked = true;\n            }\n        });\n    });\n\n    function addCrossClient(value) {\n        const trimmed = value.trim();\n        if (!trimmed) return;\n\n        const chip = document.createElement(\"div\");\n        chip.className = \"chip\";\n\n        const text = document.createElement(\"span\");\n        text.textContent = trimmed;\n\n        const hidden = document.createElement(\"input\");\n        hidden.type = \"hidden\";\n        hidden.name = \"cross_client\";\n        hidden.value = trimmed;\n\n        const remove = document.createElement(\"button\");\n        remove.type = \"button\";\n        remove.textContent = \"×\";\n        remove.onclick = () => crossClientList.removeChild(chip);\n\n        chip.append(text, hidden, remove);\n        crossClientList.appendChild(chip);\n    }\n\n    function addCustomScope(scope) {\n        const trimmed = scope.trim();\n        if (!trimmed || !scopesList) return;\n\n        // Check if scope already exists\n        const existingCheckboxes = scopesList.querySelectorAll('input[type=\"checkbox\"]');\n        for (const cb of existingCheckboxes) {\n            if (cb.value === trimmed) {\n                cb.checked = true;\n                return;\n            }\n        }\n\n        // Add new scope checkbox\n        const scopeItem = document.createElement(\"div\");\n        scopeItem.className = \"scope-item\";\n\n        const checkbox = document.createElement(\"input\");\n        checkbox.type = \"checkbox\";\n        checkbox.name = \"extra_scopes\";\n        checkbox.value = trimmed;\n        checkbox.id = \"scope_custom_\" + trimmed;\n        checkbox.checked = true;\n\n        const label = document.createElement(\"label\");\n        label.htmlFor = checkbox.id;\n        label.textContent = trimmed;\n\n        scopeItem.append(checkbox, label);\n        scopesList.appendChild(scopeItem);\n    }\n\n    addClientBtn?.addEventListener(\"click\", () => {\n        addCrossClient(crossClientInput.value);\n        crossClientInput.value = \"\";\n        crossClientInput.focus();\n    });\n\n    crossClientInput?.addEventListener(\"keydown\", (e) => {\n        if (e.key === \"Enter\") {\n            e.preventDefault();\n            addCrossClient(crossClientInput.value);\n            crossClientInput.value = \"\";\n        }\n    });\n\n    addCustomScopeBtn?.addEventListener(\"click\", () => {\n        addCustomScope(customScopeInput.value);\n        customScopeInput.value = \"\";\n        customScopeInput.focus();\n    });\n\n    customScopeInput?.addEventListener(\"keydown\", (e) => {\n        if (e.key === \"Enter\") {\n            e.preventDefault();\n            addCustomScope(customScopeInput.value);\n            customScopeInput.value = \"\";\n        }\n    });\n\n    // Device Grant Login Handler\n    const deviceGrantBtn = document.getElementById(\"device-grant-btn\");\n    deviceGrantBtn?.addEventListener(\"click\", async () => {\n        deviceGrantBtn.disabled = true;\n        deviceGrantBtn.textContent = \"Loading...\";\n\n        try {\n            // Collect form data similar to regular login\n            const form = document.getElementById(\"login-form\");\n            const formData = new FormData(form);\n\n            // Get selected scopes\n            const scopes = formData.getAll(\"extra_scopes\");\n\n            // Get cross-client values\n            const crossClients = formData.getAll(\"cross_client\");\n\n            // Get connector_id if specified\n            const connectorId = formData.get(\"connector_id\") || \"\";\n\n            // Initiate device flow with options\n            const response = await fetch('/device/login', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application/json',\n                },\n                body: JSON.stringify({\n                    scopes: scopes,\n                    cross_clients: crossClients,\n                    connector_id: connectorId\n                })\n            });\n\n            if (response.ok) {\n                // Redirect to device flow page\n                window.location.href = '/device';\n            } else {\n                const errorText = await response.text();\n                alert('Failed to start device flow: ' + errorText);\n            }\n        } catch (error) {\n            alert('Error starting device flow: ' + error.message);\n        } finally {\n            deviceGrantBtn.disabled = false;\n            deviceGrantBtn.textContent = \"Device Code Flow\";\n        }\n    });\n})();\n\n"
  },
  {
    "path": "examples/example-app/static/device.js",
    "content": "(function() {\n    const sessionID = document.getElementById(\"session-id\")?.value;\n    const deviceCode = document.getElementById(\"device-code\")?.value;\n    const pollInterval = parseInt(document.getElementById(\"poll-interval\")?.value || \"5\", 10);\n    const verificationURL = document.getElementById(\"verification-url\")?.textContent;\n    const userCode = document.getElementById(\"user-code\")?.textContent;\n    const statusText = document.getElementById(\"status-text\");\n    const errorMessage = document.getElementById(\"error-message\");\n    const openAuthBtn = document.getElementById(\"open-auth-btn\");\n\n    let pollTimer = null;\n\n    document.querySelectorAll(\".copy-btn\").forEach(btn => {\n        btn.addEventListener(\"click\", async function() {\n            const targetId = this.getAttribute(\"data-copy\");\n            const targetElement = document.getElementById(targetId);\n\n            if (targetElement) {\n                const textToCopy = targetElement.textContent;\n\n                try {\n                    await navigator.clipboard.writeText(textToCopy);\n                    const originalText = this.textContent;\n                    this.textContent = \"✓\";\n                    setTimeout(() => {\n                        this.textContent = originalText;\n                    }, 2000);\n                } catch (err) {\n                    console.error('Failed to copy:', err);\n                }\n            }\n        });\n    });\n\n    openAuthBtn?.addEventListener(\"click\", () => {\n        if (verificationURL && userCode) {\n            const url = verificationURL + \"?user_code=\" + encodeURIComponent(userCode);\n            window.open(url, \"_blank\", \"width=600,height=800\");\n        }\n    });\n\n    async function pollForToken() {\n        try {\n            const response = await fetch('/device/poll', {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application/json',\n                },\n                body: JSON.stringify({\n                    session_id: sessionID,\n                    device_code: deviceCode\n                })\n            });\n\n            const data = await response.json();\n\n            if (response.ok && data.status === 'complete') {\n                statusText.textContent = \"Authentication successful! Redirecting...\";\n                stopPolling();\n                window.location.href = '/device/result';\n            } else if (response.ok && data.status === 'pending') {\n                statusText.textContent = \"Waiting for authentication...\";\n            } else {\n                const errorText = data.error_description || data.error || 'Unknown error';\n\n                if (data.error === 'session_expired') {\n                    showError('This session has been superseded by a new device flow. Please start over.');\n                    stopPolling();\n                } else if (data.error === 'expired_token' || data.error === 'access_denied') {\n                    showError(data.error === 'expired_token' ?\n                        'The device code has expired. Please start over.' :\n                        'Authentication was denied.');\n                    stopPolling();\n                }\n            }\n        } catch (error) {\n            console.error('Polling error:', error);\n        }\n    }\n\n    function showError(message) {\n        errorMessage.textContent = message;\n        errorMessage.style.display = 'block';\n\n        // Hide the status indicator (contains spinner and status text)\n        const statusIndicator = document.querySelector('.status-indicator');\n        if (statusIndicator) {\n            statusIndicator.style.display = 'none';\n        }\n    }\n\n    function startPolling() {\n        pollForToken();\n        pollTimer = setInterval(pollForToken, pollInterval * 1000);\n    }\n\n    function stopPolling() {\n        if (pollTimer) {\n            clearInterval(pollTimer);\n            pollTimer = null;\n        }\n    }\n\n    if (deviceCode) {\n        startPolling();\n    }\n\n    window.addEventListener('beforeunload', stopPolling);\n})();\n\n"
  },
  {
    "path": "examples/example-app/static/style.css",
    "content": "body {\n    font-family: Arial, sans-serif;\n    background-color: #f2f2f2;\n    margin: 0;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    min-height: 100vh;\n    flex-direction: column;\n    padding: 20px;\n}\n\n/* Token page layout - no centering */\nbody.token-page {\n    display: block;\n    align-items: flex-start;\n    justify-content: flex-start;\n    padding: 20px;\n}\n\n.container {\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n    width: 100%;\n    max-width: 600px;\n}\n\n.logo {\n    max-width: 100%;\n    width: 200px;\n    height: auto;\n    margin-bottom: 30px;\n    display: block;\n}\n\nform {\n    background-color: #fff;\n    padding: 30px;\n    border-radius: 8px;\n    width: 100%;\n}\n\n/* Shadow only for main login form */\n.container form {\n    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n}\n\n.primary-action {\n    margin-bottom: 20px;\n}\n\n.advanced {\n    margin-top: 20px;\n    background: #f9fbfd;\n    border: 1px solid #dbe7f3;\n    border-radius: 6px;\n    padding: 15px;\n}\n\n.advanced summary {\n    cursor: pointer;\n    font-weight: bold;\n    color: #3F9FD8;\n    user-select: none;\n    text-align: center;\n}\n\n.advanced summary:hover {\n    color: #357FAA;\n}\n\n.app-description {\n    text-align: center;\n    color: #666;\n    font-size: 14px;\n    margin-bottom: 25px;\n    line-height: 1.5;\n}\n\n.app-description a {\n    color: #3F9FD8;\n    text-decoration: none;\n}\n\n.app-description a:hover {\n    text-decoration: underline;\n}\n\n.field {\n    margin-top: 15px;\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n}\n\n.field label {\n    font-weight: 600;\n    color: #333;\n    font-size: 14px;\n}\n\n.inline-input {\n    display: flex;\n    gap: 8px;\n}\n\n.inline-input input[type=\"text\"] {\n    flex: 1;\n}\n\ninput[type=\"text\"] {\n    padding: 10px 12px;\n    border: 1px solid #ddd;\n    border-radius: 4px;\n    font-size: 14px;\n    outline: none;\n    transition: border-color 0.2s;\n}\n\ninput[type=\"text\"]:focus {\n    border-color: #3F9FD8;\n}\n\n.checkbox-field {\n    flex-direction: row;\n    align-items: center;\n    gap: 8px;\n    margin-top: 10px;\n}\n\n.checkbox-field label {\n    margin: 0;\n    font-weight: 500;\n}\n\ninput[type=\"checkbox\"] {\n    width: 18px;\n    height: 18px;\n    cursor: pointer;\n}\n\n.scopes-list {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n    max-height: 200px;\n    overflow-y: auto;\n    padding: 10px;\n    background: white;\n    border: 1px solid #ddd;\n    border-radius: 4px;\n}\n\n.scope-item {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n}\n\n.scope-item input[type=\"checkbox\"] {\n    margin: 0;\n}\n\n.scope-item label {\n    margin: 0;\n    font-weight: 400;\n    font-size: 13px;\n    cursor: pointer;\n}\n\ninput[type=\"submit\"], button {\n    padding: 12px 20px;\n    background-color: #3F9FD8;\n    color: white;\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 14px;\n    font-weight: 500;\n    transition: background-color 0.2s;\n}\n\n/* Full width submit button only on main login form */\nform input[type=\"submit\"] {\n    width: 100%;\n    font-size: 16px;\n}\n\n/* Token page forms should have auto-width buttons */\nbody.token-page input[type=\"submit\"] {\n    width: auto;\n    font-size: 14px;\n}\n\ninput[type=\"submit\"]:hover, button:hover {\n    background-color: #357FAA;\n}\n\nbutton {\n    white-space: nowrap;\n}\n\n.chip-list {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 8px;\n    min-height: 32px;\n    margin-top: 8px;\n}\n\n.chip {\n    display: inline-flex;\n    align-items: center;\n    gap: 6px;\n    background: #e9f4fb;\n    border: 1px solid #cde5f5;\n    border-radius: 16px;\n    padding: 6px 12px;\n    font-size: 13px;\n}\n\n.chip button {\n    border: none;\n    background: transparent;\n    cursor: pointer;\n    font-weight: bold;\n    color: #3F9FD8;\n    padding: 0;\n    width: 20px;\n    height: 20px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border-radius: 50%;\n}\n\n.chip button:hover {\n    background: #d0e8f5;\n}\n\n.hint {\n    font-size: 12px;\n    color: #666;\n    margin-top: 4px;\n}\n\n.copy-btn {\n    padding: 4px 10px;\n    background-color: #3F9FD8;\n    color: white;\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 11px;\n    transition: background-color 0.2s;\n    margin-left: 8px;\n    white-space: nowrap;\n}\n\n.copy-btn:hover {\n    background-color: #357FAA;\n}\n\n/* Token page styles */\n.back-button {\n    display: inline-block;\n    padding: 8px 16px;\n    background-color: #EF4B5C;\n    color: white;\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 12px;\n    text-decoration: none;\n    transition: background-color 0.3s ease, transform 0.2s ease;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n    position: fixed;\n    right: 20px;\n    bottom: 20px;\n}\n\n.back-button:hover {\n    background-color: #C43B4B;\n}\n\n.token-block {\n    background-color: #fff;\n    padding: 10px 15px;\n    border-radius: 8px;\n    margin-bottom: 15px;\n    word-wrap: break-word;\n    display: flex;\n    flex-direction: column;\n    gap: 5px;\n    position: relative;\n    overflow: hidden;\n    border: 1px solid #e0e0e0;\n}\n\n.token-block form {\n    margin: 10px 0 0 0;\n    padding: 0;\n    background-color: transparent;\n    box-shadow: none;\n    border-radius: 0;\n}\n\n.token-title {\n    font-weight: bold;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n}\n\n.token-title a {\n    font-size: 0.9em;\n    text-decoration: none;\n    color: #3F9FD8;\n}\n\n.token-title a:hover {\n    text-decoration: underline;\n}\n\n.token-code {\n    overflow-wrap: break-word;\n    word-break: break-all;\n    white-space: normal;\n}\n\npre {\n    white-space: pre-wrap;\n    background-color: #f9f9f9;\n    padding: 8px;\n    border-radius: 4px;\n    border: 1px solid #ddd;\n    margin: 0;\n    font-family: 'Courier New', Courier, monospace;\n    overflow-x: auto;\n    font-size: 0.9em;\n    position: relative;\n    margin-top: 5px;\n}\n\npre .key {\n    color: #c00;\n}\n\npre .string {\n    color: #080;\n}\n\npre .number {\n    color: #00f;\n}\n\n/* Login Buttons Styles */\n.login-buttons {\n    display: flex;\n    flex-direction: column;\n    gap: 12px;\n    margin-bottom: 20px;\n}\n\n.login-button {\n    width: 100%;\n    padding: 14px 24px;\n    font-size: 16px;\n    border-radius: 4px;\n    cursor: pointer;\n    transition: all 0.3s ease;\n    font-weight: 600;\n    border: 2px solid #3F9FD8;\n}\n\n.login-button.primary {\n    background-color: #3F9FD8;\n    color: #fff;\n}\n\n.login-button.primary:hover {\n    background-color: #357FAA;\n    border-color: #357FAA;\n}\n\n.login-button.secondary {\n    background-color: #fff;\n    color: #3F9FD8;\n}\n\n.login-button.secondary:hover {\n    background-color: #f0f8ff;\n}\n\n/* Device Flow Page Styles */\n.device-flow-container {\n    background-color: #fff;\n    padding: 30px;\n    border-radius: 8px;\n    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n    width: 100%;\n}\n\n.device-instructions h2 {\n    margin-top: 0;\n    color: #333;\n    text-align: center;\n}\n\n.instruction-text {\n    text-align: center;\n    color: #666;\n    margin-bottom: 25px;\n}\n\n.verification-info {\n    display: flex;\n    flex-direction: column;\n    gap: 20px;\n    margin-bottom: 25px;\n}\n\n.info-item {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n}\n\n.info-item label {\n    font-weight: 600;\n    color: #333;\n    font-size: 14px;\n}\n\n.code-display {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    background-color: #f5f5f5;\n    padding: 12px;\n    border-radius: 4px;\n    border: 1px solid #ddd;\n}\n\n.code-display.large {\n    padding: 20px;\n}\n\n.code-display code {\n    flex: 1;\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 14px;\n    word-break: break-all;\n}\n\n.code-display code.user-code {\n    font-size: 24px;\n    font-weight: bold;\n    letter-spacing: 2px;\n    color: #3F9FD8;\n}\n\n.copy-btn {\n    background: none;\n    border: none;\n    cursor: pointer;\n    font-size: 18px;\n    padding: 5px 10px;\n    border-radius: 4px;\n    transition: background-color 0.2s;\n}\n\n.copy-btn:hover {\n    background-color: #e0e0e0;\n}\n\n.copy-btn:active {\n    background-color: #d0d0d0;\n}\n\n.actions {\n    text-align: center;\n}\n\n.primary-button {\n    padding: 12px 32px;\n    font-size: 16px;\n    background-color: #3F9FD8;\n    color: #fff;\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    font-weight: 600;\n    transition: background-color 0.3s;\n}\n\n.primary-button:hover {\n    background-color: #357FAA;\n}\n\n.polling-status {\n    margin-top: 30px;\n    padding-top: 30px;\n    border-top: 1px solid #eee;\n}\n\n.status-indicator {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 15px;\n    color: #666;\n}\n\n.spinner {\n    width: 20px;\n    height: 20px;\n    border: 3px solid #f3f3f3;\n    border-top: 3px solid #3F9FD8;\n    border-radius: 50%;\n    animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n    0% { transform: rotate(0deg); }\n    100% { transform: rotate(360deg); }\n}\n\n.error-message {\n    margin-top: 15px;\n    padding: 12px;\n    background-color: #fee;\n    border: 1px solid #fcc;\n    border-radius: 4px;\n    color: #c00;\n    text-align: center;\n}\n\n.device-data {\n    display: none;\n}\n\n/* UserInfo Styles */\n#userinfo-section {\n    margin-top: 10px;\n}\n\n.fetch-userinfo-btn {\n    padding: 10px 20px;\n    background-color: #3F9FD8;\n    color: white;\n    border: none;\n    border-radius: 4px;\n    cursor: pointer;\n    font-size: 14px;\n    font-weight: 500;\n    transition: background-color 0.2s;\n}\n\n.fetch-userinfo-btn:hover {\n    background-color: #357FAA;\n}\n\n.userinfo-loading {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n    color: #666;\n    margin-top: 10px;\n}\n\n.userinfo-loading .spinner {\n    width: 16px;\n    height: 16px;\n    border: 2px solid #f3f3f3;\n    border-top: 2px solid #3F9FD8;\n    border-radius: 50%;\n    animation: spin 1s linear infinite;\n}\n\n#userinfo-claims {\n    margin-top: 15px;\n}\n\n#userinfo-error {\n    margin-top: 10px;\n}\n\n"
  },
  {
    "path": "examples/example-app/static/token.js",
    "content": "// Simple JSON syntax highlighter\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n    const claimsElement = document.getElementById(\"claims\");\n    if (claimsElement) {\n        try {\n            const json = JSON.parse(claimsElement.textContent);\n            claimsElement.innerHTML = syntaxHighlight(json);\n        } catch (e) {\n            console.error(\"Invalid JSON in claims:\", e);\n        }\n    }\n});\n\nfunction syntaxHighlight(json) {\n    if (typeof json != 'string') {\n        json = JSON.stringify(json, undefined, 2);\n    }\n    json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');\n    return json.replace(/(\"(\\\\u[\\da-fA-F]{4}|\\\\[^u]|[^\\\\\"])*\"(\\s*:)?|\\b(true|false|null)\\b|\\b\\d+\\b)/g, function (match) {\n        let cls = 'number';\n        if (/^\"/.test(match)) {\n            if (/:$/.test(match)) {\n                cls = 'key';\n            } else {\n                cls = 'string';\n            }\n        } else if (/true|false/.test(match)) {\n            cls = 'boolean';\n        } else if (/null/.test(match)) {\n            cls = 'null';\n        }\n        return '<span class=\"' + cls + '\">' + match + '</span>';\n    });\n}\n\nfunction copyPublicKey() {\n    const publicKeyElement = document.getElementById(\"public-key\");\n    if (!publicKeyElement) return;\n\n    const text = publicKeyElement.textContent;\n\n    // Use modern clipboard API if available\n    if (navigator.clipboard && navigator.clipboard.writeText) {\n        navigator.clipboard.writeText(text).then(() => {\n            showCopyFeedback(\"Copied!\");\n        }).catch(err => {\n            console.error(\"Failed to copy:\", err);\n            fallbackCopy(text);\n        });\n    } else {\n        fallbackCopy(text);\n    }\n}\n\nfunction fallbackCopy(text) {\n    const textarea = document.createElement(\"textarea\");\n    textarea.value = text;\n    textarea.style.position = \"fixed\";\n    textarea.style.opacity = \"0\";\n    document.body.appendChild(textarea);\n    textarea.select();\n    try {\n        document.execCommand(\"copy\");\n        showCopyFeedback(\"Copied!\");\n    } catch (err) {\n        console.error(\"Fallback copy failed:\", err);\n        showCopyFeedback(\"Failed to copy\");\n    }\n    document.body.removeChild(textarea);\n}\n\nfunction showCopyFeedback(message) {\n    const btn = event.target;\n    const originalText = btn.textContent;\n    btn.textContent = message;\n    btn.style.backgroundColor = \"#28a745\";\n    setTimeout(() => {\n        btn.textContent = originalText;\n        btn.style.backgroundColor = \"\";\n    }, 2000);\n}\n\n// UserInfo functionality\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n    const form = document.getElementById(\"userinfo-form\");\n    if (form) {\n        form.addEventListener(\"submit\", fetchUserInfo);\n    }\n});\n\nasync function fetchUserInfo(event) {\n    event.preventDefault();\n\n    const form = event.target;\n    const loading = document.getElementById(\"userinfo-loading\");\n    const error = document.getElementById(\"userinfo-error\");\n    const claimsElement = document.getElementById(\"userinfo-claims\");\n    const submitButton = form.querySelector('button[type=\"submit\"]');\n\n    // Hide error and claims from previous attempts\n    error.style.display = \"none\";\n    claimsElement.style.display = \"none\";\n\n    // Show loading, hide button\n    submitButton.style.display = \"none\";\n    loading.style.display = \"flex\";\n\n    try {\n        const formData = new FormData(form);\n\n        // Convert FormData to URL-encoded string\n        const urlEncodedData = new URLSearchParams(formData).toString();\n\n        const response = await fetch(\"/userinfo\", {\n            method: \"POST\",\n            headers: {\n                \"Content-Type\": \"application/x-www-form-urlencoded\"\n            },\n            body: urlEncodedData\n        });\n\n        if (!response.ok) {\n            const errorText = await response.text();\n            throw new Error(errorText || `HTTP ${response.status}`);\n        }\n\n        const userinfo = await response.json();\n\n        // Display the userinfo claims\n        const code = claimsElement.querySelector(\"code\");\n        const formattedJson = JSON.stringify(userinfo, null, 2);\n        code.textContent = formattedJson;\n\n        // Apply syntax highlighting\n        try {\n            code.innerHTML = syntaxHighlight(userinfo);\n        } catch (e) {\n            console.error(\"Failed to highlight JSON:\", e);\n        }\n\n        claimsElement.style.display = \"block\";\n\n    } catch (err) {\n        console.error(\"Failed to fetch userinfo:\", err);\n        error.textContent = \"Failed to fetch UserInfo: \" + err.message;\n        error.style.display = \"block\";\n        submitButton.style.display = \"inline-block\";\n    } finally {\n        loading.style.display = \"none\";\n    }\n}\n\n"
  },
  {
    "path": "examples/example-app/templates/device.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Device Login - Example App</title>\n    <link rel=\"stylesheet\" href=\"/static/style.css\">\n</head>\n<body>\n    <div class=\"container\">\n        <img class=\"logo\" src=\"{{.LogoURI}}\" alt=\"Dex\">\n        <div class=\"device-flow-container\">\n            <div class=\"device-instructions\">\n                <h2>Device Login</h2>\n                <p class=\"instruction-text\">Please authenticate on your device:</p>\n\n                <div class=\"verification-info\">\n                    <div class=\"info-item\">\n                        <label>Verification URL:</label>\n                        <div class=\"code-display\">\n                            <code id=\"verification-url\">{{.VerificationURI}}</code>\n                            <button type=\"button\" class=\"copy-btn\" data-copy=\"verification-url\">📋</button>\n                        </div>\n                    </div>\n\n                    <div class=\"info-item\">\n                        <label>User Code:</label>\n                        <div class=\"code-display large\">\n                            <code id=\"user-code\" class=\"user-code\">{{.UserCode}}</code>\n                            <button type=\"button\" class=\"copy-btn\" data-copy=\"user-code\">📋</button>\n                        </div>\n                    </div>\n                </div>\n\n                <div class=\"actions\">\n                    <button type=\"button\" id=\"open-auth-btn\" class=\"primary-button\">\n                        Open Authentication Page\n                    </button>\n                </div>\n            </div>\n\n            <div class=\"polling-status\">\n                <div class=\"status-indicator\">\n                    <div class=\"spinner\"></div>\n                    <span id=\"status-text\">Waiting for authentication...</span>\n                </div>\n                <div id=\"error-message\" class=\"error-message\" style=\"display: none;\"></div>\n            </div>\n\n            <div class=\"device-data\" style=\"display: none;\">\n                <input type=\"hidden\" id=\"session-id\" value=\"{{.SessionID}}\">\n                <input type=\"hidden\" id=\"device-code\" value=\"{{.DeviceCode}}\">\n                <input type=\"hidden\" id=\"poll-interval\" value=\"{{.PollInterval}}\">\n            </div>\n        </div>\n    </div>\n\n    <script src=\"/static/device.js\"></script>\n</body>\n</html>\n\n"
  },
  {
    "path": "examples/example-app/templates/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Example App - Login</title>\n    <link rel=\"stylesheet\" href=\"/static/style.css\">\n</head>\n<body>\n    <div class=\"container\">\n        <img class=\"logo\" src=\"{{.LogoURI}}\" alt=\"Dex\">\n        <form id=\"login-form\" action=\"/login\" method=\"post\">\n            <div class=\"app-description\">\n                This is an example application for <b>Dex</b> OpenID Connect provider.<br>\n                Learn more in the <a href=\"https://dexidp.io/docs/\" target=\"_blank\">documentation</a>.\n            </div>\n            <div class=\"login-buttons\">\n                <button type=\"submit\" class=\"login-button primary-button\">\n                    Authorization Code Flow\n                </button>\n                <button type=\"button\" id=\"device-grant-btn\" class=\"login-button primary-button\">\n                    Device Code Flow\n                </button>\n            </div>\n            <details class=\"advanced\">\n                <summary>Advanced options</summary>\n                <div class=\"field\">\n                    <label>Scopes:</label>\n                    <small class=\"hint\">Select OpenID Connect scopes to request. Standard scopes are pre-selected.</small>\n                    <div class=\"scopes-list\" id=\"scopes-list\">\n                        {{range .ScopesSupported}}\n                        <div class=\"scope-item\">\n                            <input type=\"checkbox\" name=\"extra_scopes\" value=\"{{.}}\" id=\"scope_{{.}}\">\n                            <label for=\"scope_{{.}}\">{{.}}</label>\n                        </div>\n                        {{end}}\n                    </div>\n                    {{if eq (len .ScopesSupported) 0}}\n                    <div class=\"hint\">No scopes from discovery - add custom scopes below.</div>\n                    {{end}}\n                    <div class=\"inline-input\">\n                        <input type=\"text\" id=\"custom_scope_input\" placeholder=\"custom-scope\">\n                        <button type=\"button\" id=\"add-custom-scope\">Add scope</button>\n                    </div>\n                </div>\n                <div class=\"field\">\n                    <label for=\"cross_client_input\">Cross-Client auth:</label>\n                    <small class=\"hint\">Each client is sent as audience:server:client_id scope.</small>\n                    <div id=\"cross-client-list\"></div>\n                    <div class=\"inline-input\">\n                        <input type=\"text\" id=\"cross_client_input\" placeholder=\"client-id\">\n                        <button type=\"button\" id=\"add-cross-client\">Add</button>\n                    </div>\n                </div>\n                <div class=\"field\">\n                    <label for=\"connector_id\">Connector:</label>\n                    <small class=\"hint\">Specify a connector ID to bypass the connector selection screen.</small>\n                    <input type=\"text\" id=\"connector_id\" name=\"connector_id\" placeholder=\"connector id\">\n                </div>\n            </details>\n        </form>\n    </div>\n\n    <script src=\"/static/app.js\"></script>\n</body>\n</html>\n\n"
  },
  {
    "path": "examples/example-app/templates/token.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Tokens</title>\n    <link rel=\"stylesheet\" href=\"/static/style.css\">\n</head>\n<body class=\"token-page\">\n    {{ if .IDToken }}\n    <div class=\"token-block\">\n        <div class=\"token-title\">\n            ID Token:\n            <a href=\"{{.IDTokenJWTLink}}\" target=\"_blank\">Decode on jwt.io</a>\n        </div>\n        <pre><code class=\"token-code\">{{ .IDToken }}</code></pre>\n    </div>\n    {{ end }}\n\n    {{ if .AccessToken }}\n    <div class=\"token-block\">\n        <div class=\"token-title\">\n            Access Token:\n            <a href=\"{{.AccessTokenJWTLink}}\" target=\"_blank\">Decode on jwt.io</a>\n        </div>\n        <pre><code class=\"token-code\">{{ .AccessToken }}</code></pre>\n    </div>\n    {{ end }}\n\n    {{ if .Claims }}\n    <div class=\"token-block\">\n        <div class=\"token-title\">ID Token Claims:</div>\n        <pre><code id=\"claims\">{{ .Claims }}</code></pre>\n    </div>\n    {{ end }}\n\n    {{ if .AccessToken }}\n    <div class=\"token-block\">\n        <div class=\"token-title\">UserInfo:</div>\n        <div id=\"userinfo-section\">\n            <form id=\"userinfo-form\">\n                <input type=\"hidden\" name=\"access_token\" value=\"{{ .AccessToken }}\">\n                <button type=\"submit\" class=\"fetch-userinfo-btn\">\n                    Fetch UserInfo\n                </button>\n            </form>\n            <div id=\"userinfo-loading\" class=\"userinfo-loading\" style=\"display: none;\">\n                <div class=\"spinner\"></div>\n                <span>Loading...</span>\n            </div>\n            <div id=\"userinfo-error\" class=\"error-message\" style=\"display: none;\"></div>\n            <pre id=\"userinfo-claims\" style=\"display: none;\"><code></code></pre>\n        </div>\n    </div>\n    {{ end }}\n\n    {{ if .RefreshToken }}\n    <div class=\"token-block\">\n        <div class=\"token-title\">Refresh Token:</div>\n        <pre><code class=\"token-code\">{{ .RefreshToken }}</code></pre>\n        <form action=\"{{ .RedirectURL }}\" method=\"post\">\n            <input type=\"hidden\" name=\"refresh_token\" value=\"{{ .RefreshToken }}\">\n            <input type=\"submit\" value=\"Redeem refresh token\">\n        </form>\n    </div>\n    {{ end }}\n\n    {{ if .PublicKeyPEM }}\n    <div class=\"token-block\">\n        <div class=\"token-title\">\n            Public Key (for JWT verification):\n            <button type=\"button\" class=\"copy-btn\" onclick=\"copyPublicKey()\">Copy to Clipboard</button>\n        </div>\n        <pre><code id=\"public-key\">{{ .PublicKeyPEM }}</code></pre>\n        <div class=\"hint\">Copy this key and paste it into jwt.io's \"Verify Signature\" section to validate the token signature.</div>\n    </div>\n    {{ end }}\n\n    <a href=\"/\" class=\"back-button\">Back to Home</a>\n\n    <script src=\"/static/token.js\"></script>\n</body>\n</html>\n\n"
  },
  {
    "path": "examples/example-app/templates.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"embed\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"html/template\"\n\t\"io/fs\"\n\t\"log\"\n\t\"math/big\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n)\n\n//go:embed templates/*.html\nvar templatesFS embed.FS\n\n//go:embed static/*\nvar staticFS embed.FS\n\nconst dexLogoDataURI = \"/static/dex-glyph-color.svg\"\n\nvar (\n\tindexTmpl     *template.Template\n\ttokenTmpl     *template.Template\n\tdeviceTmpl    *template.Template\n\tstaticHandler http.Handler\n)\n\nfunc init() {\n\tvar err error\n\tindexTmpl, err = template.ParseFS(templatesFS, \"templates/index.html\")\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to parse index template: %v\", err)\n\t}\n\n\ttokenTmpl, err = template.ParseFS(templatesFS, \"templates/token.html\")\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to parse token template: %v\", err)\n\t}\n\n\tdeviceTmpl, err = template.ParseFS(templatesFS, \"templates/device.html\")\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to parse device template: %v\", err)\n\t}\n\n\t// Create handler for static files\n\tstaticSubFS, err := fs.Sub(staticFS, \"static\")\n\tif err != nil {\n\t\tlog.Fatalf(\"failed to create static sub filesystem: %v\", err)\n\t}\n\tstaticHandler = http.FileServer(http.FS(staticSubFS))\n}\n\nfunc renderIndex(w http.ResponseWriter, data indexPageData) {\n\trenderTemplate(w, indexTmpl, data)\n}\n\nfunc renderDevice(w http.ResponseWriter, data devicePageData) {\n\trenderTemplate(w, deviceTmpl, data)\n}\n\ntype indexPageData struct {\n\tScopesSupported []string\n\tLogoURI         string\n}\n\ntype devicePageData struct {\n\tSessionID       string\n\tDeviceCode      string\n\tUserCode        string\n\tVerificationURI string\n\tPollInterval    int\n\tLogoURI         string\n}\n\ntype tokenTmplData struct {\n\tIDToken            string\n\tIDTokenJWTLink     string\n\tAccessToken        string\n\tAccessTokenJWTLink string\n\tRefreshToken       string\n\tRedirectURL        string\n\tClaims             string\n\tPublicKeyPEM       string\n}\n\nfunc generateJWTIOLink(token string, provider *oidc.Provider, ctx context.Context) string {\n\t// JWT.io doesn't support automatic public key via URL parameter\n\t// The public key is displayed separately on the page for manual copy-paste\n\treturn \"https://jwt.io/#debugger-io?token=\" + url.QueryEscape(token)\n}\n\nfunc getPublicKeyPEM(provider *oidc.Provider) string {\n\tif provider == nil {\n\t\treturn \"\"\n\t}\n\n\tjwksURL := provider.Endpoint().AuthURL\n\tif len(jwksURL) > 5 {\n\t\tjwksURL = jwksURL[:len(jwksURL)-5] + \"/keys\"\n\t} else {\n\t\treturn \"\"\n\t}\n\n\tresp, err := http.Get(jwksURL)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tdefer resp.Body.Close()\n\n\tvar jwks struct {\n\t\tKeys []json.RawMessage `json:\"keys\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&jwks); err != nil || len(jwks.Keys) == 0 {\n\t\treturn \"\"\n\t}\n\n\tvar key struct {\n\t\tN   string `json:\"n\"`\n\t\tE   string `json:\"e\"`\n\t\tKty string `json:\"kty\"`\n\t}\n\tif err := json.Unmarshal(jwks.Keys[0], &key); err != nil || key.Kty != \"RSA\" {\n\t\treturn \"\"\n\t}\n\n\tnBytes, err1 := base64.RawURLEncoding.DecodeString(key.N)\n\teBytes, err2 := base64.RawURLEncoding.DecodeString(key.E)\n\tif err1 != nil || err2 != nil {\n\t\treturn \"\"\n\t}\n\n\tvar eInt int\n\tfor _, b := range eBytes {\n\t\teInt = eInt<<8 | int(b)\n\t}\n\n\tpubKey := &rsa.PublicKey{\n\t\tN: new(big.Int).SetBytes(nBytes),\n\t\tE: eInt,\n\t}\n\n\tpubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tpubKeyPEM := pem.EncodeToMemory(&pem.Block{\n\t\tType:  \"PUBLIC KEY\",\n\t\tBytes: pubKeyBytes,\n\t})\n\n\treturn string(pubKeyPEM)\n}\n\nfunc renderToken(w http.ResponseWriter, ctx context.Context, provider *oidc.Provider, redirectURL, idToken, accessToken, refreshToken, claims string) {\n\tdata := tokenTmplData{\n\t\tIDToken:            idToken,\n\t\tIDTokenJWTLink:     generateJWTIOLink(idToken, provider, ctx),\n\t\tAccessToken:        accessToken,\n\t\tAccessTokenJWTLink: generateJWTIOLink(accessToken, provider, ctx),\n\t\tRefreshToken:       refreshToken,\n\t\tRedirectURL:        redirectURL,\n\t\tClaims:             claims,\n\t\tPublicKeyPEM:       getPublicKeyPEM(provider),\n\t}\n\trenderTemplate(w, tokenTmpl, data)\n}\n\nfunc renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) {\n\terr := tmpl.Execute(w, data)\n\tif err == nil {\n\t\treturn\n\t}\n\n\tswitch err := err.(type) {\n\tcase *template.Error:\n\t\tlog.Printf(\"Error rendering template %s: %s\", tmpl.Name(), err)\n\t\thttp.Error(w, \"Internal server error\", http.StatusInternalServerError)\n\tdefault:\n\t\t// An error with the underlying write, such as the connection being dropped. Ignore for now.\n\t}\n}\n"
  },
  {
    "path": "examples/example-app/utils.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"os\"\n\t\"slices\"\n\t\"time\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"golang.org/x/oauth2\"\n)\n\n// generateSessionID creates a random session identifier\nfunc generateSessionID() string {\n\tb := make([]byte, 16)\n\tif _, err := rand.Read(b); err != nil {\n\t\t// Fallback to timestamp if random fails\n\t\treturn fmt.Sprintf(\"%d\", time.Now().UnixNano())\n\t}\n\treturn hex.EncodeToString(b)\n}\n\n// buildScopes constructs a scope list from base scopes and cross-client IDs\nfunc buildScopes(baseScopes []string, crossClients []string) []string {\n\tscopes := make([]string, len(baseScopes))\n\tcopy(scopes, baseScopes)\n\n\t// Add audience scopes for cross-client authorization\n\tfor _, client := range crossClients {\n\t\tif client != \"\" {\n\t\t\tscopes = append(scopes, \"audience:server:client_id:\"+client)\n\t\t}\n\t}\n\n\treturn uniqueStrings(scopes)\n}\n\nfunc (a *app) oauth2Config(scopes []string) *oauth2.Config {\n\treturn &oauth2.Config{\n\t\tClientID:     a.clientID,\n\t\tClientSecret: a.clientSecret,\n\t\tEndpoint:     a.provider.Endpoint(),\n\t\tScopes:       scopes,\n\t\tRedirectURL:  a.redirectURI,\n\t}\n}\n\nfunc uniqueStrings(values []string) []string {\n\tslices.Sort(values)\n\tvalues = slices.Compact(values)\n\treturn values\n}\n\n// return an HTTP client which trusts the provided root CAs.\nfunc httpClientForRootCAs(rootCAs string) (*http.Client, error) {\n\ttlsConfig := tls.Config{RootCAs: x509.NewCertPool()}\n\trootCABytes, err := os.ReadFile(rootCAs)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read root-ca: %v\", err)\n\t}\n\tif !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {\n\t\treturn nil, fmt.Errorf(\"no certs found in root CA file %q\", rootCAs)\n\t}\n\treturn &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tTLSClientConfig: &tlsConfig,\n\t\t\tProxy:           http.ProxyFromEnvironment,\n\t\t\tDial: (&net.Dialer{\n\t\t\t\tTimeout:   30 * time.Second,\n\t\t\t\tKeepAlive: 30 * time.Second,\n\t\t\t}).Dial,\n\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t\tExpectContinueTimeout: 1 * time.Second,\n\t\t},\n\t}, nil\n}\n\ntype debugTransport struct {\n\tt http.RoundTripper\n}\n\nfunc (d debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {\n\treqDump, err := httputil.DumpRequest(req, true)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlog.Printf(\"%s\", reqDump)\n\n\tresp, err := d.t.RoundTrip(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\trespDump, err := httputil.DumpResponse(resp, true)\n\tif err != nil {\n\t\tresp.Body.Close()\n\t\treturn nil, err\n\t}\n\tlog.Printf(\"%s\", respDump)\n\treturn resp, nil\n}\n\nfunc encodeToken(idToken *oidc.IDToken) (string, error) {\n\tvar claims json.RawMessage\n\tif err := idToken.Claims(&claims); err != nil {\n\t\treturn \"\", fmt.Errorf(\"error decoding ID token claims: %v\", err)\n\t}\n\n\tbuff := new(bytes.Buffer)\n\tif err := json.Indent(buff, claims, \"\", \"  \"); err != nil {\n\t\treturn \"\", fmt.Errorf(\"error indenting ID token claims: %v\", err)\n\t}\n\treturn buff.String(), nil\n}\n\nfunc parseAndRenderToken(w http.ResponseWriter, r *http.Request, a *app, token *oauth2.Token) {\n\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\tif !ok {\n\t\thttp.Error(w, \"no id_token in token response\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tidToken, err := a.verifier.Verify(r.Context(), rawIDToken)\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"failed to verify ID token: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\taccessToken, ok := token.Extra(\"access_token\").(string)\n\tif !ok {\n\t\taccessToken = token.AccessToken\n\t\tif accessToken == \"\" {\n\t\t\thttp.Error(w, \"no access_token in token response\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n\n\tbuf, err := encodeToken(idToken)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusInternalServerError)\n\t\treturn\n\t}\n\trenderToken(w, r.Context(), a.provider, a.redirectURI, rawIDToken, accessToken, token.RefreshToken, buf)\n}\n"
  },
  {
    "path": "examples/go.mod",
    "content": "module github.com/dexidp/dex/examples\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/coreos/go-oidc/v3 v3.17.0\n\tgithub.com/dexidp/dex/api/v2 v2.4.0\n\tgithub.com/spf13/cobra v1.10.2\n\tgolang.org/x/oauth2 v0.36.0\n\tgoogle.golang.org/grpc v1.79.3\n)\n\nrequire (\n\tgithub.com/go-jose/go-jose/v4 v4.1.3 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgolang.org/x/net v0.48.0 // indirect\n\tgolang.org/x/sys v0.39.0 // indirect\n\tgolang.org/x/text v0.32.0 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/protobuf v1.36.10 // indirect\n)\n"
  },
  {
    "path": "examples/go.sum",
    "content": "github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=\ngithub.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/dexidp/dex/api/v2 v2.4.0 h1:gNba7n6BKVp8X4Jp24cxYn5rIIGhM6kDOXcZoL6tr9A=\ngithub.com/dexidp/dex/api/v2 v2.4.0/go.mod h1:/p550ADvFFh7K95VmhUD+jgm15VdaNnab9td8DHOpyI=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=\ngolang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=\ngolang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=\ngolang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\n"
  },
  {
    "path": "examples/grpc-client/.gitignore",
    "content": "*.key\n*.crt\n*.csr\nindex.*\nserial*\n"
  },
  {
    "path": "examples/grpc-client/README.md",
    "content": "# Running a Dex gRPC client\n\nUsing gRPC, a client application can directly call methods on a server application as if it was a local object. The schema for Dex's gRPC API calls is defined in [`api/api.proto`][api-proto]. [`client.go`][client] is an example client program that makes a bunch of API calls to the dex server. For further details on the Dex API refer the [documentation][https://dexidp.io/docs/api/].\n\n## Generating Credentials\n\nBefore running the client or the server, TLS credentials have to be setup for secure communication. Run the `cred-gen` script to create TLS credentials for running this example. This script generates a `ca.crt`, `server.crt`, `server.key`, `client.crt`, and `client.key`.\n\n```\n# Used to set certificate subject alt names.\nexport SAN=IP.1:127.0.0.1\n\n# Run the script\n./examples/grpc-client/cert-gen\n```\nTo verify that the server and client certificates were signed by the CA, run the following commands:\n\n```\nopenssl verify -CAfile ca.crt server.crt\nopenssl verify -CAfile ca.crt client.crt\n```\n\n## Running the Dex server\n\nTo expose the gRPC service, the gRPC option must be enabled via the dex config file as shown below.\n\n```yaml\n# Enables the gRPC API.\ngrpc:\n  addr: 127.0.0.1:5557\n  tlsCert: server.crt\n  tlsKey: server.key\n\n```\nStart an instance of the dex server with an in-memory data store:\n\n```\n./bin/dex serve examples/grpc-client/config.yaml\n```\n\n## Running the Dex client\n\nFinally run the Dex client providing the CA certificate, client certificate and client key as arguments.\n\n```\n./bin/grpc-client -ca-crt=ca.crt -client-crt=client.crt -client-key=client.key\n```\nRunning the gRPC client will cause the following API calls to be made to the server\n1. CreatePassword\n2. ListPasswords\n3. VerifyPassword\n4. DeletePassword\n5. CreateClient\n6. ListClients\n7. DeleteClient\n\n## Cleaning up\n\nRun the following command to destroy all the credentials files that were created by the `cert-gen` script:\n\n```\n./examples/grpc-client/cert-destroy\n```\n[api-proto]: ../../api/api.proto\n[client]: client.go\n"
  },
  {
    "path": "examples/grpc-client/cert-destroy",
    "content": "#!/bin/bash\n\nrm -f ca.key ca.crt server.key server.csr server.crt client.key client.csr client.crt index.* serial*\nrm -rf certs crl newcerts\n"
  },
  {
    "path": "examples/grpc-client/cert-gen",
    "content": "#!/bin/bash\n\nif [ -z $SAN ]\n  then echo \"Set SAN with a DNS or IP(e.g. export SAN=IP.1:127.0.0.1,IP.2:172.18.0.2).\"\n  exit 1\nfi\n\necho \"Creating CA, server cert/key, and client cert/key...\"\n\n# Creating basic files/directories\nmkdir -p {certs,crl,newcerts}\ntouch index.txt\necho 1000 > serial\n\n# CA private key (unencrypted)\nopenssl genrsa -out ca.key 4096\n# Certificate Authority (self-signed certificate)\nopenssl req -config examples/grpc-client/openssl.conf -new -x509 -days 3650 -sha256 -key ca.key -extensions v3_ca -out ca.crt -subj \"/CN=fake-ca\"\n\n# Server private key (unencrypted)\nopenssl genrsa -out server.key 2048\n# Server certificate signing request (CSR)\nopenssl req -config examples/grpc-client/openssl.conf -new -sha256 -key server.key -out server.csr -subj \"/CN=fake-server\"\n# Certificate Authority signs CSR to grant a certificate\nopenssl ca -batch -config examples/grpc-client/openssl.conf -extensions server_cert -days 365 -notext -md sha256 -in server.csr -out server.crt -cert ca.crt -keyfile ca.key\n\n# Client private key (unencrypted)\nopenssl genrsa -out client.key 2048\n# Signed client certificate signing request (CSR)\nopenssl req -config examples/grpc-client/openssl.conf -new -sha256 -key client.key -out client.csr -subj \"/CN=fake-client\"\n# Certificate Authority signs CSR to grant a certificate\nopenssl ca -batch -config examples/grpc-client/openssl.conf -extensions usr_cert -days 365 -notext -md sha256 -in client.csr -out client.crt -cert ca.crt -keyfile ca.key\n\n# Remove CSR's\nrm *.csr\n"
  },
  {
    "path": "examples/grpc-client/client.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials\"\n\n\t\"github.com/dexidp/dex/api/v2\"\n)\n\nfunc newDexClient(hostAndPort, caPath, clientCrt, clientKey string) (api.DexClient, error) {\n\tcPool := x509.NewCertPool()\n\tcaCert, err := os.ReadFile(caPath)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid CA crt file: %s\", caPath)\n\t}\n\tif cPool.AppendCertsFromPEM(caCert) != true {\n\t\treturn nil, fmt.Errorf(\"failed to parse CA crt\")\n\t}\n\n\tclientCert, err := tls.LoadX509KeyPair(clientCrt, clientKey)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid client crt file: %s\", caPath)\n\t}\n\n\tclientTLSConfig := &tls.Config{\n\t\tRootCAs:      cPool,\n\t\tCertificates: []tls.Certificate{clientCert},\n\t}\n\tcreds := credentials.NewTLS(clientTLSConfig)\n\n\tconn, err := grpc.Dial(hostAndPort, grpc.WithTransportCredentials(creds))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial: %v\", err)\n\t}\n\treturn api.NewDexClient(conn), nil\n}\n\nfunc createPassword(cli api.DexClient) error {\n\tp := api.Password{\n\t\tEmail: \"test@example.com\",\n\t\t// bcrypt hash of the value \"test1\" with cost 10\n\t\tHash:     []byte(\"$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO\"),\n\t\tUsername: \"test\",\n\t\tUserId:   \"test\",\n\t}\n\n\tcreateReq := &api.CreatePasswordReq{\n\t\tPassword: &p,\n\t}\n\n\t// Create password.\n\tif resp, err := cli.CreatePassword(context.TODO(), createReq); err != nil || resp.AlreadyExists {\n\t\tif resp != nil && resp.AlreadyExists {\n\t\t\treturn fmt.Errorf(\"Password %s already exists\", createReq.Password.Email)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to create password: %v\", err)\n\t}\n\tlog.Printf(\"Created password with email %s\", createReq.Password.Email)\n\n\t// List all passwords.\n\tresp, err := cli.ListPasswords(context.TODO(), &api.ListPasswordReq{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list password: %v\", err)\n\t}\n\n\tlog.Print(\"Listing Passwords:\\n\")\n\tfor _, pass := range resp.Passwords {\n\t\tlog.Printf(\"%+v\", pass)\n\t}\n\n\t// Verifying correct and incorrect passwords\n\tlog.Print(\"Verifying Password:\\n\")\n\tverifyReq := &api.VerifyPasswordReq{\n\t\tEmail:    \"test@example.com\",\n\t\tPassword: \"test1\",\n\t}\n\tverifyResp, err := cli.VerifyPassword(context.TODO(), verifyReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run VerifyPassword for correct password: %v\", err)\n\t}\n\tif !verifyResp.Verified {\n\t\treturn fmt.Errorf(\"failed to verify correct password: %v\", verifyResp)\n\t}\n\tlog.Printf(\"properly verified correct password: %t\\n\", verifyResp.Verified)\n\n\tbadVerifyReq := &api.VerifyPasswordReq{\n\t\tEmail:    \"test@example.com\",\n\t\tPassword: \"wrong_password\",\n\t}\n\tbadVerifyResp, err := cli.VerifyPassword(context.TODO(), badVerifyReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to run VerifyPassword for incorrect password: %v\", err)\n\t}\n\tif badVerifyResp.Verified {\n\t\treturn fmt.Errorf(\"verify returned true for incorrect password: %v\", badVerifyResp)\n\t}\n\tlog.Printf(\"properly failed to verify incorrect password: %t\\n\", badVerifyResp.Verified)\n\n\tlog.Print(\"Listing Passwords:\\n\")\n\tfor _, pass := range resp.Passwords {\n\t\tlog.Printf(\"%+v\", pass)\n\t}\n\n\tdeleteReq := &api.DeletePasswordReq{\n\t\tEmail: p.Email,\n\t}\n\n\t// Delete password with email = test@example.com.\n\tif resp, err := cli.DeletePassword(context.TODO(), deleteReq); err != nil || resp.NotFound {\n\t\tif resp != nil && resp.NotFound {\n\t\t\treturn fmt.Errorf(\"Password %s not found\", deleteReq.Email)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to delete password: %v\", err)\n\t}\n\tlog.Printf(\"Deleted password with email %s\", deleteReq.Email)\n\n\treturn nil\n}\n\nfunc createAndListClients(cli api.DexClient) error {\n\tclient := &api.Client{\n\t\tId:           \"example-client\",\n\t\tSecret:       \"example-secret\",\n\t\tRedirectUris: []string{\"http://localhost:8080/callback\"},\n\t\tTrustedPeers: []string{},\n\t\tPublic:       false,\n\t\tName:         \"Example Client\",\n\t\tLogoUrl:      \"http://example.com/logo.png\",\n\t}\n\n\tcreateReq := &api.CreateClientReq{\n\t\tClient: client,\n\t}\n\n\tif resp, err := cli.CreateClient(context.TODO(), createReq); err != nil || resp.AlreadyExists {\n\t\tif resp != nil && resp.AlreadyExists {\n\t\t\tlog.Printf(\"Client %s already exists\", createReq.Client.Id)\n\t\t} else {\n\t\t\treturn fmt.Errorf(\"failed to create client: %v\", err)\n\t\t}\n\t} else {\n\t\tlog.Printf(\"Created client with ID %s\", createReq.Client.Id)\n\t}\n\n\tlistResp, err := cli.ListClients(context.TODO(), &api.ListClientReq{})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list clients: %v\", err)\n\t}\n\n\tlog.Print(\"Listing Clients:\\n\")\n\tfor _, client := range listResp.Clients {\n\t\tlog.Printf(\"ID: %s, Name: %s, Public: %t, RedirectURIs: %v\",\n\t\t\tclient.Id, client.Name, client.Public, client.RedirectUris)\n\t}\n\n\tdeleteReq := &api.DeleteClientReq{\n\t\tId: client.Id,\n\t}\n\n\tif resp, err := cli.DeleteClient(context.TODO(), deleteReq); err != nil || resp.NotFound {\n\t\tif resp != nil && resp.NotFound {\n\t\t\treturn fmt.Errorf(\"Client %s not found\", deleteReq.Id)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to delete client: %v\", err)\n\t}\n\tlog.Printf(\"Deleted client with ID %s\", deleteReq.Id)\n\n\treturn nil\n}\n\nfunc main() {\n\tcaCrt := flag.String(\"ca-crt\", \"\", \"CA certificate\")\n\tclientCrt := flag.String(\"client-crt\", \"\", \"Client certificate\")\n\tclientKey := flag.String(\"client-key\", \"\", \"Client key\")\n\tflag.Parse()\n\n\tif *clientCrt == \"\" || *caCrt == \"\" || *clientKey == \"\" {\n\t\tlog.Fatal(\"Please provide CA & client certificates and client key. Usage: ./client --ca-crt=<path ca.crt> --client-crt=<path client.crt> --client-key=<path client key>\")\n\t}\n\n\tclient, err := newDexClient(\"127.0.0.1:5557\", *caCrt, *clientCrt, *clientKey)\n\tif err != nil {\n\t\tlog.Fatalf(\"failed creating dex client: %v \", err)\n\t}\n\n\tif err := createPassword(client); err != nil {\n\t\tlog.Fatalf(\"testPassword failed: %v\", err)\n\t}\n\n\tif err := createAndListClients(client); err != nil {\n\t\tlog.Fatalf(\"testClients failed: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "examples/grpc-client/config.yaml",
    "content": "issuer: http://127.0.0.1:5556/dex\n\nstorage:\n  type: sqlite3\n  config:\n    file: examples/dex.db\n\n# Configuration for the HTTP endpoints.\nweb:\n  http: 0.0.0.0:5556\n\ngrpc:\n  addr: 127.0.0.1:5557\n  tlsCert: server.crt\n  tlsKey: server.key\n  tlsClientCA: ca.crt\n\nconnectors:\n- type: mockCallback\n  id: mock\n  name: Example\n\n# Let dex keep a list of passwords which can be used to login to dex.\nenablePasswordDB: true\n\n"
  },
  {
    "path": "examples/grpc-client/openssl.conf",
    "content": "# OpenSSL configuration file.\n# Adapted from https://github.com/coreos/matchbox/blob/master/examples/etc/matchbox/openssl.conf\n\n# default environment variable values\nSAN =\n\n[ ca ]\n# `man ca`\ndefault_ca = CA_default\n\n[ CA_default ]\n# Directory and file locations.\ndir               = .\ncerts             = $dir/certs\ncrl_dir           = $dir/crl\nnew_certs_dir     = $dir/newcerts\ndatabase          = $dir/index.txt\nserial            = $dir/serial\n# certificate revocation lists.\ncrlnumber         = $dir/crlnumber\ncrl               = $dir/crl/intermediate-ca.crl\ncrl_extensions    = crl_ext\ndefault_crl_days  = 30\ndefault_md        = sha256\n\nname_opt          = ca_default\ncert_opt          = ca_default\ndefault_days      = 375\npreserve          = no\npolicy            = policy_loose\n\n[ policy_loose ]\n# Allow the CA to sign a range of certificates.\ncountryName             = optional\nstateOrProvinceName     = optional\nlocalityName            = optional\norganizationName        = optional\norganizationalUnitName  = optional\ncommonName              = supplied\nemailAddress            = optional\n\n[ req ]\n# `man req`\ndefault_bits        = 4096\ndistinguished_name  = req_distinguished_name\nstring_mask         = utf8only\ndefault_md          = sha256\n\n[ req_distinguished_name ]\ncountryName                    = Country Name (2 letter code)\nstateOrProvinceName            = State or Province Name\nlocalityName                   = Locality Name\n0.organizationName             = Organization Name\norganizationalUnitName         = Organizational Unit Name\ncommonName                     = Common Name\n\n# Certificate extensions (`man x509v3_config`)\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer\nbasicConstraints = critical, CA:true, pathlen:0\nkeyUsage = critical, digitalSignature, cRLSign, keyCertSign\n\n[ usr_cert ]\nbasicConstraints = CA:FALSE\nnsCertType = client\nnsComment = \"OpenSSL Generated Client Certificate\"\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = clientAuth\n\n[ server_cert ]\nbasicConstraints = CA:FALSE\nnsCertType = server\nnsComment = \"OpenSSL Generated Server Certificate\"\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer:always\nkeyUsage = critical, digitalSignature, keyEncipherment\nextendedKeyUsage = serverAuth\nsubjectAltName = $ENV::SAN\n"
  },
  {
    "path": "examples/k8s/.gitignore",
    "content": "ssl/\n"
  },
  {
    "path": "examples/k8s/dex.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: dex\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: dex\n  name: dex\n  namespace: dex\nspec:\n  replicas: 3\n  selector:\n    matchLabels:\n      app: dex\n  template:\n    metadata:\n      labels:\n        app: dex\n    spec:\n      serviceAccountName: dex # This is created below\n      containers:\n      - image: ghcr.io/dexidp/dex:v2.32.0\n        name: dex\n        command: [\"/usr/local/bin/dex\", \"serve\", \"/etc/dex/cfg/config.yaml\"]\n\n        ports:\n        - name: https\n          containerPort: 5556\n\n        volumeMounts:\n        - name: config\n          mountPath: /etc/dex/cfg\n        - name: tls\n          mountPath: /etc/dex/tls\n\n        env:\n        - name: GITHUB_CLIENT_ID\n          valueFrom:\n            secretKeyRef:\n              name: github-client\n              key: client-id\n        - name: GITHUB_CLIENT_SECRET\n          valueFrom:\n            secretKeyRef:\n              name: github-client\n              key: client-secret\n\n        readinessProbe:\n          httpGet:\n            path: /healthz\n            port: 5556\n            scheme: HTTPS\n      volumes:\n      - name: config\n        configMap:\n          name: dex\n          items:\n          - key: config.yaml\n            path: config.yaml\n      - name: tls\n        secret:\n          secretName: dex.example.com.tls\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: dex\n  namespace: dex\ndata:\n  config.yaml: |\n    issuer: https://dex.example.com:32000\n    storage:\n      type: kubernetes\n      config:\n        inCluster: true\n    web:\n      https: 0.0.0.0:5556\n      tlsCert: /etc/dex/tls/tls.crt\n      tlsKey: /etc/dex/tls/tls.key\n    connectors:\n    - type: github\n      id: github\n      name: GitHub\n      config:\n        clientID: $GITHUB_CLIENT_ID\n        clientSecret: $GITHUB_CLIENT_SECRET\n        redirectURI: https://dex.example.com:32000/callback\n        org: kubernetes\n    oauth2:\n      skipApprovalScreen: true\n\n    staticClients:\n    - id: example-app\n      redirectURIs:\n      - 'http://127.0.0.1:5555/callback'\n      name: 'Example App'\n      secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n\n    enablePasswordDB: true\n    staticPasswords:\n    - email: \"admin@example.com\"\n      # bcrypt hash of the string \"password\": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)\n      hash: \"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W\"\n      username: \"admin\"\n      name: \"Admin User\"\n      emailVerified: true\n      preferredUsername: \"admin\"\n      groups:\n      - \"team-a\"\n      - \"team-a/admins\"\n      userID: \"08a8684b-db88-4b73-90a9-3cd1661f5466\"\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: dex\n  namespace: dex\nspec:\n  type: NodePort\n  ports:\n  - name: dex\n    port: 5556\n    protocol: TCP\n    targetPort: 5556\n    nodePort: 32000\n  selector:\n    app: dex\n---\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  labels:\n    app: dex\n  name: dex\n  namespace: dex\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: dex\nrules:\n- apiGroups: [\"dex.coreos.com\"] # API group created by dex\n  resources: [\"*\"]\n  verbs: [\"*\"]\n- apiGroups: [\"apiextensions.k8s.io\"]\n  resources: [\"customresourcedefinitions\"]\n  verbs: [\"create\"] # To manage its own resources, dex must be able to create customresourcedefinitions\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: dex\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: dex\nsubjects:\n- kind: ServiceAccount\n  name: dex           # Service account assigned to the dex pod, created above\n  namespace: dex  # The namespace dex is running in\n"
  },
  {
    "path": "examples/k8s/gencert.sh",
    "content": "#!/bin/bash\n\nmkdir -p ssl\n\ncat << EOF > ssl/req.cnf\n[req]\nreq_extensions = v3_req\ndistinguished_name = req_distinguished_name\n\n[req_distinguished_name]\n\n[ v3_req ]\nbasicConstraints = CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = dex.example.com\nEOF\n\nopenssl genrsa -out ssl/ca-key.pem 2048\nopenssl req -x509 -new -nodes -key ssl/ca-key.pem -days 10 -out ssl/ca.pem -subj \"/CN=kube-ca\"\n\nopenssl genrsa -out ssl/key.pem 2048\nopenssl req -new -key ssl/key.pem -out ssl/csr.pem -subj \"/CN=kube-ca\" -config ssl/req.cnf\nopenssl x509 -req -in ssl/csr.pem -CA ssl/ca.pem -CAkey ssl/ca-key.pem -CAcreateserial -out ssl/cert.pem -days 10 -extensions v3_req -extfile ssl/req.cnf\n"
  },
  {
    "path": "examples/ldap/config-ldap.ldif",
    "content": "# Already included in default config of Docker image osixia/openldap:1.4.0.\n#\n# dn: dc=example,dc=org\n# objectClass: dcObject\n# objectClass: organization\n# o: Example Company\n# dc: example\n\ndn: ou=People,dc=example,dc=org\nobjectClass: organizationalUnit\nou: People\n\ndn: cn=jane,ou=People,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: jane\nmail: janedoe@example.com\nuserpassword: foo\n\ndn: cn=john,ou=People,dc=example,dc=org\nobjectClass: person\nobjectClass: inetOrgPerson\nsn: doe\ncn: john\nmail: johndoe@example.com\nuserpassword: bar\n\n# Group definitions.\n\ndn: ou=Groups,dc=example,dc=org\nobjectClass: organizationalUnit\nou: Groups\n\ndn: cn=admins,ou=Groups,dc=example,dc=org\nobjectClass: groupOfNames\ncn: admins\nmember: cn=john,ou=People,dc=example,dc=org\nmember: cn=jane,ou=People,dc=example,dc=org\n\ndn: cn=developers,ou=Groups,dc=example,dc=org\nobjectClass: groupOfNames\ncn: developers\nmember: cn=jane,ou=People,dc=example,dc=org\n"
  },
  {
    "path": "examples/ldap/config-ldap.yaml",
    "content": "issuer: http://127.0.0.1:5556/dex\nstorage:\n  type: sqlite3\n  config:\n    file: examples/dex.db\nweb:\n  http: 0.0.0.0:5556\n\nconnectors:\n- type: ldap\n  name: OpenLDAP\n  id: ldap\n  config:\n    # The following configurations seem to work with OpenLDAP:\n    #\n    # 1) Plain LDAP, without TLS:\n    host: localhost:389\n    insecureNoSSL: true\n    #\n    # 2) LDAPS without certificate validation:\n    #host: localhost:636\n    #insecureNoSSL: false\n    #insecureSkipVerify: true\n    #\n    # 3) LDAPS with certificate validation:\n    #host: YOUR-HOSTNAME:636\n    #insecureNoSSL: false\n    #insecureSkipVerify: false\n    #rootCAData: 'CERT'\n    # ...where CERT=\"$( base64 -w 0 your-cert.crt )\"\n\n    # This would normally be a read-only user.\n    bindDN: cn=admin,dc=example,dc=org\n    bindPW: admin\n\n    usernamePrompt: Email Address\n\n    userSearch:\n      baseDN: ou=People,dc=example,dc=org\n      filter: \"(objectClass=person)\"\n      username: mail\n      # \"DN\" (case sensitive) is a special attribute name. It indicates that\n      # this value should be taken from the entity's DN not an attribute on\n      # the entity.\n      idAttr: DN\n      emailAttr: mail\n      nameAttr: cn\n\n    groupSearch:\n      baseDN: ou=Groups,dc=example,dc=org\n      filter: \"(objectClass=groupOfNames)\"\n\n      userMatchers:\n        # A user is a member of a group when their DN matches\n        # the value of a \"member\" attribute on the group entity.\n      - userAttr: DN\n        groupAttr: member\n\n      # The group name should be the \"cn\" value.\n      nameAttr: cn\n\nstaticClients:\n- id: example-app\n  redirectURIs:\n  - 'http://127.0.0.1:5555/callback'\n  name: 'Example App'\n  secret: ZXhhbXBsZS1hcHAtc2VjcmV0\n"
  },
  {
    "path": "examples/ldap/docker-compose.yaml",
    "content": "version: \"3\"\n\n# For LDAPS with certificate validation:\n# How to extract the TLS certificate from the OpenLDAP container, and encode it for the Dex config (`rootCAData`):\n#   $ docker-compose exec ldap cat /container/run/service/slapd/assets/certs/ca.crt | base64 -w 0\n# But note this issue: https://github.com/osixia/docker-openldap/issues/506\n\nservices:\n  ldap:\n    image: osixia/openldap:1.4.0\n    # Copying is required because the entrypoint modifies the *.ldif files.\n    # For verbose output, use:  command: [\"--copy-service\", \"--loglevel\", \"debug\"]\n    command: [\"--copy-service\"]\n    environment:\n      # Required if using LDAPS:\n      # Since Dex doesn't use a client TLS certificate, downgrade from \"demand\" to \"try\".\n      LDAP_TLS_VERIFY_CLIENT: try\n    # The hostname is required if using LDAPS with certificate validation.\n    # In Dex, use the same hostname (with port) for `connectors[].config.host`.\n    #hostname: YOUR-HOSTNAME\n    #\n    # https://github.com/osixia/docker-openldap#seed-ldap-database-with-ldif\n    # Option 1: Add custom seed file -> mount to         /container/service/slapd/assets/config/bootstrap/ldif/custom/\n    # Option 2: Overwrite default seed file -> mount to  /container/service/slapd/assets/config/bootstrap/ldif/\n    volumes:\n    - ./config-ldap.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/config-ldap.ldif\n    ports:\n    - 389:389\n    - 636:636\n"
  },
  {
    "path": "examples/oidc-conformance/config.yaml.tmpl",
    "content": "# Dex configuration for OIDC Conformance Testing.\n# See https://dexidp.io/docs/development/oidc-certification/\n#\n# This template is processed by run.sh which replaces ISSUER_URL and ALIAS\n# with actual values before starting Dex.\n\nissuer: ISSUER_URL/dex\n\nstorage:\n  type: sqlite3\n  config:\n    file: examples/oidc-conformance/dex.db\n\nweb:\n  http: 0.0.0.0:5556\n\nenablePasswordDB: true\n\nstaticPasswords:\n- email: \"admin@example.com\"\n  # bcrypt hash of the string \"password\"\n  hash: \"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W\"\n  username: \"admin\"\n\nstaticClients:\n  - id: first_client\n    secret: 89d6205220381728e85c4cf5\n    redirectURIs:\n      - https://www.certification.openid.net/test/a/ALIAS/callback\n    name: First client\n\n  - id: second_client\n    secret: 51c612288018fd384b05d6ad\n    redirectURIs:\n      - https://www.certification.openid.net/test/a/ALIAS/callback\n    name: Second client\n"
  },
  {
    "path": "examples/oidc-conformance/run.sh",
    "content": "#!/usr/bin/env bash\n#\n# OIDC Conformance Test Runner\n#\n# Starts Dex with a test configuration and exposes it via a public tunnel\n# for use with https://www.certification.openid.net/\n#\n# Usage:\n#   ./run.sh                         # uses cloudflared (default)\n#   ./run.sh --tunnel ngrok          # uses ngrok\n#   ./run.sh --url https://my.url    # uses a pre-existing public URL (no tunnel)\n#   ./run.sh --alias my-dex          # custom alias for the test plan (default: dex)\n#\n# Prerequisites:\n#   - Dex binary in PATH or ../../bin/dex\n#   - ngrok or cloudflared installed (unless --url is provided)\n\nset -euo pipefail\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\nROOT_DIR=\"$(cd \"$SCRIPT_DIR/../..\" && pwd)\"\nDEX_PORT=5556\nTUNNEL_TYPE=\"cloudflared\"\nPUBLIC_URL=\"\"\nALIAS=\"dex\"\n\nwhile [[ $# -gt 0 ]]; do\n    case $1 in\n        --tunnel)  TUNNEL_TYPE=\"$2\"; shift 2 ;;\n        --url)     PUBLIC_URL=\"$2\"; shift 2 ;;\n        --alias)   ALIAS=\"$2\"; shift 2 ;;\n        -h|--help)\n            sed -n '2,/^$/p' \"$0\" | sed 's/^# \\?//'\n            exit 0\n            ;;\n        *) echo \"Unknown option: $1\"; exit 1 ;;\n    esac\ndone\n\n# Find dex binary.\nDEX_BIN=\"\"\nfor candidate in \"dex\" \"$ROOT_DIR/bin/dex\"; do\n    if command -v \"$candidate\" &>/dev/null || [[ -x \"$candidate\" ]]; then\n        DEX_BIN=\"$candidate\"\n        break\n    fi\ndone\nif [[ -z \"$DEX_BIN\" ]]; then\n    echo \"Error: dex binary not found. Run 'make build' first or install dex.\"\n    exit 1\nfi\n\ncleanup() {\n    echo \"\"\n    echo \"Shutting down...\"\n    kill \"${TUNNEL_PID:-}\" \"${DEX_PID:-}\" 2>/dev/null || true\n    rm -f \"${CONFIG_FILE:-}\"\n    wait 2>/dev/null\n}\ntrap cleanup EXIT\n\n# Start tunnel if no URL provided.\nTUNNEL_PID=\"\"\nif [[ -z \"$PUBLIC_URL\" ]]; then\n    case \"$TUNNEL_TYPE\" in\n        ngrok)\n            if ! command -v ngrok &>/dev/null; then\n                echo \"Error: ngrok not found. Install it from https://ngrok.com/ or use --url.\"\n                exit 1\n            fi\n            ngrok http \"$DEX_PORT\" --log=stdout --log-level=warn &>/dev/null &\n            TUNNEL_PID=$!\n            echo \"Waiting for ngrok tunnel...\"\n            sleep 3\n            PUBLIC_URL=$(curl -s http://localhost:4040/api/tunnels | grep -o '\"public_url\":\"https://[^\"]*' | head -1 | cut -d'\"' -f4)\n            if [[ -z \"$PUBLIC_URL\" ]]; then\n                echo \"Error: failed to get ngrok public URL. Is ngrok running?\"\n                exit 1\n            fi\n            ;;\n        cloudflared)\n            if ! command -v cloudflared &>/dev/null; then\n                echo \"Error: cloudflared not found. Install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/\"\n                exit 1\n            fi\n            CLOUDFLARED_LOG=$(mktemp)\n            cloudflared tunnel --url \"http://localhost:$DEX_PORT\" --no-autoupdate 2>\"$CLOUDFLARED_LOG\" &\n            TUNNEL_PID=$!\n            echo \"Waiting for cloudflared tunnel...\"\n            for _ in $(seq 1 30); do\n                PUBLIC_URL=$(grep -o 'https://[^ ]*\\.trycloudflare\\.com' \"$CLOUDFLARED_LOG\" | head -1) && break\n                sleep 1\n            done\n            rm -f \"$CLOUDFLARED_LOG\"\n            if [[ -z \"$PUBLIC_URL\" ]]; then\n                echo \"Error: failed to get cloudflared URL.\"\n                exit 1\n            fi\n            ;;\n        *)\n            echo \"Error: unknown tunnel type '$TUNNEL_TYPE'. Use 'ngrok' or 'cloudflared'.\"\n            exit 1\n            ;;\n    esac\nfi\n\nPUBLIC_URL=\"${PUBLIC_URL%/}\"\necho \"Public URL: $PUBLIC_URL\"\n\n# Generate config from template.\nCONFIG_FILE=$(mktemp)\nsed -e \"s|ISSUER_URL|$PUBLIC_URL|g\" -e \"s|ALIAS|$ALIAS|g\" \"$SCRIPT_DIR/config.yaml.tmpl\" > \"$CONFIG_FILE\"\n\necho \"Starting Dex on port $DEX_PORT...\"\n\"$DEX_BIN\" serve \"$CONFIG_FILE\" &\nDEX_PID=$!\nsleep 2\n\nDISCOVERY_URL=\"$PUBLIC_URL/dex/.well-known/openid-configuration\"\n\necho \"\"\necho \"============================================================\"\necho \"  OIDC Conformance Test Setup Ready\"\necho \"============================================================\"\necho \"\"\necho \"  Discovery URL: $DISCOVERY_URL\"\necho \"  Alias:         $ALIAS\"\necho \"\"\necho \"  Client 1: id=first_client  secret=89d6205220381728e85c4cf5\"\necho \"  Client 2: id=second_client secret=51c612288018fd384b05d6ad\"\necho \"\"\necho \"  Steps:\"\necho \"  1. Open https://www.certification.openid.net/\"\necho \"  2. Log in with Google or GitLab\"\necho \"  3. Create a new test plan:\"\necho \"     - Plan: OpenID Connect Core: Basic Certification Profile\"\necho \"     - Server metadata: discovery\"\necho \"     - Client registration: static_client\"\necho \"     - Alias: $ALIAS\"\necho \"     - Discovery URL: $DISCOVERY_URL\"\necho \"     - Enter both client credentials above\"\necho \"  4. Run tests and follow instructions\"\necho \"\"\necho \"  Press Ctrl+C to stop.\"\necho \"============================================================\"\n\nwait \"$DEX_PID\"\n"
  },
  {
    "path": "flake.nix",
    "content": "{\n  inputs = {\n    nixpkgs.url = \"github:NixOS/nixpkgs/nixpkgs-unstable\";\n    flake-parts.url = \"github:hercules-ci/flake-parts\";\n    devenv.url = \"github:cachix/devenv\";\n  };\n\n  outputs =\n    inputs@{ flake-parts, ... }:\n    flake-parts.lib.mkFlake { inherit inputs; } {\n      imports = [\n        inputs.devenv.flakeModule\n      ];\n\n      systems = [\n        \"x86_64-linux\"\n        \"x86_64-darwin\"\n        \"aarch64-darwin\"\n        \"aarch64-linux\"\n      ];\n\n      perSystem =\n        { pkgs, ... }:\n        rec {\n          devenv.shells = {\n            default = {\n              languages = {\n                go = {\n                  enable = true;\n                  package = pkgs.go_1_25;\n                };\n              };\n\n              packages = with pkgs; [\n                gnumake\n\n                # golangci-lint\n                (golangci-lint.override (o: {\n                  buildGoModule = pkgs.buildGo125Module;\n                }))\n                gotestsum\n                protobuf\n                protoc-gen-go\n                protoc-gen-go-grpc\n                kind\n              ];\n\n              # https://github.com/cachix/devenv/issues/528#issuecomment-1556108767\n              containers = pkgs.lib.mkForce { };\n            };\n\n            ci = devenv.shells.default;\n          };\n        };\n    };\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/dexidp/dex\n\ngo 1.25.0\n\nrequire (\n\tcloud.google.com/go/compute/metadata v0.9.0\n\tentgo.io/ent v0.14.5\n\tgithub.com/AppsFlyer/go-sundheit v0.6.0\n\tgithub.com/Masterminds/semver v1.5.0\n\tgithub.com/Masterminds/sprig/v3 v3.3.0\n\tgithub.com/beevik/etree v1.6.0\n\tgithub.com/coreos/go-oidc/v3 v3.17.0\n\tgithub.com/dexidp/dex/api/v2 v2.4.0\n\tgithub.com/fsnotify/fsnotify v1.9.0\n\tgithub.com/ghodss/yaml v1.0.0\n\tgithub.com/go-jose/go-jose/v4 v4.1.3\n\tgithub.com/go-ldap/ldap/v3 v3.4.13\n\tgithub.com/go-sql-driver/mysql v1.9.3\n\tgithub.com/google/cel-go v0.27.0\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/gorilla/handlers v1.5.2\n\tgithub.com/gorilla/mux v1.8.1\n\tgithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0\n\tgithub.com/kylelemons/godebug v1.1.0\n\tgithub.com/lib/pq v1.12.0\n\tgithub.com/mattermost/xml-roundtrip-validator v0.1.0\n\tgithub.com/mattn/go-sqlite3 v1.14.37\n\tgithub.com/oklog/run v1.2.0\n\tgithub.com/openbao/openbao/api/v2 v2.5.1\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/pquerna/otp v1.5.0\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/russellhaering/goxmldsig v1.6.0\n\tgithub.com/spf13/cobra v1.10.2\n\tgithub.com/stretchr/testify v1.11.1\n\tgo.etcd.io/etcd/client/pkg/v3 v3.6.8\n\tgo.etcd.io/etcd/client/v3 v3.6.8\n\tgolang.org/x/crypto v0.49.0\n\tgolang.org/x/exp v0.0.0-20240823005443-9b4947da3948\n\tgolang.org/x/net v0.52.0\n\tgolang.org/x/oauth2 v0.36.0\n\tgoogle.golang.org/api v0.272.0\n\tgoogle.golang.org/grpc v1.79.3\n\tgoogle.golang.org/protobuf v1.36.11\n)\n\nrequire (\n\tariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 // indirect\n\tcel.dev/expr v0.25.1 // indirect\n\tcloud.google.com/go/auth v0.18.2 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tdario.cat/mergo v1.0.1 // indirect\n\tfilippo.io/edwards25519 v1.1.1 // indirect\n\tgithub.com/Azure/go-ntlmssp v0.1.0 // indirect\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.3.0 // indirect\n\tgithub.com/agext/levenshtein v1.2.3 // indirect\n\tgithub.com/antlr4-go/antlr/v4 v4.13.1 // indirect\n\tgithub.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bmatcuk/doublestar v1.3.4 // indirect\n\tgithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect\n\tgithub.com/cenkalti/backoff/v4 v4.3.0 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/coreos/go-semver v0.3.1 // indirect\n\tgithub.com/coreos/go-systemd/v22 v22.5.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-openapi/inflect v0.19.0 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.4.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.18.0 // indirect\n\tgithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-retryablehttp v0.7.8 // indirect\n\tgithub.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect\n\tgithub.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect\n\tgithub.com/hashicorp/go-sockaddr v1.0.7 // indirect\n\tgithub.com/hashicorp/hcl v1.0.1-vault-7 // indirect\n\tgithub.com/hashicorp/hcl/v2 v2.18.1 // indirect\n\tgithub.com/huandu/xstrings v1.5.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jonboulle/clockwork v0.5.0 // indirect\n\tgithub.com/mattn/go-runewidth v0.0.9 // indirect\n\tgithub.com/mitchellh/copystructure v1.2.0 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/olekukonko/tablewriter v0.0.5 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/ryanuber/go-glob v1.0.0 // indirect\n\tgithub.com/shopspring/decimal v1.4.0 // indirect\n\tgithub.com/spf13/cast v1.7.0 // indirect\n\tgithub.com/spf13/pflag v1.0.9 // indirect\n\tgithub.com/zclconf/go-cty v1.14.4 // indirect\n\tgithub.com/zclconf/go-cty-yaml v1.1.0 // indirect\n\tgo.etcd.io/etcd/api/v3 v3.6.8 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.39.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.39.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.39.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.27.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.2 // indirect\n\tgolang.org/x/mod v0.33.0 // indirect\n\tgolang.org/x/sync v0.20.0 // indirect\n\tgolang.org/x/sys v0.42.0 // indirect\n\tgolang.org/x/text v0.35.0 // indirect\n\tgolang.org/x/time v0.15.0 // indirect\n\tgolang.org/x/tools v0.42.0 // indirect\n\tgolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nreplace github.com/dexidp/dex/api/v2 => ./api/v2\n\ntool entgo.io/ent/cmd/ent\n"
  },
  {
    "path": "go.sum",
    "content": "ariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9 h1:E0wvcUXTkgyN4wy4LGtNzMNGMytJN8afmIWXJVMi4cc=\nariga.io/atlas v0.32.1-0.20250325101103-175b25e1c1b9/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w=\ncel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=\ncel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=\ncloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=\ncloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ndario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=\ndario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=\nentgo.io/ent v0.14.5 h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=\nentgo.io/ent v0.14.5/go.mod h1:zTzLmWtPvGpmSwtkaayM2cm5m819NdM7z7tYPq3vN0U=\nfilippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=\nfilippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/AppsFlyer/go-sundheit v0.6.0 h1:d2hBvCjBSb2lUsEWGfPigr4MCOt04sxB+Rppl0yUMSk=\ngithub.com/AppsFlyer/go-sundheit v0.6.0/go.mod h1:LDdBHD6tQBtmHsdW+i1GwdTt6Wqc0qazf5ZEJVTbTME=\ngithub.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=\ngithub.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=\ngithub.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=\ngithub.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=\ngithub.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=\ngithub.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs=\ngithub.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0=\ngithub.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=\ngithub.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=\ngithub.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=\ngithub.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=\ngithub.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=\ngithub.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=\ngithub.com/beevik/etree v1.6.0 h1:u8Kwy8pp9D9XeITj2Z0XtA5qqZEmtJtuXZRQi+j03eE=\ngithub.com/beevik/etree v1.6.0/go.mod h1:bh4zJxiIr62SOf9pRzN7UUYaEDa9HEKafK25+sLc0Gc=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=\ngithub.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=\ngithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=\ngithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=\ngithub.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=\ngithub.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=\ngithub.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=\ngithub.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=\ngithub.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=\ngithub.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=\ngithub.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=\ngithub.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=\ngithub.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=\ngithub.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=\ngithub.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=\ngithub.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=\ngithub.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=\ngithub.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=\ngithub.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=\ngithub.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=\ngithub.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo=\ngithub.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=\ngithub.com/googleapis/gax-go/v2 v2.18.0 h1:jxP5Uuo3bxm3M6gGtV94P4lliVetoCB4Wk2x8QA86LI=\ngithub.com/googleapis/gax-go/v2 v2.18.0/go.mod h1:uSzZN4a356eRG985CzJ3WfbFSpqkLTjsnhWGJR6EwrE=\ngithub.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=\ngithub.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=\ngithub.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=\ngithub.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=\ngithub.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=\ngithub.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=\ngithub.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=\ngithub.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=\ngithub.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=\ngithub.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=\ngithub.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=\ngithub.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=\ngithub.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=\ngithub.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=\ngithub.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=\ngithub.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZYSo=\ngithub.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=\ngithub.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=\ngithub.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=\ngithub.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=\ngithub.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=\ngithub.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=\ngithub.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=\ngithub.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=\ngithub.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=\ngithub.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=\ngithub.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=\ngithub.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=\ngithub.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=\ngithub.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/lib/pq v1.12.0 h1:mC1zeiNamwKBecjHarAr26c/+d8V5w/u4J0I/yASbJo=\ngithub.com/lib/pq v1.12.0/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=\ngithub.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=\ngithub.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=\ngithub.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=\ngithub.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=\ngithub.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=\ngithub.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\ngithub.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=\ngithub.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=\ngithub.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=\ngithub.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/openbao/openbao/api/v2 v2.5.1 h1:Br79D6L20SbAa5P7xqENxmvv8LyI4HoKosPy7klhn4o=\ngithub.com/openbao/openbao/api/v2 v2.5.1/go.mod h1:Dh5un77tqGgMbmlVEqjqN+8/dMyUohnkaQVg/wXW0Ig=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=\ngithub.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/russellhaering/goxmldsig v1.6.0 h1:8fdWXEPh2k/NZNQBPFNoVfS3JmzS4ZprY/sAOpKQLks=\ngithub.com/russellhaering/goxmldsig v1.6.0/go.mod h1:TrnaquDcYxWXfJrOjeMBTX4mLBeYAqaHEyUeWPxZlBM=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=\ngithub.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=\ngithub.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=\ngithub.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=\ngithub.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=\ngithub.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=\ngithub.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=\ngithub.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=\ngithub.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=\ngithub.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=\ngithub.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=\ngithub.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=\ngithub.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=\ngithub.com/zclconf/go-cty-yaml v1.1.0/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs=\ngo.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM=\ngo.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q=\ngo.etcd.io/etcd/client/pkg/v3 v3.6.8 h1:Qs/5C0LNFiqXxYf2GU8MVjYUEXJ6sZaYOz0zEqQgy50=\ngo.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw=\ngo.etcd.io/etcd/client/v3 v3.6.8 h1:B3G76t1UykqAOrbio7s/EPatixQDkQBevN8/mwiplrY=\ngo.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=\ngo.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=\ngo.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=\ngo.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=\ngolang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=\ngolang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=\ngolang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=\ngolang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=\ngolang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=\ngolang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=\ngolang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=\ngolang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=\ngolang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=\ngolang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=\ngolang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=\ngolang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=\ngolang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=\ngolang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=\ngolang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=\ngolang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=\ngoogle.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=\ngoogle.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d h1:vsOm753cOAMkt76efriTCDKjpCbK18XGHMJHo0JUKhc=\ngoogle.golang.org/genproto v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:0oz9d7g9QLSdv9/lgbIjowW1JoxMbxmBVNe8i6tORJI=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d h1:EocjzKLywydp5uZ5tJ79iP6Q0UjDnyiHkGRWxuPBP8s=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20260217215200-42d3e9bedb6d/go.mod h1:48U2I+QQUYhsFrg2SY6r+nJzeOtjey7j//WBESw+qyQ=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "pkg/cel/cel.go",
    "content": "package cel\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/google/cel-go/cel\"\n\t\"github.com/google/cel-go/checker\"\n\t\"github.com/google/cel-go/common/types/ref\"\n\t\"github.com/google/cel-go/ext\"\n\n\t\"github.com/dexidp/dex/pkg/cel/library\"\n)\n\n// EnvironmentVersion represents the version of the CEL environment.\n// New variables, functions, or libraries are introduced in new versions.\ntype EnvironmentVersion uint32\n\nconst (\n\t// EnvironmentV1 is the initial CEL environment.\n\tEnvironmentV1 EnvironmentVersion = 1\n)\n\n// CompilationResult holds a compiled CEL program ready for evaluation.\ntype CompilationResult struct {\n\tProgram    cel.Program\n\tOutputType *cel.Type\n\tExpression string\n\n\tast *cel.Ast\n}\n\n// CompilerOption configures a Compiler.\ntype CompilerOption func(*compilerConfig)\n\ntype compilerConfig struct {\n\tcostBudget uint64\n\tversion    EnvironmentVersion\n}\n\nfunc defaultCompilerConfig() *compilerConfig {\n\treturn &compilerConfig{\n\t\tcostBudget: DefaultCostBudget,\n\t\tversion:    EnvironmentV1,\n\t}\n}\n\n// WithCostBudget sets a custom cost budget for expression evaluation.\nfunc WithCostBudget(budget uint64) CompilerOption {\n\treturn func(cfg *compilerConfig) {\n\t\tcfg.costBudget = budget\n\t}\n}\n\n// WithVersion sets the target environment version for the compiler.\n// Defaults to the latest version. Specifying an older version ensures\n// that only functions/types available at that version are used.\nfunc WithVersion(v EnvironmentVersion) CompilerOption {\n\treturn func(cfg *compilerConfig) {\n\t\tcfg.version = v\n\t}\n}\n\n// Compiler compiles CEL expressions against a specific environment.\ntype Compiler struct {\n\tenv *cel.Env\n\tcfg *compilerConfig\n}\n\n// NewCompiler creates a new CEL compiler with the specified variable\n// declarations and options.\n//\n// All custom Dex libraries are automatically included.\n// The environment is configured with cost limits and safe defaults.\nfunc NewCompiler(variables []VariableDeclaration, opts ...CompilerOption) (*Compiler, error) {\n\tcfg := defaultCompilerConfig()\n\tfor _, opt := range opts {\n\t\topt(cfg)\n\t}\n\n\tenvOpts := make([]cel.EnvOption, 0, 8+len(variables))\n\tenvOpts = append(envOpts,\n\t\tcel.DefaultUTCTimeZone(true),\n\n\t\t// Standard extension libraries (same set as Kubernetes)\n\t\text.Strings(),\n\t\text.Encoders(),\n\t\text.Lists(),\n\t\text.Sets(),\n\t\text.Math(),\n\n\t\t// Native Go types for typed variable access.\n\t\t// This gives compile-time field checking: identity.emial → error at config load.\n\t\text.NativeTypes(\n\t\t\text.ParseStructTags(true),\n\t\t\treflect.TypeOf(IdentityVal{}),\n\t\t\treflect.TypeOf(RequestVal{}),\n\t\t),\n\n\t\t// Custom Dex libraries\n\t\tcel.Lib(&library.Email{}),\n\t\tcel.Lib(&library.Groups{}),\n\n\t\t// Presence tests like has(field) and 'key' in map are O(1) hash\n\t\t// lookups on map(string, dyn) variables, so they should not count\n\t\t// toward the cost budget. Without this, expressions with multiple\n\t\t// 'in' checks (e.g. \"'admin' in identity.groups\") would accumulate\n\t\t// inflated cost estimates. This matches Kubernetes CEL behavior\n\t\t// where presence tests are free for CRD validation rules.\n\t\tcel.CostEstimatorOptions(\n\t\t\tchecker.PresenceTestHasCost(false),\n\t\t),\n\t)\n\n\tfor _, v := range variables {\n\t\tenvOpts = append(envOpts, cel.Variable(v.Name, v.Type))\n\t}\n\n\tenv, err := cel.NewEnv(envOpts...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create CEL environment: %w\", err)\n\t}\n\n\treturn &Compiler{env: env, cfg: cfg}, nil\n}\n\n// CompileBool compiles a CEL expression that must evaluate to bool.\nfunc (c *Compiler) CompileBool(expression string) (*CompilationResult, error) {\n\treturn c.compile(expression, cel.BoolType)\n}\n\n// CompileString compiles a CEL expression that must evaluate to string.\nfunc (c *Compiler) CompileString(expression string) (*CompilationResult, error) {\n\treturn c.compile(expression, cel.StringType)\n}\n\n// CompileStringList compiles a CEL expression that must evaluate to list(string).\nfunc (c *Compiler) CompileStringList(expression string) (*CompilationResult, error) {\n\treturn c.compile(expression, cel.ListType(cel.StringType))\n}\n\n// Compile compiles a CEL expression with any output type.\nfunc (c *Compiler) Compile(expression string) (*CompilationResult, error) {\n\treturn c.compile(expression, nil)\n}\n\nfunc (c *Compiler) compile(expression string, expectedType *cel.Type) (*CompilationResult, error) {\n\tif len(expression) > MaxExpressionLength {\n\t\treturn nil, fmt.Errorf(\"expression exceeds maximum length of %d characters\", MaxExpressionLength)\n\t}\n\n\tast, issues := c.env.Compile(expression)\n\tif issues != nil && issues.Err() != nil {\n\t\treturn nil, fmt.Errorf(\"CEL compilation failed: %w\", issues.Err())\n\t}\n\n\tif expectedType != nil && !ast.OutputType().IsEquivalentType(expectedType) {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"expected expression output type %s, got %s\",\n\t\t\texpectedType, ast.OutputType(),\n\t\t)\n\t}\n\n\t// Estimate cost at compile time and reject expressions that are too expensive.\n\tcostEst, err := c.env.EstimateCost(ast, &defaultCostEstimator{})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"CEL cost estimation failed: %w\", err)\n\t}\n\n\tif costEst.Max > c.cfg.costBudget {\n\t\treturn nil, fmt.Errorf(\n\t\t\t\"CEL expression estimated cost %d exceeds budget %d\",\n\t\t\tcostEst.Max, c.cfg.costBudget,\n\t\t)\n\t}\n\n\tprog, err := c.env.Program(ast,\n\t\tcel.EvalOptions(cel.OptOptimize),\n\t\tcel.CostLimit(c.cfg.costBudget),\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"CEL program creation failed: %w\", err)\n\t}\n\n\treturn &CompilationResult{\n\t\tProgram:    prog,\n\t\tOutputType: ast.OutputType(),\n\t\tExpression: expression,\n\t\tast:        ast,\n\t}, nil\n}\n\n// Eval evaluates a compiled program against the given variables.\nfunc Eval(ctx context.Context, result *CompilationResult, variables map[string]any) (ref.Val, error) {\n\tout, _, err := result.Program.ContextEval(ctx, variables)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"CEL evaluation failed: %w\", err)\n\t}\n\n\treturn out, nil\n}\n\n// EvalBool is a convenience function that evaluates and asserts bool output.\nfunc EvalBool(ctx context.Context, result *CompilationResult, variables map[string]any) (bool, error) {\n\tout, err := Eval(ctx, result, variables)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tv, ok := out.Value().(bool)\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"expected bool result, got %T\", out.Value())\n\t}\n\n\treturn v, nil\n}\n\n// EvalString is a convenience function that evaluates and asserts string output.\nfunc EvalString(ctx context.Context, result *CompilationResult, variables map[string]any) (string, error) {\n\tout, err := Eval(ctx, result, variables)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tv, ok := out.Value().(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"expected string result, got %T\", out.Value())\n\t}\n\n\treturn v, nil\n}\n"
  },
  {
    "path": "pkg/cel/cel_test.go",
    "content": "package cel_test\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/connector\"\n\tdexcel \"github.com/dexidp/dex/pkg/cel\"\n)\n\nfunc TestCompileBool(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr    string\n\t\twantErr bool\n\t}{\n\t\t\"true literal\": {\n\t\t\texpr: \"true\",\n\t\t},\n\t\t\"comparison\": {\n\t\t\texpr: \"1 == 1\",\n\t\t},\n\t\t\"string type mismatch\": {\n\t\t\texpr:    \"'hello'\",\n\t\t\twantErr: true,\n\t\t},\n\t\t\"int type mismatch\": {\n\t\t\texpr:    \"42\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tresult, err := compiler.CompileBool(tc.expr)\n\t\t\tif tc.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\tassert.Nil(t, result)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.NotNil(t, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCompileString(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr    string\n\t\twantErr bool\n\t}{\n\t\t\"string literal\": {\n\t\t\texpr: \"'hello'\",\n\t\t},\n\t\t\"string concatenation\": {\n\t\t\texpr: \"'hello' + ' ' + 'world'\",\n\t\t},\n\t\t\"bool type mismatch\": {\n\t\t\texpr:    \"true\",\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tresult, err := compiler.CompileString(tc.expr)\n\t\t\tif tc.wantErr {\n\t\t\t\tassert.Error(t, err)\n\t\t\t} else {\n\t\t\t\tassert.NoError(t, err)\n\t\t\t\tassert.NotNil(t, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCompileStringList(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\tresult, err := compiler.CompileStringList(\"['a', 'b', 'c']\")\n\tassert.NoError(t, err)\n\tassert.NotNil(t, result)\n\n\t_, err = compiler.CompileStringList(\"'not a list'\")\n\tassert.Error(t, err)\n}\n\nfunc TestCompile(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\t// Compile accepts any type\n\tresult, err := compiler.Compile(\"true\")\n\tassert.NoError(t, err)\n\tassert.NotNil(t, result)\n\n\tresult, err = compiler.Compile(\"'hello'\")\n\tassert.NoError(t, err)\n\tassert.NotNil(t, result)\n\n\tresult, err = compiler.Compile(\"42\")\n\tassert.NoError(t, err)\n\tassert.NotNil(t, result)\n}\n\nfunc TestCompileErrors(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr string\n\t}{\n\t\t\"syntax error\": {\n\t\t\texpr: \"1 +\",\n\t\t},\n\t\t\"undefined variable\": {\n\t\t\texpr: \"undefined_var\",\n\t\t},\n\t\t\"undefined function\": {\n\t\t\texpr: \"undefinedFunc()\",\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\t_, err := compiler.Compile(tc.expr)\n\t\t\tassert.Error(t, err)\n\t\t})\n\t}\n}\n\nfunc TestCompileRejectsUnknownFields(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\t// Typo in field name: should fail at compile time with ObjectType\n\t_, err = compiler.CompileBool(\"identity.emial == 'test@example.com'\")\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), \"compilation failed\")\n\n\t// Type mismatch: comparing string field to int should fail at compile time\n\t_, err = compiler.CompileBool(\"identity.email == 123\")\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), \"compilation failed\")\n\n\t// Valid field: should compile fine\n\t_, err = compiler.CompileBool(\"identity.email == 'test@example.com'\")\n\tassert.NoError(t, err)\n}\n\nfunc TestMaxExpressionLength(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\tlongExpr := \"'\" + strings.Repeat(\"a\", dexcel.MaxExpressionLength) + \"'\"\n\t_, err = compiler.Compile(longExpr)\n\tassert.Error(t, err)\n\tassert.Contains(t, err.Error(), \"maximum length\")\n}\n\nfunc TestEvalBool(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr     string\n\t\tidentity dexcel.IdentityVal\n\t\twant     bool\n\t}{\n\t\t\"email endsWith\": {\n\t\t\texpr:     \"identity.email.endsWith('@example.com')\",\n\t\t\tidentity: dexcel.IdentityVal{Email: \"user@example.com\"},\n\t\t\twant:     true,\n\t\t},\n\t\t\"email endsWith false\": {\n\t\t\texpr:     \"identity.email.endsWith('@example.com')\",\n\t\t\tidentity: dexcel.IdentityVal{Email: \"user@other.com\"},\n\t\t\twant:     false,\n\t\t},\n\t\t\"email_verified\": {\n\t\t\texpr:     \"identity.email_verified == true\",\n\t\t\tidentity: dexcel.IdentityVal{EmailVerified: true},\n\t\t\twant:     true,\n\t\t},\n\t\t\"group membership\": {\n\t\t\texpr:     \"identity.groups.exists(g, g == 'admin')\",\n\t\t\tidentity: dexcel.IdentityVal{Groups: []string{\"admin\", \"dev\"}},\n\t\t\twant:     true,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tprog, err := compiler.CompileBool(tc.expr)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tresult, err := dexcel.EvalBool(context.Background(), prog, map[string]any{\n\t\t\t\t\"identity\": tc.identity,\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tc.want, result)\n\t\t})\n\t}\n}\n\nfunc TestEvalString(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\t// With ObjectType, identity.email is typed as string, so CompileString works.\n\tprog, err := compiler.CompileString(\"identity.email\")\n\trequire.NoError(t, err)\n\n\tresult, err := dexcel.EvalString(context.Background(), prog, map[string]any{\n\t\t\"identity\": dexcel.IdentityVal{Email: \"user@example.com\"},\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"user@example.com\", result)\n}\n\nfunc TestEvalWithIdentityAndRequest(t *testing.T) {\n\tvars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...)\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\tprog, err := compiler.CompileBool(\n\t\t`identity.email.endsWith('@example.com') && 'admin' in identity.groups && request.connector_id == 'okta'`,\n\t)\n\trequire.NoError(t, err)\n\n\tidentity := dexcel.IdentityFromConnector(connector.Identity{\n\t\tUserID:   \"123\",\n\t\tUsername: \"john\",\n\t\tEmail:    \"john@example.com\",\n\t\tGroups:   []string{\"admin\", \"dev\"},\n\t})\n\trequest := dexcel.RequestFromContext(dexcel.RequestContext{\n\t\tClientID:    \"my-app\",\n\t\tConnectorID: \"okta\",\n\t\tScopes:      []string{\"openid\", \"email\"},\n\t})\n\n\tresult, err := dexcel.EvalBool(context.Background(), prog, map[string]any{\n\t\t\"identity\": identity,\n\t\t\"request\":  request,\n\t})\n\trequire.NoError(t, err)\n\tassert.True(t, result)\n}\n\nfunc TestNewCompilerWithVariables(t *testing.T) {\n\t// Claims variable — remains map(string, dyn)\n\tcompiler, err := dexcel.NewCompiler(dexcel.ClaimsVariable())\n\trequire.NoError(t, err)\n\n\t// claims.email returns dyn from map access, use Compile (not CompileString)\n\tprog, err := compiler.Compile(\"claims.email\")\n\trequire.NoError(t, err)\n\n\tresult, err := dexcel.EvalString(context.Background(), prog, map[string]any{\n\t\t\"claims\": map[string]any{\n\t\t\t\"email\": \"test@example.com\",\n\t\t},\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"test@example.com\", result)\n}\n"
  },
  {
    "path": "pkg/cel/cost.go",
    "content": "package cel\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/google/cel-go/checker\"\n)\n\n// DefaultCostBudget is the default cost budget for a single expression\n// evaluation. Aligned with Kubernetes defaults: enough for typical identity\n// operations but prevents runaway expressions.\nconst DefaultCostBudget uint64 = 10_000_000\n\n// MaxExpressionLength is the maximum length of a CEL expression string.\nconst MaxExpressionLength = 10_240\n\n// DefaultStringMaxLength is the estimated max length of string values\n// (emails, usernames, group names, etc.) used for compile-time cost estimation.\nconst DefaultStringMaxLength = 256\n\n// DefaultListMaxLength is the estimated max length of list values\n// (groups, scopes) used for compile-time cost estimation.\nconst DefaultListMaxLength = 100\n\n// CostEstimate holds the estimated cost range for a compiled expression.\ntype CostEstimate struct {\n\tMin uint64\n\tMax uint64\n}\n\n// EstimateCost returns the estimated cost range for a compiled expression.\n// This is computed statically at compile time without evaluating the expression.\nfunc (c *Compiler) EstimateCost(result *CompilationResult) (CostEstimate, error) {\n\tcostEst, err := c.env.EstimateCost(result.ast, &defaultCostEstimator{})\n\tif err != nil {\n\t\treturn CostEstimate{}, fmt.Errorf(\"CEL cost estimation failed: %w\", err)\n\t}\n\n\treturn CostEstimate{Min: costEst.Min, Max: costEst.Max}, nil\n}\n\n// defaultCostEstimator provides size hints for compile-time cost estimation.\n// Without these hints, the CEL cost estimator assumes unbounded sizes for\n// variables, leading to wildly overestimated max costs.\ntype defaultCostEstimator struct{}\n\nfunc (defaultCostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstimate {\n\t// Provide size hints for map(string, dyn) variables: identity, request, claims.\n\t// Without these, the estimator assumes lists/strings can be infinitely large.\n\tif element.Path() == nil {\n\t\treturn nil\n\t}\n\n\tpath := element.Path()\n\tif len(path) == 0 {\n\t\treturn nil\n\t}\n\n\troot := path[0]\n\n\tswitch root {\n\tcase \"identity\", \"request\", \"claims\":\n\t\t// Nested field access (e.g. identity.email, identity.groups)\n\t\tif len(path) >= 2 {\n\t\t\tfield := path[1]\n\t\t\tswitch field {\n\t\t\tcase \"groups\", \"scopes\":\n\t\t\t\t// list(string) fields\n\t\t\t\treturn &checker.SizeEstimate{Min: 0, Max: DefaultListMaxLength}\n\t\t\tcase \"email_verified\":\n\t\t\t\t// bool field — size is always 1\n\t\t\t\treturn &checker.SizeEstimate{Min: 1, Max: 1}\n\t\t\tdefault:\n\t\t\t\t// string fields (email, username, user_id, client_id, etc.)\n\t\t\t\treturn &checker.SizeEstimate{Min: 0, Max: DefaultStringMaxLength}\n\t\t\t}\n\t\t}\n\t\t// The map itself: number of keys\n\t\treturn &checker.SizeEstimate{Min: 0, Max: 20}\n\t}\n\n\treturn nil\n}\n\nfunc (defaultCostEstimator) EstimateCallCost(function, overloadID string, target *checker.AstNode, args []checker.AstNode) *checker.CallEstimate {\n\tswitch function {\n\tcase \"dex.emailDomain\", \"dex.emailLocalPart\":\n\t\t// Simple string split — O(n) where n is string length, bounded.\n\t\treturn &checker.CallEstimate{\n\t\t\tCostEstimate: checker.CostEstimate{Min: 1, Max: 2},\n\t\t}\n\tcase \"dex.groupMatches\":\n\t\t// Iterates over groups list and matches each against a pattern.\n\t\treturn &checker.CallEstimate{\n\t\t\tCostEstimate: checker.CostEstimate{Min: 1, Max: DefaultListMaxLength},\n\t\t}\n\tcase \"dex.groupFilter\":\n\t\t// Builds a set from allowed list, then iterates groups.\n\t\treturn &checker.CallEstimate{\n\t\t\tCostEstimate: checker.CostEstimate{Min: 1, Max: 2 * DefaultListMaxLength},\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/cel/cost_test.go",
    "content": "package cel_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\tdexcel \"github.com/dexidp/dex/pkg/cel\"\n)\n\nfunc TestEstimateCost(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr string\n\t}{\n\t\t\"simple bool\": {\n\t\t\texpr: \"true\",\n\t\t},\n\t\t\"string comparison\": {\n\t\t\texpr: \"identity.email == 'test@example.com'\",\n\t\t},\n\t\t\"group membership\": {\n\t\t\texpr: \"identity.groups.exists(g, g == 'admin')\",\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tprog, err := compiler.Compile(tc.expr)\n\t\t\trequire.NoError(t, err)\n\n\t\t\test, err := compiler.EstimateCost(prog)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.True(t, est.Max >= est.Min, \"max cost should be >= min cost\")\n\t\t\tassert.True(t, est.Max <= dexcel.DefaultCostBudget,\n\t\t\t\t\"estimated max cost %d should be within default budget %d\", est.Max, dexcel.DefaultCostBudget)\n\t\t})\n\t}\n}\n\nfunc TestCompileTimeCostAcceptsSimpleExpressions(t *testing.T) {\n\tvars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...)\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\ttests := map[string]string{\n\t\t\"literal\":         \"true\",\n\t\t\"email endsWith\":  \"identity.email.endsWith('@example.com')\",\n\t\t\"group check\":     \"'admin' in identity.groups\",\n\t\t\"emailDomain\":     `dex.emailDomain(identity.email)`,\n\t\t\"groupMatches\":    `dex.groupMatches(identity.groups, \"team:*\")`,\n\t\t\"groupFilter\":     `dex.groupFilter(identity.groups, [\"admin\", \"dev\"])`,\n\t\t\"combined policy\": `identity.email.endsWith('@example.com') && 'admin' in identity.groups`,\n\t\t\"complex policy\": `identity.email.endsWith('@example.com') &&\n\t\t\tidentity.groups.exists(g, g == 'admin') &&\n\t\t\trequest.connector_id == 'okta' &&\n\t\t\trequest.scopes.exists(s, s == 'openid')`,\n\t\t\"filter+map chain\": `identity.groups\n\t\t\t.filter(g, g.startsWith('team:'))\n\t\t\t.map(g, g.replace('team:', ''))\n\t\t\t.size() > 0`,\n\t}\n\n\tfor name, expr := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\t_, err := compiler.Compile(expr)\n\t\t\tassert.NoError(t, err, \"expression should compile within default budget\")\n\t\t})\n\t}\n}\n\nfunc TestCompileTimeCostRejection(t *testing.T) {\n\tvars := append(dexcel.IdentityVariables(), dexcel.RequestVariables()...)\n\n\ttests := map[string]struct {\n\t\tbudget uint64\n\t\texpr   string\n\t}{\n\t\t\"simple exists exceeds tiny budget\": {\n\t\t\tbudget: 1,\n\t\t\texpr:   \"identity.groups.exists(g, g == 'admin')\",\n\t\t},\n\t\t\"endsWith exceeds tiny budget\": {\n\t\t\tbudget: 2,\n\t\t\texpr:   \"identity.email.endsWith('@example.com')\",\n\t\t},\n\t\t\"nested comprehension over groups exceeds moderate budget\": {\n\t\t\t// Two nested iterations over groups: O(n^2) where n=100 → ~280K\n\t\t\tbudget: 10_000,\n\t\t\texpr: `identity.groups.exists(g1,\n\t\t\t\tidentity.groups.exists(g2,\n\t\t\t\t\tg1 != g2 && g1.startsWith(g2)\n\t\t\t\t)\n\t\t\t)`,\n\t\t},\n\t\t\"cross-variable comprehension exceeds moderate budget\": {\n\t\t\t// filter groups then check each against scopes: O(n*m) → ~162K\n\t\t\tbudget: 10_000,\n\t\t\texpr: `identity.groups\n\t\t\t\t.filter(g, g.startsWith('team:'))\n\t\t\t\t.exists(g, request.scopes.exists(s, s == g))`,\n\t\t},\n\t\t\"chained filter+map+filter+map exceeds small budget\": {\n\t\t\tbudget: 1000,\n\t\t\texpr: `identity.groups\n\t\t\t\t.filter(g, g.startsWith('team:'))\n\t\t\t\t.map(g, g.replace('team:', ''))\n\t\t\t\t.filter(g, g.size() > 3)\n\t\t\t\t.map(g, g.upperAscii())\n\t\t\t\t.size() > 0`,\n\t\t},\n\t\t\"many independent exists exceeds small budget\": {\n\t\t\tbudget: 5000,\n\t\t\texpr: `identity.groups.exists(g, g.contains('a')) &&\n\t\t\t\tidentity.groups.exists(g, g.contains('b')) &&\n\t\t\t\tidentity.groups.exists(g, g.contains('c')) &&\n\t\t\t\tidentity.groups.exists(g, g.contains('d')) &&\n\t\t\t\tidentity.groups.exists(g, g.contains('e'))`,\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tcompiler, err := dexcel.NewCompiler(vars, dexcel.WithCostBudget(tc.budget))\n\t\t\trequire.NoError(t, err)\n\n\t\t\t_, err = compiler.Compile(tc.expr)\n\t\t\tassert.Error(t, err)\n\t\t\tassert.Contains(t, err.Error(), \"estimated cost\")\n\t\t\tassert.Contains(t, err.Error(), \"exceeds budget\")\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cel/doc.go",
    "content": "// Package cel provides a safe, sandboxed CEL (Common Expression Language)\n// environment for policy evaluation, claim mapping, and token customization\n// in Dex. It includes cost budgets, Kubernetes-grade compatibility guarantees,\n// and a curated set of extension libraries.\npackage cel\n"
  },
  {
    "path": "pkg/cel/library/doc.go",
    "content": "// Package library provides custom CEL function libraries for Dex.\n// Each library implements the cel.Library interface and can be registered\n// in a CEL environment.\npackage library\n"
  },
  {
    "path": "pkg/cel/library/email.go",
    "content": "package library\n\nimport (\n\t\"strings\"\n\n\t\"github.com/google/cel-go/cel\"\n\t\"github.com/google/cel-go/common/types\"\n\t\"github.com/google/cel-go/common/types/ref\"\n)\n\n// Email provides email-related CEL functions.\n//\n// Functions (V1):\n//\n//\tdex.emailDomain(email: string) -> string\n//\t  Returns the domain portion of an email address.\n//\t  Example: dex.emailDomain(\"user@example.com\") == \"example.com\"\n//\n//\tdex.emailLocalPart(email: string) -> string\n//\t  Returns the local part of an email address.\n//\t  Example: dex.emailLocalPart(\"user@example.com\") == \"user\"\ntype Email struct{}\n\nfunc (Email) CompileOptions() []cel.EnvOption {\n\treturn []cel.EnvOption{\n\t\tcel.Function(\"dex.emailDomain\",\n\t\t\tcel.Overload(\"dex_email_domain_string\",\n\t\t\t\t[]*cel.Type{cel.StringType},\n\t\t\t\tcel.StringType,\n\t\t\t\tcel.UnaryBinding(emailDomainImpl),\n\t\t\t),\n\t\t),\n\t\tcel.Function(\"dex.emailLocalPart\",\n\t\t\tcel.Overload(\"dex_email_local_part_string\",\n\t\t\t\t[]*cel.Type{cel.StringType},\n\t\t\t\tcel.StringType,\n\t\t\t\tcel.UnaryBinding(emailLocalPartImpl),\n\t\t\t),\n\t\t),\n\t}\n}\n\nfunc (Email) ProgramOptions() []cel.ProgramOption {\n\treturn nil\n}\n\nfunc emailDomainImpl(arg ref.Val) ref.Val {\n\temail, ok := arg.Value().(string)\n\tif !ok {\n\t\treturn types.NewErr(\"dex.emailDomain: expected string argument\")\n\t}\n\n\t_, domain, found := strings.Cut(email, \"@\")\n\tif !found {\n\t\treturn types.String(\"\")\n\t}\n\n\treturn types.String(domain)\n}\n\nfunc emailLocalPartImpl(arg ref.Val) ref.Val {\n\temail, ok := arg.Value().(string)\n\tif !ok {\n\t\treturn types.NewErr(\"dex.emailLocalPart: expected string argument\")\n\t}\n\n\tlocalPart, _, found := strings.Cut(email, \"@\")\n\tif !found {\n\t\treturn types.String(email)\n\t}\n\n\treturn types.String(localPart)\n}\n"
  },
  {
    "path": "pkg/cel/library/email_test.go",
    "content": "package library_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\tdexcel \"github.com/dexidp/dex/pkg/cel\"\n)\n\nfunc TestEmailDomain(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr string\n\t\twant string\n\t}{\n\t\t\"standard email\": {\n\t\t\texpr: `dex.emailDomain(\"user@example.com\")`,\n\t\t\twant: \"example.com\",\n\t\t},\n\t\t\"subdomain\": {\n\t\t\texpr: `dex.emailDomain(\"admin@sub.domain.org\")`,\n\t\t\twant: \"sub.domain.org\",\n\t\t},\n\t\t\"no at sign\": {\n\t\t\texpr: `dex.emailDomain(\"nodomain\")`,\n\t\t\twant: \"\",\n\t\t},\n\t\t\"empty string\": {\n\t\t\texpr: `dex.emailDomain(\"\")`,\n\t\t\twant: \"\",\n\t\t},\n\t\t\"multiple at signs\": {\n\t\t\texpr: `dex.emailDomain(\"user@name@example.com\")`,\n\t\t\twant: \"name@example.com\",\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tprog, err := compiler.CompileString(tc.expr)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tresult, err := dexcel.EvalString(context.Background(), prog, map[string]any{})\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tc.want, result)\n\t\t})\n\t}\n}\n\nfunc TestEmailLocalPart(t *testing.T) {\n\tcompiler, err := dexcel.NewCompiler(nil)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr string\n\t\twant string\n\t}{\n\t\t\"standard email\": {\n\t\t\texpr: `dex.emailLocalPart(\"user@example.com\")`,\n\t\t\twant: \"user\",\n\t\t},\n\t\t\"no at sign\": {\n\t\t\texpr: `dex.emailLocalPart(\"justuser\")`,\n\t\t\twant: \"justuser\",\n\t\t},\n\t\t\"empty string\": {\n\t\t\texpr: `dex.emailLocalPart(\"\")`,\n\t\t\twant: \"\",\n\t\t},\n\t\t\"multiple at signs\": {\n\t\t\texpr: `dex.emailLocalPart(\"user@name@example.com\")`,\n\t\t\twant: \"user\",\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tprog, err := compiler.CompileString(tc.expr)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tresult, err := dexcel.EvalString(context.Background(), prog, map[string]any{})\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tc.want, result)\n\t\t})\n\t}\n}\n\nfunc TestEmailDomainWithIdentityVariable(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\tprog, err := compiler.CompileString(`dex.emailDomain(identity.email)`)\n\trequire.NoError(t, err)\n\n\tresult, err := dexcel.EvalString(context.Background(), prog, map[string]any{\n\t\t\"identity\": dexcel.IdentityVal{Email: \"admin@corp.example.com\"},\n\t})\n\trequire.NoError(t, err)\n\tassert.Equal(t, \"corp.example.com\", result)\n}\n"
  },
  {
    "path": "pkg/cel/library/groups.go",
    "content": "package library\n\nimport (\n\t\"path\"\n\n\t\"github.com/google/cel-go/cel\"\n\t\"github.com/google/cel-go/common/types\"\n\t\"github.com/google/cel-go/common/types/ref\"\n\t\"github.com/google/cel-go/common/types/traits\"\n)\n\n// Groups provides group-related CEL functions.\n//\n// Functions (V1):\n//\n//\tdex.groupMatches(groups: list(string), pattern: string) -> list(string)\n//\t  Returns groups matching a glob pattern.\n//\t  Example: dex.groupMatches([\"team:dev\", \"team:ops\", \"admin\"], \"team:*\")\n//\n//\tdex.groupFilter(groups: list(string), allowed: list(string)) -> list(string)\n//\t  Returns only groups present in the allowed list.\n//\t  Example: dex.groupFilter([\"admin\", \"dev\", \"ops\"], [\"admin\", \"ops\"])\ntype Groups struct{}\n\nfunc (Groups) CompileOptions() []cel.EnvOption {\n\treturn []cel.EnvOption{\n\t\tcel.Function(\"dex.groupMatches\",\n\t\t\tcel.Overload(\"dex_group_matches_list_string\",\n\t\t\t\t[]*cel.Type{cel.ListType(cel.StringType), cel.StringType},\n\t\t\t\tcel.ListType(cel.StringType),\n\t\t\t\tcel.BinaryBinding(groupMatchesImpl),\n\t\t\t),\n\t\t),\n\t\tcel.Function(\"dex.groupFilter\",\n\t\t\tcel.Overload(\"dex_group_filter_list_list\",\n\t\t\t\t[]*cel.Type{cel.ListType(cel.StringType), cel.ListType(cel.StringType)},\n\t\t\t\tcel.ListType(cel.StringType),\n\t\t\t\tcel.BinaryBinding(groupFilterImpl),\n\t\t\t),\n\t\t),\n\t}\n}\n\nfunc (Groups) ProgramOptions() []cel.ProgramOption {\n\treturn nil\n}\n\nfunc groupMatchesImpl(lhs, rhs ref.Val) ref.Val {\n\tgroupList, ok := lhs.(traits.Lister)\n\tif !ok {\n\t\treturn types.NewErr(\"dex.groupMatches: expected list(string) as first argument\")\n\t}\n\n\tpattern, ok := rhs.Value().(string)\n\tif !ok {\n\t\treturn types.NewErr(\"dex.groupMatches: expected string pattern as second argument\")\n\t}\n\n\titer := groupList.Iterator()\n\tvar matched []ref.Val\n\n\tfor iter.HasNext() == types.True {\n\t\titem := iter.Next()\n\n\t\tgroup, ok := item.Value().(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tok, err := path.Match(pattern, group)\n\t\tif err != nil {\n\t\t\treturn types.NewErr(\"dex.groupMatches: invalid pattern %q: %v\", pattern, err)\n\t\t}\n\t\tif ok {\n\t\t\tmatched = append(matched, types.String(group))\n\t\t}\n\t}\n\n\treturn types.NewRefValList(types.DefaultTypeAdapter, matched)\n}\n\nfunc groupFilterImpl(lhs, rhs ref.Val) ref.Val {\n\tgroupList, ok := lhs.(traits.Lister)\n\tif !ok {\n\t\treturn types.NewErr(\"dex.groupFilter: expected list(string) as first argument\")\n\t}\n\n\tallowedList, ok := rhs.(traits.Lister)\n\tif !ok {\n\t\treturn types.NewErr(\"dex.groupFilter: expected list(string) as second argument\")\n\t}\n\n\tallowed := make(map[string]struct{})\n\titer := allowedList.Iterator()\n\tfor iter.HasNext() == types.True {\n\t\titem := iter.Next()\n\n\t\ts, ok := item.Value().(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tallowed[s] = struct{}{}\n\t}\n\n\tvar filtered []ref.Val\n\titer = groupList.Iterator()\n\n\tfor iter.HasNext() == types.True {\n\t\titem := iter.Next()\n\n\t\tgroup, ok := item.Value().(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tif _, exists := allowed[group]; exists {\n\t\t\tfiltered = append(filtered, types.String(group))\n\t\t}\n\t}\n\n\treturn types.NewRefValList(types.DefaultTypeAdapter, filtered)\n}\n"
  },
  {
    "path": "pkg/cel/library/groups_test.go",
    "content": "package library_test\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\tdexcel \"github.com/dexidp/dex/pkg/cel\"\n)\n\nfunc TestGroupMatches(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr   string\n\t\tgroups []string\n\t\twant   []string\n\t}{\n\t\t\"wildcard pattern\": {\n\t\t\texpr:   `dex.groupMatches(identity.groups, \"team:*\")`,\n\t\t\tgroups: []string{\"team:dev\", \"team:ops\", \"admin\"},\n\t\t\twant:   []string{\"team:dev\", \"team:ops\"},\n\t\t},\n\t\t\"exact match\": {\n\t\t\texpr:   `dex.groupMatches(identity.groups, \"admin\")`,\n\t\t\tgroups: []string{\"team:dev\", \"admin\", \"user\"},\n\t\t\twant:   []string{\"admin\"},\n\t\t},\n\t\t\"no matches\": {\n\t\t\texpr:   `dex.groupMatches(identity.groups, \"nonexistent\")`,\n\t\t\tgroups: []string{\"team:dev\", \"admin\"},\n\t\t\twant:   []string{},\n\t\t},\n\t\t\"question mark pattern\": {\n\t\t\texpr:   `dex.groupMatches(identity.groups, \"team?\")`,\n\t\t\tgroups: []string{\"teamA\", \"teamB\", \"teams-long\"},\n\t\t\twant:   []string{\"teamA\", \"teamB\"},\n\t\t},\n\t\t\"match all\": {\n\t\t\texpr:   `dex.groupMatches(identity.groups, \"*\")`,\n\t\t\tgroups: []string{\"a\", \"b\", \"c\"},\n\t\t\twant:   []string{\"a\", \"b\", \"c\"},\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tprog, err := compiler.CompileStringList(tc.expr)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tout, err := dexcel.Eval(context.Background(), prog, map[string]any{\n\t\t\t\t\"identity\": dexcel.IdentityVal{Groups: tc.groups},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnativeVal, err := out.ConvertToNative(reflect.TypeOf([]string{}))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tgot, ok := nativeVal.([]string)\n\t\t\trequire.True(t, ok, \"expected []string, got %T\", nativeVal)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n\nfunc TestGroupMatchesInvalidPattern(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\tprog, err := compiler.CompileStringList(`dex.groupMatches(identity.groups, \"[invalid\")`)\n\trequire.NoError(t, err)\n\n\t_, err = dexcel.Eval(context.Background(), prog, map[string]any{\n\t\t\"identity\": dexcel.IdentityVal{Groups: []string{\"admin\"}},\n\t})\n\trequire.Error(t, err)\n\tassert.Contains(t, err.Error(), \"invalid pattern\")\n}\n\nfunc TestGroupFilter(t *testing.T) {\n\tvars := dexcel.IdentityVariables()\n\tcompiler, err := dexcel.NewCompiler(vars)\n\trequire.NoError(t, err)\n\n\ttests := map[string]struct {\n\t\texpr   string\n\t\tgroups []string\n\t\twant   []string\n\t}{\n\t\t\"filter to allowed\": {\n\t\t\texpr:   `dex.groupFilter(identity.groups, [\"admin\", \"ops\"])`,\n\t\t\tgroups: []string{\"admin\", \"dev\", \"ops\"},\n\t\t\twant:   []string{\"admin\", \"ops\"},\n\t\t},\n\t\t\"no overlap\": {\n\t\t\texpr:   `dex.groupFilter(identity.groups, [\"marketing\"])`,\n\t\t\tgroups: []string{\"admin\", \"dev\"},\n\t\t\twant:   []string{},\n\t\t},\n\t\t\"all allowed\": {\n\t\t\texpr:   `dex.groupFilter(identity.groups, [\"a\", \"b\", \"c\"])`,\n\t\t\tgroups: []string{\"a\", \"b\", \"c\"},\n\t\t\twant:   []string{\"a\", \"b\", \"c\"},\n\t\t},\n\t\t\"empty allowed list\": {\n\t\t\texpr:   `dex.groupFilter(identity.groups, [])`,\n\t\t\tgroups: []string{\"admin\", \"dev\"},\n\t\t\twant:   []string{},\n\t\t},\n\t\t\"preserves order\": {\n\t\t\texpr:   `dex.groupFilter(identity.groups, [\"z\", \"a\"])`,\n\t\t\tgroups: []string{\"a\", \"b\", \"z\"},\n\t\t\twant:   []string{\"a\", \"z\"},\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tprog, err := compiler.CompileStringList(tc.expr)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tout, err := dexcel.Eval(context.Background(), prog, map[string]any{\n\t\t\t\t\"identity\": dexcel.IdentityVal{Groups: tc.groups},\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tnativeVal, err := out.ConvertToNative(reflect.TypeOf([]string{}))\n\t\t\trequire.NoError(t, err)\n\n\t\t\tgot, ok := nativeVal.([]string)\n\t\t\trequire.True(t, ok, \"expected []string, got %T\", nativeVal)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/cel/types.go",
    "content": "package cel\n\nimport (\n\t\"github.com/google/cel-go/cel\"\n\n\t\"github.com/dexidp/dex/connector\"\n)\n\n// VariableDeclaration declares a named variable and its CEL type\n// that will be available in expressions.\ntype VariableDeclaration struct {\n\tName string\n\tType *cel.Type\n}\n\n// IdentityVal is the CEL native type for the identity variable.\n// Fields are typed so that the CEL compiler rejects unknown field access\n// (e.g. identity.emial) at config load time rather than at evaluation time.\ntype IdentityVal struct {\n\tUserID            string   `cel:\"user_id\"`\n\tUsername          string   `cel:\"username\"`\n\tPreferredUsername string   `cel:\"preferred_username\"`\n\tEmail             string   `cel:\"email\"`\n\tEmailVerified     bool     `cel:\"email_verified\"`\n\tGroups            []string `cel:\"groups\"`\n}\n\n// RequestVal is the CEL native type for the request variable.\ntype RequestVal struct {\n\tClientID    string   `cel:\"client_id\"`\n\tConnectorID string   `cel:\"connector_id\"`\n\tScopes      []string `cel:\"scopes\"`\n\tRedirectURI string   `cel:\"redirect_uri\"`\n}\n\n// identityTypeName is the CEL type name for IdentityVal.\n// Derived by ext.NativeTypes as simplePkgAlias(pkgPath) + \".\" + structName.\nconst identityTypeName = \"cel.IdentityVal\"\n\n// requestTypeName is the CEL type name for RequestVal.\nconst requestTypeName = \"cel.RequestVal\"\n\n// IdentityVariables provides the 'identity' variable with typed fields.\n//\n//\tidentity.user_id            — string\n//\tidentity.username           — string\n//\tidentity.preferred_username — string\n//\tidentity.email              — string\n//\tidentity.email_verified     — bool\n//\tidentity.groups             — list(string)\nfunc IdentityVariables() []VariableDeclaration {\n\treturn []VariableDeclaration{\n\t\t{Name: \"identity\", Type: cel.ObjectType(identityTypeName)},\n\t}\n}\n\n// RequestVariables provides the 'request' variable with typed fields.\n//\n//\trequest.client_id     — string\n//\trequest.connector_id  — string\n//\trequest.scopes        — list(string)\n//\trequest.redirect_uri  — string\nfunc RequestVariables() []VariableDeclaration {\n\treturn []VariableDeclaration{\n\t\t{Name: \"request\", Type: cel.ObjectType(requestTypeName)},\n\t}\n}\n\n// ClaimsVariable provides a 'claims' map for raw upstream claims.\n// Claims remain map(string, dyn) because their shape is genuinely\n// unknown — they carry arbitrary upstream IdP data.\n//\n//\tclaims — map(string, dyn)\nfunc ClaimsVariable() []VariableDeclaration {\n\treturn []VariableDeclaration{\n\t\t{Name: \"claims\", Type: cel.MapType(cel.StringType, cel.DynType)},\n\t}\n}\n\n// IdentityFromConnector converts a connector.Identity to a CEL-compatible IdentityVal.\nfunc IdentityFromConnector(id connector.Identity) IdentityVal {\n\treturn IdentityVal{\n\t\tUserID:            id.UserID,\n\t\tUsername:          id.Username,\n\t\tPreferredUsername: id.PreferredUsername,\n\t\tEmail:             id.Email,\n\t\tEmailVerified:     id.EmailVerified,\n\t\tGroups:            id.Groups,\n\t}\n}\n\n// RequestContext represents the authentication/token request context\n// available as the 'request' variable in CEL expressions.\ntype RequestContext struct {\n\tClientID    string\n\tConnectorID string\n\tScopes      []string\n\tRedirectURI string\n}\n\n// RequestFromContext converts a RequestContext to a CEL-compatible RequestVal.\nfunc RequestFromContext(rc RequestContext) RequestVal {\n\treturn RequestVal{\n\t\tClientID:    rc.ClientID,\n\t\tConnectorID: rc.ConnectorID,\n\t\tScopes:      rc.Scopes,\n\t\tRedirectURI: rc.RedirectURI,\n\t}\n}\n"
  },
  {
    "path": "pkg/featureflags/doc.go",
    "content": "// Package featureflags provides a mechanism for toggling experimental or\n// optional Dex features via environment variables (DEX_<FLAG_NAME>).\npackage featureflags\n"
  },
  {
    "path": "pkg/featureflags/flag.go",
    "content": "package featureflags\n\nimport (\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype flag struct {\n\tName    string\n\tDefault bool\n}\n\nfunc (f *flag) env() string {\n\treturn \"DEX_\" + strings.ToUpper(f.Name)\n}\n\nfunc (f *flag) Enabled() bool {\n\traw := os.Getenv(f.env())\n\tif raw == \"\" {\n\t\treturn f.Default\n\t}\n\n\tres, err := strconv.ParseBool(raw)\n\tif err != nil {\n\t\treturn f.Default\n\t}\n\treturn res\n}\n\nfunc newFlag(s string, d bool) *flag {\n\treturn &flag{Name: s, Default: d}\n}\n"
  },
  {
    "path": "pkg/featureflags/set.go",
    "content": "package featureflags\n\nvar (\n\t// EntEnabled enables experimental ent-based engine for the database storages.\n\t// https://entgo.io/\n\tEntEnabled = newFlag(\"ent_enabled\", false)\n\n\t// ExpandEnv can enable or disable env expansion in the config which can be useful in environments where, e.g.,\n\t// $ sign is a part of the password for LDAP user.\n\tExpandEnv = newFlag(\"expand_env\", true)\n\n\t// APIConnectorsCRUD allows CRUD operations on connectors through the gRPC API\n\tAPIConnectorsCRUD = newFlag(\"api_connectors_crud\", false)\n\n\t// ContinueOnConnectorFailure allows the server to start even if some connectors fail to initialize.\n\tContinueOnConnectorFailure = newFlag(\"continue_on_connector_failure\", true)\n\n\t// ConfigDisallowUnknownFields enables to forbid unknown fields in the config while unmarshaling.\n\tConfigDisallowUnknownFields = newFlag(\"config_disallow_unknown_fields\", false)\n\n\t// ClientCredentialGrantEnabledByDefault enables the client_credentials grant type by default\n\t// without requiring explicit configuration in oauth2.grantTypes.\n\tClientCredentialGrantEnabledByDefault = newFlag(\"client_credential_grant_enabled_by_default\", false)\n\n\t// SessionsEnabled enables experimental auth sessions support.\n\tSessionsEnabled = newFlag(\"sessions_enabled\", false)\n)\n"
  },
  {
    "path": "pkg/groups/doc.go",
    "content": "// Package groups contains helper functions related to groups.\npackage groups\n"
  },
  {
    "path": "pkg/groups/groups.go",
    "content": "package groups\n\n// Filter filters out any groups of given that are not in required. Thus it may\n// happen that the resulting slice is empty.\nfunc Filter(given, required []string) []string {\n\tgroups := []string{}\n\tgroupFilter := make(map[string]struct{})\n\tfor _, group := range required {\n\t\tgroupFilter[group] = struct{}{}\n\t}\n\tfor _, group := range given {\n\t\tif _, ok := groupFilter[group]; ok {\n\t\t\tgroups = append(groups, group)\n\t\t}\n\t}\n\treturn groups\n}\n"
  },
  {
    "path": "pkg/groups/groups_test.go",
    "content": "package groups_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/dexidp/dex/pkg/groups\"\n)\n\nfunc TestFilter(t *testing.T) {\n\tcases := map[string]struct {\n\t\tgiven, required, expected []string\n\t}{\n\t\t\"nothing given\":                 {given: []string{}, required: []string{\"ops\"}, expected: []string{}},\n\t\t\"exactly one match\":             {given: []string{\"foo\"}, required: []string{\"foo\"}, expected: []string{\"foo\"}},\n\t\t\"no group of the required ones\": {given: []string{\"foo\", \"bar\"}, required: []string{\"baz\"}, expected: []string{}},\n\t\t\"subset matching\":               {given: []string{\"foo\", \"bar\", \"baz\"}, required: []string{\"bar\", \"baz\"}, expected: []string{\"bar\", \"baz\"}},\n\t}\n\tfor name, tc := range cases {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tactual := groups.Filter(tc.given, tc.required)\n\t\t\tassert.ElementsMatch(t, tc.expected, actual)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/httpclient/doc.go",
    "content": "// Package httpclient provides a configurable HTTP client constructor with\n// support for custom CA certificates, root CAs, and TLS settings.\npackage httpclient\n"
  },
  {
    "path": "pkg/httpclient/httpclient.go",
    "content": "package httpclient\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"time\"\n)\n\nfunc extractCAs(input []string) [][]byte {\n\tresult := make([][]byte, 0, len(input))\n\tfor _, ca := range input {\n\t\tif ca == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\tpemData, err := os.ReadFile(ca)\n\t\tif err != nil {\n\t\t\tpemData, err = base64.StdEncoding.DecodeString(ca)\n\t\t\tif err != nil {\n\t\t\t\tpemData = []byte(ca)\n\t\t\t}\n\t\t}\n\n\t\tresult = append(result, pemData)\n\t}\n\treturn result\n}\n\nfunc NewHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, error) {\n\tpool, err := x509.SystemCertPool()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttlsConfig := tls.Config{RootCAs: pool, InsecureSkipVerify: insecureSkipVerify}\n\tfor index, rootCABytes := range extractCAs(rootCAs) {\n\t\tif !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {\n\t\t\treturn nil, fmt.Errorf(\"rootCAs.%d is not in PEM format, certificate must be \"+\n\t\t\t\t\"a PEM encoded string, a base64 encoded bytes that contain PEM encoded string, \"+\n\t\t\t\t\"or a path to a PEM encoded certificate\", index)\n\t\t}\n\t}\n\n\treturn &http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tTLSClientConfig: &tlsConfig,\n\t\t\tProxy:           http.ProxyFromEnvironment,\n\t\t\tDialContext: (&net.Dialer{\n\t\t\t\tTimeout:   30 * time.Second,\n\t\t\t\tKeepAlive: 30 * time.Second,\n\t\t\t\tDualStack: true,\n\t\t\t}).DialContext,\n\t\t\tMaxIdleConns:          100,\n\t\t\tIdleConnTimeout:       90 * time.Second,\n\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t\tExpectContinueTimeout: 1 * time.Second,\n\t\t},\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/httpclient/httpclient_test.go",
    "content": "package httpclient_test\n\nimport (\n\t\"crypto/tls\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\"github.com/dexidp/dex/pkg/httpclient\"\n)\n\nfunc TestRootCAs(t *testing.T) {\n\tts, err := NewLocalHTTPSTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprint(w, \"Hello, client\")\n\t}))\n\tassert.Nil(t, err)\n\tdefer ts.Close()\n\n\trunTest := func(name string, certs []string) {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trootCAs := certs\n\t\t\ttestClient, err := httpclient.NewHTTPClient(rootCAs, false)\n\t\t\tassert.Nil(t, err)\n\n\t\t\tres, err := testClient.Get(ts.URL)\n\t\t\tassert.Nil(t, err)\n\n\t\t\tgreeting, err := io.ReadAll(res.Body)\n\t\t\tres.Body.Close()\n\t\t\tassert.Nil(t, err)\n\n\t\t\tassert.Equal(t, \"Hello, client\", string(greeting))\n\t\t})\n\t}\n\n\trunTest(\"From file\", []string{\"testdata/rootCA.pem\"})\n\n\tcontent, err := os.ReadFile(\"testdata/rootCA.pem\")\n\tassert.NoError(t, err)\n\trunTest(\"From string\", []string{string(content)})\n\n\tcontentStr := base64.StdEncoding.EncodeToString(content)\n\trunTest(\"From bytes\", []string{contentStr})\n}\n\nfunc TestInsecureSkipVerify(t *testing.T) {\n\tts, err := NewLocalHTTPSTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprint(w, \"Hello, client\")\n\t}))\n\tassert.Nil(t, err)\n\tdefer ts.Close()\n\n\tinsecureSkipVerify := true\n\n\ttestClient, err := httpclient.NewHTTPClient(nil, insecureSkipVerify)\n\tassert.Nil(t, err)\n\n\tres, err := testClient.Get(ts.URL)\n\tassert.Nil(t, err)\n\n\tgreeting, err := io.ReadAll(res.Body)\n\tres.Body.Close()\n\tassert.Nil(t, err)\n\n\tassert.Equal(t, \"Hello, client\", string(greeting))\n}\n\nfunc NewLocalHTTPSTestServer(handler http.Handler) (*httptest.Server, error) {\n\tts := httptest.NewUnstartedServer(handler)\n\tcert, err := tls.LoadX509KeyPair(\"testdata/server.crt\", \"testdata/server.key\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}\n\tts.StartTLS()\n\treturn ts, nil\n}\n"
  },
  {
    "path": "pkg/httpclient/readme.md",
    "content": "# Regenerate testdata\n\n### server.csr.cnf\n\n```\n[req]\ndefault_bits = 2048\nprompt = no\ndefault_md = sha256\ndistinguished_name = dn\n\n[dn]\nC=US\nST=RandomState\nL=RandomCity\nO=RandomOrganization\nOU=RandomOrganizationUnit\nemailAddress=hello@example.com\nCN = localhost\n```\n\nand\n\n### v3.ext\n```\nauthorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = localhost\nIP.1 = 127.0.0.1\n```\n\n### Then enter the following commands:\n\n`openssl genrsa -out rootCA.key 2048`\n\n`openssl req -x509 -new -nodes -key rootCA.key -sha256 -days 3650 -out rootCA.pem -config server.csr.cnf`\n\n`openssl req -new -sha256 -nodes -out server.csr -newkey rsa:2048 -keyout server.key -config server.csr.cnf`\n\n`openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 3650 -sha256 -extfile v3.ext`\n"
  },
  {
    "path": "pkg/httpclient/testdata/rootCA.key",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA4dB5aQCjCmMsW71u9F0WNm1TYjXQBZ4p7oNT+BQwCc/MZ2xc\n5NexS2O86nbRkw5jwyfAAMSMKRr9s2FluVTHqiln78rg+XUgmrmNT3ZroLmW6QL6\nCa8dbMPky+tQclZsvMd3HAeCyyrs4pf7wM1AyUJD7H0xAlVD1fsohkg7jhBFUfV+\nq2VMMdnsaV5vFrW/2vPBWz1SNPW/Xm+Ilny7xg9njQLcPMNtVtF+7EPB6sxD6qrj\nBC+Kj5zQ3bZOfdrh7yy63dbh/Kh+3NScgO+k+x92HlAjRIvj5y4KrbGZl7CmOth5\ny7fPywApVbDfZRWJChI1PVflOyDdnC+vhMLbHQIDAQABAoIBAEmjrrQrXP/6L3EL\naa+O27uME3Enk1sBpTL+6Ncx3iiU91eS4whNvqeTMvxTGy0VuDrgL6EQd5TAFJP2\n4zF5EFPRhO+R/aPcKnHKqOaM+7RCUZBTRC78SGA70dUeO/HNdVBqy9D8Mg8HRJDw\nd0z8om//iB8LBHx6SdDyQtjnnWRKFTzQRurBBoyLe2vPMFtINKtNUkahjc8HE4GO\naIv1LICJUzf4ZnkntKd5cFHZ42R2Tmfj0Y9G9DyJbuSA3+0u5IhYB39Uy6jFxLi8\nI5PoIVhgYZ0aivsVBIviShwQ9kgv6807YBxt22eSNovBDrSp+cAnIF9+p0b3MnkU\naCHSiBECgYEA84lssi6AqfCEsSiQMSM9kMCXJ4KQI/l7pmrIA50+V5HSEby9lg2Y\nN6XJ4V4q46t8FcZBjmMvzn9fwiPMRw5e995cVNBQ31a1FX/1Hy6RNtEiLZRnkHI5\nWznY9IxQ+c9JXJeFY1sO0BfO0TS3WvOf1rwqOb92q+cQaItnPQ+4Ya8CgYEA7V7e\nIqW3PpO4H+c5hH9egM0BjAxH71C9YpYzZpF9uiPIkuMnJ8nm9bB6RiuDaYCxvrfE\nA0h/SQewoYJKL4OfKGjrbG7U4zLMZHIWlf8Za55Zik5BNjvgBqFFrrSgLUGxdRTX\nN0+TlWlW1bvJblWpdjIbJbg/6kCU98TzK852fvMCgYAWYa/apElw1MjtGyQ9T9bN\nodWCbQ5gMAJ8Jd4h7uaW17DtrmHiE3fEzXjDPItGhzENMz49HsJ7ANvFFNMmSJzT\nvNzRcp+sFuTnh+34Iqh32DqC49usu8KnrqZQu0CJ5NICL26z1d+DolyAf47GThOH\ngZ2D1yPJ4p9wbDddtj8kwwKBgCFKB68mPG+rOcxHmjppvnAj0A66/i+izBySYf0F\ndHNxZ0SqVKhw2VIlgNBsc86M/OB5VyT6utccG/paklrdg6mgJTwcwwBl9GI12dMJ\nZqBAIeCSnvSjKwTjAynALSKLrv5zgMdCArmWf1YUMuilXNG1rzb4AwawLfQdi9jd\n6KJfAoGBALFl6ldywl3sGPk9K2xCDYYhb1TNQyheA5YvoZzZ6XCo1q0Lbwy/FamZ\n0TSWkoEmGB/Hck3HgtZDRo3CTI1vYfbpAtgI7oD1NA1zMaLulNQxKjH3iVvyb+R7\nZcIT7EVPZgkUwr0bsp22yVDekh/CHoB6FZPCyoAb8WnfJfooTBzB\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "pkg/httpclient/testdata/rootCA.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIID1jCCAr4CCQCG4JBeSi6cDjANBgkqhkiG9w0BAQsFADCBrDELMAkGA1UEBhMC\nVVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQHDApSYW5kb21DaXR5MRsw\nGQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNVBAsMFlJhbmRvbU9yZ2Fu\naXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxvQGV4YW1wbGUuY29tMRIw\nEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIxMDA3MjIwNjQwWhcNMzIxMDA0MjIwNjQw\nWjCBrDELMAkGA1UEBhMCVVMxFDASBgNVBAgMC1JhbmRvbVN0YXRlMRMwEQYDVQQH\nDApSYW5kb21DaXR5MRswGQYDVQQKDBJSYW5kb21Pcmdhbml6YXRpb24xHzAdBgNV\nBAsMFlJhbmRvbU9yZ2FuaXphdGlvblVuaXQxIDAeBgkqhkiG9w0BCQEWEWhlbGxv\nQGV4YW1wbGUuY29tMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB\nAQUAA4IBDwAwggEKAoIBAQDh0HlpAKMKYyxbvW70XRY2bVNiNdAFninug1P4FDAJ\nz8xnbFzk17FLY7zqdtGTDmPDJ8AAxIwpGv2zYWW5VMeqKWfvyuD5dSCauY1Pdmug\nuZbpAvoJrx1sw+TL61ByVmy8x3ccB4LLKuzil/vAzUDJQkPsfTECVUPV+yiGSDuO\nEEVR9X6rZUwx2expXm8Wtb/a88FbPVI09b9eb4iWfLvGD2eNAtw8w21W0X7sQ8Hq\nzEPqquMEL4qPnNDdtk592uHvLLrd1uH8qH7c1JyA76T7H3YeUCNEi+PnLgqtsZmX\nsKY62HnLt8/LAClVsN9lFYkKEjU9V+U7IN2cL6+EwtsdAgMBAAEwDQYJKoZIhvcN\nAQELBQADggEBAN6g0qit/3R2X+KdR0LgRXF/h4qQFgcV6cxnhRAmLIDNJlxKSHqN\nIE5+bxzCbkblzGfr/jNPqW0s+yaN4CyMgKNYSzkLBPE4FF+19Uv+dyYfFms3mDJ7\n0rGjS5bCscThWhpaSw20LcwQcr/+X+/fGzJ01dVFK1UOjBKg4d4dMwxklbIkZqIq\nsiRW0GMy26mgVZ/BSjeh5kEjs6h6H3cJsGl7xYT+BI7wnxHwGeT9tkBgiyT5FwaS\nvtdZkBpQ9q8f7FwsEm3woLHdWuOnrtUtVpY/oc6WFGdROQdGzjSk0D3kHs9YhueC\nGSzZKrqX+TSIgpPrLYNHX4uxlo5TAwP/5GM=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "pkg/httpclient/testdata/rootCA.srl",
    "content": "C1B35F0051A641BB\n"
  },
  {
    "path": "pkg/httpclient/testdata/server.crt",
    "content": "-----BEGIN CERTIFICATE-----\nMIIE5TCCA82gAwIBAgIJAMGzXwBRpkG7MA0GCSqGSIb3DQEBCwUAMIGsMQswCQYD\nVQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzARBgNVBAcMClJhbmRvbUNp\ndHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEfMB0GA1UECwwWUmFuZG9t\nT3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYRaGVsbG9AZXhhbXBsZS5j\nb20xEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjEwMDcyMjA3MDhaFw0zMjEwMDQy\nMjA3MDhaMIGsMQswCQYDVQQGEwJVUzEUMBIGA1UECAwLUmFuZG9tU3RhdGUxEzAR\nBgNVBAcMClJhbmRvbUNpdHkxGzAZBgNVBAoMElJhbmRvbU9yZ2FuaXphdGlvbjEf\nMB0GA1UECwwWUmFuZG9tT3JnYW5pemF0aW9uVW5pdDEgMB4GCSqGSIb3DQEJARYR\naGVsbG9AZXhhbXBsZS5jb20xEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZI\nhvcNAQEBBQADggEPADCCAQoCggEBAMuKdpXP87Q7Kg3iafXzvBuVIyV1K5UmMYiN\nkoztkC5XrCzHaQRS/CoIb7/nUqmtAxx7RL0jzhZ93zBN4HY/Zcnrd9tXoPPxi0mG\nZZWfFU6nN8nOkMHWzEbHVBmhxpfGtwmLcajQ4HrK1TZwJUn6GqclHQRy/gjxkiw5\nKPqzfVOVlA6ht4KdKstKazQkWZ5gdWT4d8yrEy/IT4oaW05xALBMQ7YGjkzWKsSF\n6ygXI7xqF9rg9jCnUsPYg4f8ut3N0c00KjsfKOOj2dF/ZyjedQ5c0u4hHmxSo3Ka\n0ZTmIrMfbVXgGjxRG2HZXLpPvQKoCf/fOX8Irdr+lahFVKASxN0CAwEAAaOCAQYw\nggECMIHLBgNVHSMEgcMwgcChgbKkga8wgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQI\nDAtSYW5kb21TdGF0ZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFu\nZG9tT3JnYW5pemF0aW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0\nMSAwHgYJKoZIhvcNAQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9j\nYWxob3N0ggkAhuCQXkounA4wCQYDVR0TBAIwADALBgNVHQ8EBAMCBPAwGgYDVR0R\nBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQCWmh5ebpkm\nv2B1yQgarSCSSkLZ5DZSAJjrPgW2IJqCW2q2D1HworbW1Yn5jqrM9FKGnJfjCyve\nzBB5AOlGp+0bsZGgMRMCavgv4QhTThXUoJqqHcfEu4wHndcgrqSadxmV5aisSR4u\ngXnjW43o3akby+h1K40RR3vVkpzPaoC3/bgk7WVpfpPiP32E24a01gETozRb/of/\nATN3JBe0xh+e63CrPX1sago5+u3UETIoOr0fW8M/gU9GApmJiFAXwHag6j54hLCG\n23EtVDwmlarG8Pj+i0yru8s22QqzAJi5E0OwR4aB8tqicLKYBVfzyLCOielIBUrK\nOkuFKp+VjxQX\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "pkg/httpclient/testdata/server.csr",
    "content": "-----BEGIN CERTIFICATE REQUEST-----\nMIIC8jCCAdoCAQAwgawxCzAJBgNVBAYTAlVTMRQwEgYDVQQIDAtSYW5kb21TdGF0\nZTETMBEGA1UEBwwKUmFuZG9tQ2l0eTEbMBkGA1UECgwSUmFuZG9tT3JnYW5pemF0\naW9uMR8wHQYDVQQLDBZSYW5kb21Pcmdhbml6YXRpb25Vbml0MSAwHgYJKoZIhvcN\nAQkBFhFoZWxsb0BleGFtcGxlLmNvbTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjAN\nBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy4p2lc/ztDsqDeJp9fO8G5UjJXUr\nlSYxiI2SjO2QLlesLMdpBFL8Kghvv+dSqa0DHHtEvSPOFn3fME3gdj9lyet321eg\n8/GLSYZllZ8VTqc3yc6QwdbMRsdUGaHGl8a3CYtxqNDgesrVNnAlSfoapyUdBHL+\nCPGSLDko+rN9U5WUDqG3gp0qy0prNCRZnmB1ZPh3zKsTL8hPihpbTnEAsExDtgaO\nTNYqxIXrKBcjvGoX2uD2MKdSw9iDh/y63c3RzTQqOx8o46PZ0X9nKN51DlzS7iEe\nbFKjcprRlOYisx9tVeAaPFEbYdlcuk+9AqgJ/985fwit2v6VqEVUoBLE3QIDAQAB\noAAwDQYJKoZIhvcNAQELBQADggEBADjuujIFoDJllR6Xo/w7j5vfNOeHO5GSgxF2\nXnuuDOI9Tomi7vURFZNbz3VAYiehpxRxYqLwFoQUwFtux2qRuGyg0P9fP1iQXPUE\nQUfFXmvB80uf2bG4lkbUwnmlZLFOEwhGZyPxpvsrxp2Ei2ppkUopCkzOMsSk3m0X\nMC50ZsTHOxfkA3r1WmS7oE2c0p0Fvyx+UJw0URAXFvDS1X0ONgww3FxqbBbm9W37\n5N4FZzGAK6j1wzuynKKXrn20YDCANXYH55PZyupfCeSZT0H0AZifWL7rz/G9uqme\nRzbIYc/CNQQTympjinBegQdVeB3yjVNZIvpGOuPSKQqhwFtmDFo=\n-----END CERTIFICATE REQUEST-----\n"
  },
  {
    "path": "pkg/httpclient/testdata/server.csr.cnf",
    "content": "[req]\ndefault_bits = 2048\nprompt = no\ndefault_md = sha256\ndistinguished_name = dn\n\n[dn]\nC=US\nST=RandomState\nL=RandomCity\nO=RandomOrganization\nOU=RandomOrganizationUnit\nemailAddress=hello@example.com\nCN = localhost\n"
  },
  {
    "path": "pkg/httpclient/testdata/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDLinaVz/O0OyoN\n4mn187wblSMldSuVJjGIjZKM7ZAuV6wsx2kEUvwqCG+/51KprQMce0S9I84Wfd8w\nTeB2P2XJ63fbV6Dz8YtJhmWVnxVOpzfJzpDB1sxGx1QZocaXxrcJi3Go0OB6ytU2\ncCVJ+hqnJR0Ecv4I8ZIsOSj6s31TlZQOobeCnSrLSms0JFmeYHVk+HfMqxMvyE+K\nGltOcQCwTEO2Bo5M1irEhesoFyO8ahfa4PYwp1LD2IOH/LrdzdHNNCo7Hyjjo9nR\nf2co3nUOXNLuIR5sUqNymtGU5iKzH21V4Bo8URth2Vy6T70CqAn/3zl/CK3a/pWo\nRVSgEsTdAgMBAAECggEAU6cxu7q+54kVbKVsdThaTF/MFR4F7oPHAd9lpuQQSOuh\niLngMHXGy6OyAgYZlEDWMYN8KdwoXFgZPaoUIaVGuWk8Vnq6XOgeHfbNk2PRhwT0\nyc1K80/Lnx9XMj2p+EEkgxi7eu12BSGN5ZTLzo6rG50GQwjb3WMjd2d6rybL0GjC\nwg2arcBk3sSMYmvZOqlAsaQmtgwkJhvhVkVfEQSD3VKF7g0dh/h3LIPyM0Ff4M67\nKpLMPPwzUJ/0Z4ewAP06mMKUA86R93M+dWs2eh1oBGnRkVQdhCJLXJpuGHZ6BTiB\nRy0AeorHfnVXPbtpUeAq6m5/BBl6qX0ooB08BIFwAQKBgQDqJpTZS/ZzqL6Kcs14\nMyFu+7DungSxQ5oK9ju7EFSosanSk4UEa/lw992kM6nsIMwgSVQgba5zKcVMeSmk\nAVbpznegQD1BYCwOGwbGvkJ8jbhPy+WLbbRjWT/E6AItZgUK+fyTIcNvSehcQqsT\nfhgWsK7ueZCmLQfVhK1AxtvY3QKBgQDeiKuo8plsH/7IxDn7KVHBOHKPC2ZPzg03\ni7La6zomiRckwwPnhicRSYsjtfCCW6Ms+uzjTEItgFM+5PdrXheeku+z/sExRtZu\nemqPqDomixlXDRQ6RN3gnBSk4RU+ROB1u1uBLWXqRz8Gp2zJGRxhHfYt2zefBv4w\n/cIuPC3cAQKBgD2UsAkGJWb9tj8LOmama+CYaUwYWvuT3+uKHuNvxBQpxZQQICet\njgjb53rL66Cib4z+PBXbQsoe7jjSlNUBVS5gkq2et31+IZgEG6AhYbMIQrUZ1uD4\nlTybuF289vWhoynj3T2E37VhJq89CWky/HrbNOabKiPKLAlHv5kNs7wxAoGBANEJ\nXQbU7J2O6Iy7FyQBSlTQq3wHX1Iz4mJ9DcNrFzK/sEfOEMrZT7WDefpPm984KW3F\nP+S766ZGVuxLtMbcmh9RM23HLr8VJbSdtZ/AjO9L1r/Y/1lE+49TzmibLpNRq++r\n0WbkuEl8J44ek6fLuMbZmDi3JeZycTCgDlnUGdgBAoGAYdliovtURZCm46t1uE3F\nidCLCXCccjkt1hcNGNjck/b0trHA7wOEqICIguoWDlEBTc0PDvHEq6PfKyqptGkj\nAgaZTMF/aZiGqlT7VRpBuzxM/uV5xzCg+i2ViaW/p3xq0z2PRljVZiEfe5aWcjiM\nouTtnC3TgmcjhTgGmb48QQE=\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "pkg/httpclient/testdata/v3.ext",
    "content": "authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\nDNS.1 = localhost\nIP.1 = 127.0.0.1\n"
  },
  {
    "path": "scripts/git-version",
    "content": "#!/bin/sh -e\n\n# parse the current git commit hash\nCOMMIT=`git rev-parse --short=8 HEAD`\n\n# check if the current commit has a matching tag (filter for v* tags, excluding api/)\nTAG=$(git describe --exact-match --abbrev=0 --tags --match=\"v[0-9]*\" 2> /dev/null || true)\n\n# use the matching tag as the version, if available\nif [ -z \"$TAG\" ]; then\n    # No exact tag on current commit, find the last version tag and bump minor version\n    # Get all tags matching v[0-9]*, sort them, and take the last one\n    LAST_TAG=$(git tag --list \"v[0-9]*\" --sort=-version:refname | head -1)\n\n    if [ -z \"$LAST_TAG\" ]; then\n        # No tags found, use v0.1.0 as fallback\n        BASE_VERSION=\"v0.1.0\"\n    else\n        # Parse the last tag and bump minor version\n        # Remove 'v' prefix\n        TAG_WITHOUT_V=\"${LAST_TAG#v}\"\n\n        # Split version into parts (major.minor.patch)\n        MAJOR=$(echo \"$TAG_WITHOUT_V\" | cut -d. -f1)\n        MINOR=$(echo \"$TAG_WITHOUT_V\" | cut -d. -f2)\n        PATCH=$(echo \"$TAG_WITHOUT_V\" | cut -d. -f3)\n\n        # Bump minor version\n        MINOR=$((MINOR + 1))\n\n        # Construct base version with bumped minor\n        BASE_VERSION=\"v${MAJOR}.${MINOR}.0\"\n    fi\n\n    # Get commit timestamp in YYYYMMDDhhmmss format\n    TIMESTAMP=$(git log -1 --format=%ci HEAD | sed 's/[-: ]//g' | cut -c1-14)\n\n    # Construct pseudo-version\n    VERSION=\"${BASE_VERSION}-${TIMESTAMP}-${COMMIT}\"\nelse\n    VERSION=$TAG\nfi\n\n# check for changed files (not untracked files)\nif [ -n \"$(git diff --shortstat 2> /dev/null | tail -n1)\" ]; then\n    VERSION=\"${VERSION}-dirty\"\nfi\n\necho $VERSION\n"
  },
  {
    "path": "scripts/manifests/.editorconfig",
    "content": "[{*.yml,*.yaml}]\nindent_size = 2\n"
  },
  {
    "path": "scripts/manifests/crds/authcodes.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authcodes.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: AuthCode\n    listKind: AuthCodeList\n    plural: authcodes\n    singular: authcode\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/authrequests.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: authrequests.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: AuthRequest\n    listKind: AuthRequestList\n    plural: authrequests\n    singular: authrequest\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/connectors.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: connectors.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: Connector\n    listKind: ConnectorList\n    plural: connectors\n    singular: connector\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/devicerequests.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: devicerequests.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: DeviceRequest\n    listKind: DeviceRequestList\n    plural: devicerequests\n    singular: devicerequest\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/devicetokens.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: devicetokens.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: DeviceToken\n    listKind: DeviceTokenList\n    plural: devicetokens\n    singular: devicetoken\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/oauth2clients.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: oauth2clients.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: OAuth2Client\n    listKind: OAuth2ClientList\n    plural: oauth2clients\n    singular: oauth2client\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/offlinesessionses.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: offlinesessionses.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: OfflineSessions\n    listKind: OfflineSessionsList\n    plural: offlinesessionses\n    singular: offlinesessions\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/passwords.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: passwords.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: Password\n    listKind: PasswordList\n    plural: passwords\n    singular: password\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/refreshtokens.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: refreshtokens.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: RefreshToken\n    listKind: RefreshTokenList\n    plural: refreshtokens\n    singular: refreshtoken\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/manifests/crds/signingkeies.yaml",
    "content": "apiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  name: signingkeies.dex.coreos.com\nspec:\n  group: dex.coreos.com\n  names:\n    kind: SigningKey\n    listKind: SigningKeyList\n    plural: signingkeies\n    singular: signingkey\n  scope: Namespaced\n  versions:\n  - name: v1\n    served: true\n    storage: true\n    schema:\n      openAPIV3Schema:\n        type: object\n        x-kubernetes-preserve-unknown-fields: true\n"
  },
  {
    "path": "scripts/update-gomplate",
    "content": "#!/bin/sh -e\n# Script to check for a new gomplate version and update it in Dockerfile\n\nGOMPLATE_REPO=\"hairyhenderson/gomplate\"\nDOCKERFILE=\"${1:-.}/Dockerfile\"\n\n# Check if Dockerfile exists\nif [ ! -f \"$DOCKERFILE\" ]; then\n    echo \"Error: Dockerfile not found at $DOCKERFILE\"\n    exit 1\nfi\n\n# Get the latest release version from GitHub\necho \"Checking for the latest gomplate version on GitHub...\"\nLATEST_VERSION=$(curl -s \"https://api.github.com/repos/${GOMPLATE_REPO}/releases/latest\" | grep -o '\"tag_name\": \"[^\"]*\"' | head -1 | cut -d'\"' -f4)\n\nif [ -z \"$LATEST_VERSION\" ]; then\n    echo \"Error: Could not fetch the latest version from GitHub\"\n    exit 1\nfi\n\necho \"Latest gomplate version: $LATEST_VERSION\"\n\n# Get the current version from Dockerfile\nCURRENT_VERSION=$(grep 'ENV GOMPLATE_VERSION' \"$DOCKERFILE\" | sed 's/.*GOMPLATE_VERSION=//;s/[[:space:]]*$//')\n\necho \"Current gomplate version in Dockerfile: $CURRENT_VERSION\"\n\n# Check if versions are different\nif [ \"$LATEST_VERSION\" = \"$CURRENT_VERSION\" ]; then\n    echo \"✓ Already on the latest version ($LATEST_VERSION)\"\n    exit 0\nfi\n\necho \"✓ New version available: $LATEST_VERSION\"\necho \"Updating Dockerfile...\"\n\n# Update the Dockerfile - use a more specific pattern to avoid multiple replacements\nsed -i '' \"s/ENV GOMPLATE_VERSION=.*/ENV GOMPLATE_VERSION=${LATEST_VERSION}/\" \"$DOCKERFILE\"\n\nif grep -q \"ENV GOMPLATE_VERSION=${LATEST_VERSION}\" \"$DOCKERFILE\"; then\n    echo \"✓ Successfully updated Dockerfile to version $LATEST_VERSION\"\n    echo \"\"\n    echo \"Changes made:\"\n    echo \"  - GOMPLATE_VERSION: $CURRENT_VERSION → $LATEST_VERSION\"\nelse\n    echo \"Error: Failed to update Dockerfile\"\n    exit 1\nfi\n\n\n\n\n"
  },
  {
    "path": "server/api.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strconv\"\n\n\t\"golang.org/x/crypto/bcrypt\"\n\n\t\"github.com/dexidp/dex/api/v2\"\n\t\"github.com/dexidp/dex/pkg/featureflags\"\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// apiVersion increases every time a new call is added to the API. Clients should use this info\n// to determine if the server supports specific features.\nconst apiVersion = 3\n\nconst (\n\t// recCost is the recommended bcrypt cost, which balances hash strength and\n\t// efficiency.\n\trecCost = 12\n\n\t// upBoundCost is a sane upper bound on bcrypt cost determined by benchmarking:\n\t// high enough to ensure secure encryption, low enough to not put unnecessary\n\t// load on a dex server.\n\tupBoundCost = 16\n)\n\n// NewAPI returns a server which implements the gRPC API interface.\nfunc NewAPI(s storage.Storage, logger *slog.Logger, version string, server *Server) api.DexServer {\n\treturn dexAPI{\n\t\ts:       s,\n\t\tlogger:  logger.With(\"component\", \"api\"),\n\t\tversion: version,\n\t\tserver:  server,\n\t}\n}\n\ntype dexAPI struct {\n\tapi.UnimplementedDexServer\n\n\ts       storage.Storage\n\tlogger  *slog.Logger\n\tversion string\n\tserver  *Server\n}\n\nfunc (d dexAPI) GetClient(ctx context.Context, req *api.GetClientReq) (*api.GetClientResp, error) {\n\tc, err := d.s.GetClient(ctx, req.Id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn &api.GetClientResp{\n\t\tClient: &api.Client{\n\t\t\tId:                c.ID,\n\t\t\tName:              c.Name,\n\t\t\tSecret:            c.Secret,\n\t\t\tRedirectUris:      c.RedirectURIs,\n\t\t\tTrustedPeers:      c.TrustedPeers,\n\t\t\tPublic:            c.Public,\n\t\t\tLogoUrl:           c.LogoURL,\n\t\t\tAllowedConnectors: c.AllowedConnectors,\n\t\t},\n\t}, nil\n}\n\nfunc (d dexAPI) CreateClient(ctx context.Context, req *api.CreateClientReq) (*api.CreateClientResp, error) {\n\tif req.Client == nil {\n\t\treturn nil, errors.New(\"no client supplied\")\n\t}\n\n\tif req.Client.Id == \"\" {\n\t\treq.Client.Id = storage.NewID()\n\t}\n\tif req.Client.Secret == \"\" && !req.Client.Public {\n\t\treq.Client.Secret = storage.NewID() + storage.NewID()\n\t}\n\n\tc := storage.Client{\n\t\tID:                req.Client.Id,\n\t\tSecret:            req.Client.Secret,\n\t\tRedirectURIs:      req.Client.RedirectUris,\n\t\tTrustedPeers:      req.Client.TrustedPeers,\n\t\tPublic:            req.Client.Public,\n\t\tName:              req.Client.Name,\n\t\tLogoURL:           req.Client.LogoUrl,\n\t\tAllowedConnectors: req.Client.AllowedConnectors,\n\t}\n\tif err := d.s.CreateClient(ctx, c); err != nil {\n\t\tif err == storage.ErrAlreadyExists {\n\t\t\treturn &api.CreateClientResp{AlreadyExists: true}, nil\n\t\t}\n\t\td.logger.Error(\"failed to create client\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"create client: %v\", err)\n\t}\n\n\treturn &api.CreateClientResp{\n\t\tClient: req.Client,\n\t}, nil\n}\n\nfunc (d dexAPI) UpdateClient(ctx context.Context, req *api.UpdateClientReq) (*api.UpdateClientResp, error) {\n\tif req.Id == \"\" {\n\t\treturn nil, errors.New(\"update client: no client ID supplied\")\n\t}\n\n\terr := d.s.UpdateClient(ctx, req.Id, func(old storage.Client) (storage.Client, error) {\n\t\tif req.RedirectUris != nil {\n\t\t\told.RedirectURIs = req.RedirectUris\n\t\t}\n\t\tif req.TrustedPeers != nil {\n\t\t\told.TrustedPeers = req.TrustedPeers\n\t\t}\n\t\tif req.Name != \"\" {\n\t\t\told.Name = req.Name\n\t\t}\n\t\tif req.LogoUrl != \"\" {\n\t\t\told.LogoURL = req.LogoUrl\n\t\t}\n\t\tif req.AllowedConnectors != nil {\n\t\t\told.AllowedConnectors = req.AllowedConnectors\n\t\t}\n\t\treturn old, nil\n\t})\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.UpdateClientResp{NotFound: true}, nil\n\t\t}\n\t\td.logger.Error(\"failed to update the client\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"update client: %v\", err)\n\t}\n\treturn &api.UpdateClientResp{}, nil\n}\n\nfunc (d dexAPI) DeleteClient(ctx context.Context, req *api.DeleteClientReq) (*api.DeleteClientResp, error) {\n\terr := d.s.DeleteClient(ctx, req.Id)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.DeleteClientResp{NotFound: true}, nil\n\t\t}\n\t\td.logger.Error(\"failed to delete client\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"delete client: %v\", err)\n\t}\n\treturn &api.DeleteClientResp{}, nil\n}\n\nfunc (d dexAPI) ListClients(ctx context.Context, req *api.ListClientReq) (*api.ListClientResp, error) {\n\tclientList, err := d.s.ListClients(ctx)\n\tif err != nil {\n\t\td.logger.Error(\"failed to list clients\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"list clients: %v\", err)\n\t}\n\n\tclients := make([]*api.ClientInfo, 0, len(clientList))\n\tfor _, client := range clientList {\n\t\tc := api.ClientInfo{\n\t\t\tId:                client.ID,\n\t\t\tName:              client.Name,\n\t\t\tRedirectUris:      client.RedirectURIs,\n\t\t\tTrustedPeers:      client.TrustedPeers,\n\t\t\tPublic:            client.Public,\n\t\t\tLogoUrl:           client.LogoURL,\n\t\t\tAllowedConnectors: client.AllowedConnectors,\n\t\t}\n\t\tclients = append(clients, &c)\n\t}\n\n\treturn &api.ListClientResp{\n\t\tClients: clients,\n\t}, nil\n}\n\n// checkCost returns an error if the hash provided does not meet lower or upper\n// bound cost requirements.\nfunc checkCost(hash []byte) error {\n\tactual, err := bcrypt.Cost(hash)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"parsing bcrypt hash: %v\", err)\n\t}\n\tif actual < bcrypt.DefaultCost {\n\t\treturn fmt.Errorf(\"given hash cost = %d does not meet minimum cost requirement = %d\", actual, bcrypt.DefaultCost)\n\t}\n\tif actual > upBoundCost {\n\t\treturn fmt.Errorf(\"given hash cost = %d is above upper bound cost = %d, recommended cost = %d\", actual, upBoundCost, recCost)\n\t}\n\treturn nil\n}\n\nfunc (d dexAPI) CreatePassword(ctx context.Context, req *api.CreatePasswordReq) (*api.CreatePasswordResp, error) {\n\tif req.Password == nil {\n\t\treturn nil, errors.New(\"no password supplied\")\n\t}\n\tif req.Password.UserId == \"\" {\n\t\treturn nil, errors.New(\"no user ID supplied\")\n\t}\n\tif req.Password.Hash != nil {\n\t\tif err := checkCost(req.Password.Hash); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\treturn nil, errors.New(\"no hash of password supplied\")\n\t}\n\n\tp := storage.Password{\n\t\tEmail:    req.Password.Email,\n\t\tHash:     req.Password.Hash,\n\t\tUsername: req.Password.Username,\n\t\tUserID:   req.Password.UserId,\n\t}\n\tif err := d.s.CreatePassword(ctx, p); err != nil {\n\t\tif err == storage.ErrAlreadyExists {\n\t\t\treturn &api.CreatePasswordResp{AlreadyExists: true}, nil\n\t\t}\n\t\td.logger.Error(\"failed to create password\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"create password: %v\", err)\n\t}\n\n\treturn &api.CreatePasswordResp{}, nil\n}\n\nfunc (d dexAPI) UpdatePassword(ctx context.Context, req *api.UpdatePasswordReq) (*api.UpdatePasswordResp, error) {\n\tif req.Email == \"\" {\n\t\treturn nil, errors.New(\"no email supplied\")\n\t}\n\tif req.NewHash == nil && req.NewUsername == \"\" {\n\t\treturn nil, errors.New(\"nothing to update\")\n\t}\n\n\tif req.NewHash != nil {\n\t\tif err := checkCost(req.NewHash); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tupdater := func(old storage.Password) (storage.Password, error) {\n\t\tif req.NewHash != nil {\n\t\t\told.Hash = req.NewHash\n\t\t}\n\n\t\tif req.NewUsername != \"\" {\n\t\t\told.Username = req.NewUsername\n\t\t}\n\n\t\treturn old, nil\n\t}\n\n\tif err := d.s.UpdatePassword(ctx, req.Email, updater); err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.UpdatePasswordResp{NotFound: true}, nil\n\t\t}\n\t\td.logger.Error(\"failed to update password\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"update password: %v\", err)\n\t}\n\n\treturn &api.UpdatePasswordResp{}, nil\n}\n\nfunc (d dexAPI) DeletePassword(ctx context.Context, req *api.DeletePasswordReq) (*api.DeletePasswordResp, error) {\n\tif req.Email == \"\" {\n\t\treturn nil, errors.New(\"no email supplied\")\n\t}\n\n\terr := d.s.DeletePassword(ctx, req.Email)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.DeletePasswordResp{NotFound: true}, nil\n\t\t}\n\t\td.logger.Error(\"failed to delete password\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"delete password: %v\", err)\n\t}\n\treturn &api.DeletePasswordResp{}, nil\n}\n\nfunc (d dexAPI) GetVersion(ctx context.Context, req *api.VersionReq) (*api.VersionResp, error) {\n\treturn &api.VersionResp{\n\t\tServer: d.version,\n\t\tApi:    apiVersion,\n\t}, nil\n}\n\nfunc (d dexAPI) GetDiscovery(ctx context.Context, req *api.DiscoveryReq) (*api.DiscoveryResp, error) {\n\tdiscoveryDoc := d.server.constructDiscovery(ctx)\n\tdata, err := json.Marshal(discoveryDoc)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal discovery data: %v\", err)\n\t}\n\tresp := api.DiscoveryResp{}\n\terr = json.Unmarshal(data, &resp)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal discovery data: %v\", err)\n\t}\n\treturn &resp, nil\n}\n\nfunc (d dexAPI) ListPasswords(ctx context.Context, req *api.ListPasswordReq) (*api.ListPasswordResp, error) {\n\tpasswordList, err := d.s.ListPasswords(ctx)\n\tif err != nil {\n\t\td.logger.Error(\"failed to list passwords\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"list passwords: %v\", err)\n\t}\n\n\tpasswords := make([]*api.Password, 0, len(passwordList))\n\tfor _, password := range passwordList {\n\t\tp := api.Password{\n\t\t\tEmail:    password.Email,\n\t\t\tUsername: password.Username,\n\t\t\tUserId:   password.UserID,\n\t\t}\n\t\tpasswords = append(passwords, &p)\n\t}\n\n\treturn &api.ListPasswordResp{\n\t\tPasswords: passwords,\n\t}, nil\n}\n\nfunc (d dexAPI) VerifyPassword(ctx context.Context, req *api.VerifyPasswordReq) (*api.VerifyPasswordResp, error) {\n\tif req.Email == \"\" {\n\t\treturn nil, errors.New(\"no email supplied\")\n\t}\n\n\tif req.Password == \"\" {\n\t\treturn nil, errors.New(\"no password to verify supplied\")\n\t}\n\n\tpassword, err := d.s.GetPassword(ctx, req.Email)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.VerifyPasswordResp{\n\t\t\t\tNotFound: true,\n\t\t\t}, nil\n\t\t}\n\t\td.logger.Error(\"there was an error retrieving the password\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"verify password: %v\", err)\n\t}\n\n\tif err := bcrypt.CompareHashAndPassword(password.Hash, []byte(req.Password)); err != nil {\n\t\td.logger.Info(\"password check failed\", \"err\", err)\n\t\treturn &api.VerifyPasswordResp{\n\t\t\tVerified: false,\n\t\t}, nil\n\t}\n\treturn &api.VerifyPasswordResp{\n\t\tVerified: true,\n\t}, nil\n}\n\nfunc (d dexAPI) ListRefresh(ctx context.Context, req *api.ListRefreshReq) (*api.ListRefreshResp, error) {\n\tid := new(internal.IDTokenSubject)\n\tif err := internal.Unmarshal(req.UserId, id); err != nil {\n\t\td.logger.Error(\"failed to unmarshal ID Token subject\", \"err\", err)\n\t\treturn nil, err\n\t}\n\n\tofflineSessions, err := d.s.GetOfflineSessions(ctx, id.UserId, id.ConnId)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\t// This means that this user-client pair does not have a refresh token yet.\n\t\t\t// An empty list should be returned instead of an error.\n\t\t\treturn &api.ListRefreshResp{}, nil\n\t\t}\n\t\td.logger.Error(\"failed to list refresh tokens here\", \"err\", err)\n\t\treturn nil, err\n\t}\n\n\trefreshTokenRefs := make([]*api.RefreshTokenRef, 0, len(offlineSessions.Refresh))\n\tfor _, session := range offlineSessions.Refresh {\n\t\tr := api.RefreshTokenRef{\n\t\t\tId:        session.ID,\n\t\t\tClientId:  session.ClientID,\n\t\t\tCreatedAt: session.CreatedAt.Unix(),\n\t\t\tLastUsed:  session.LastUsed.Unix(),\n\t\t}\n\t\trefreshTokenRefs = append(refreshTokenRefs, &r)\n\t}\n\n\treturn &api.ListRefreshResp{\n\t\tRefreshTokens: refreshTokenRefs,\n\t}, nil\n}\n\nfunc (d dexAPI) RevokeRefresh(ctx context.Context, req *api.RevokeRefreshReq) (*api.RevokeRefreshResp, error) {\n\tid := new(internal.IDTokenSubject)\n\tif err := internal.Unmarshal(req.UserId, id); err != nil {\n\t\td.logger.Error(\"failed to unmarshal ID Token subject\", \"err\", err)\n\t\treturn nil, err\n\t}\n\n\tvar (\n\t\trefreshID string\n\t\tnotFound  bool\n\t)\n\tupdater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) {\n\t\trefreshRef := old.Refresh[req.ClientId]\n\t\tif refreshRef == nil || refreshRef.ID == \"\" {\n\t\t\td.logger.Error(\"refresh token issued to client not found for deletion\", \"client_id\", req.ClientId, \"user_id\", id.UserId)\n\t\t\tnotFound = true\n\t\t\treturn old, storage.ErrNotFound\n\t\t}\n\n\t\trefreshID = refreshRef.ID\n\n\t\t// Remove entry from Refresh list of the OfflineSession object.\n\t\tdelete(old.Refresh, req.ClientId)\n\n\t\treturn old, nil\n\t}\n\n\tif err := d.s.UpdateOfflineSessions(ctx, id.UserId, id.ConnId, updater); err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.RevokeRefreshResp{NotFound: true}, nil\n\t\t}\n\t\td.logger.Error(\"failed to update offline session object\", \"err\", err)\n\t\treturn nil, err\n\t}\n\n\tif notFound {\n\t\treturn &api.RevokeRefreshResp{NotFound: true}, nil\n\t}\n\n\t// Delete the refresh token from the storage\n\t//\n\t// TODO(ericchiang): we don't have any good recourse if this call fails.\n\t// Consider garbage collection of refresh tokens with no associated ref.\n\tif err := d.s.DeleteRefresh(ctx, refreshID); err != nil {\n\t\td.logger.Error(\"failed to delete refresh token\", \"err\", err)\n\t\treturn nil, err\n\t}\n\n\treturn &api.RevokeRefreshResp{}, nil\n}\n\nfunc (d dexAPI) CreateConnector(ctx context.Context, req *api.CreateConnectorReq) (*api.CreateConnectorResp, error) {\n\tif !featureflags.APIConnectorsCRUD.Enabled() {\n\t\treturn nil, fmt.Errorf(\"%s feature flag is not enabled\", featureflags.APIConnectorsCRUD.Name)\n\t}\n\n\tif req.Connector.Id == \"\" {\n\t\treturn nil, errors.New(\"no id supplied\")\n\t}\n\n\tif req.Connector.Type == \"\" {\n\t\treturn nil, errors.New(\"no type supplied\")\n\t}\n\n\tif req.Connector.Name == \"\" {\n\t\treturn nil, errors.New(\"no name supplied\")\n\t}\n\n\tif len(req.Connector.Config) == 0 {\n\t\treturn nil, errors.New(\"no config supplied\")\n\t}\n\n\tif !json.Valid(req.Connector.Config) {\n\t\treturn nil, errors.New(\"invalid config supplied\")\n\t}\n\n\tfor _, gt := range req.Connector.GrantTypes {\n\t\tif !ConnectorGrantTypes[gt] {\n\t\t\treturn nil, fmt.Errorf(\"unknown grant type %q\", gt)\n\t\t}\n\t}\n\n\tc := storage.Connector{\n\t\tID:              req.Connector.Id,\n\t\tName:            req.Connector.Name,\n\t\tType:            req.Connector.Type,\n\t\tResourceVersion: \"1\",\n\t\tConfig:          req.Connector.Config,\n\t\tGrantTypes:      req.Connector.GrantTypes,\n\t}\n\tif err := d.s.CreateConnector(ctx, c); err != nil {\n\t\tif err == storage.ErrAlreadyExists {\n\t\t\treturn &api.CreateConnectorResp{AlreadyExists: true}, nil\n\t\t}\n\t\td.logger.Error(\"api: failed to create connector\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"create connector: %v\", err)\n\t}\n\n\t// Make sure we don't reuse stale entries in the cache\n\tif d.server != nil {\n\t\td.server.CloseConnector(req.Connector.Id)\n\t}\n\n\treturn &api.CreateConnectorResp{}, nil\n}\n\nfunc (d dexAPI) UpdateConnector(ctx context.Context, req *api.UpdateConnectorReq) (*api.UpdateConnectorResp, error) {\n\tif !featureflags.APIConnectorsCRUD.Enabled() {\n\t\treturn nil, fmt.Errorf(\"%s feature flag is not enabled\", featureflags.APIConnectorsCRUD.Name)\n\t}\n\n\tif req.Id == \"\" {\n\t\treturn nil, errors.New(\"no email supplied\")\n\t}\n\n\thasUpdate := len(req.NewConfig) != 0 ||\n\t\treq.NewName != \"\" ||\n\t\treq.NewType != \"\" ||\n\t\treq.NewGrantTypes != nil\n\tif !hasUpdate {\n\t\treturn nil, errors.New(\"nothing to update\")\n\t}\n\n\tif len(req.NewConfig) != 0 && !json.Valid(req.NewConfig) {\n\t\treturn nil, errors.New(\"invalid config supplied\")\n\t}\n\n\tif req.NewGrantTypes != nil {\n\t\tfor _, gt := range req.NewGrantTypes.GrantTypes {\n\t\t\tif !ConnectorGrantTypes[gt] {\n\t\t\t\treturn nil, fmt.Errorf(\"unknown grant type %q\", gt)\n\t\t\t}\n\t\t}\n\t}\n\n\tupdater := func(old storage.Connector) (storage.Connector, error) {\n\t\tif req.NewType != \"\" {\n\t\t\told.Type = req.NewType\n\t\t}\n\n\t\tif req.NewName != \"\" {\n\t\t\told.Name = req.NewName\n\t\t}\n\n\t\tif len(req.NewConfig) != 0 {\n\t\t\told.Config = req.NewConfig\n\t\t}\n\n\t\tif req.NewGrantTypes != nil {\n\t\t\told.GrantTypes = req.NewGrantTypes.GrantTypes\n\t\t}\n\n\t\tif rev, err := strconv.Atoi(defaultTo(old.ResourceVersion, \"0\")); err == nil {\n\t\t\told.ResourceVersion = strconv.Itoa(rev + 1)\n\t\t}\n\n\t\treturn old, nil\n\t}\n\n\tif err := d.s.UpdateConnector(ctx, req.Id, updater); err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.UpdateConnectorResp{NotFound: true}, nil\n\t\t}\n\t\td.logger.Error(\"api: failed to update connector\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"update connector: %v\", err)\n\t}\n\n\treturn &api.UpdateConnectorResp{}, nil\n}\n\nfunc (d dexAPI) DeleteConnector(ctx context.Context, req *api.DeleteConnectorReq) (*api.DeleteConnectorResp, error) {\n\tif !featureflags.APIConnectorsCRUD.Enabled() {\n\t\treturn nil, fmt.Errorf(\"%s feature flag is not enabled\", featureflags.APIConnectorsCRUD.Name)\n\t}\n\n\tif req.Id == \"\" {\n\t\treturn nil, errors.New(\"no id supplied\")\n\t}\n\n\terr := d.s.DeleteConnector(ctx, req.Id)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn &api.DeleteConnectorResp{NotFound: true}, nil\n\t\t}\n\t\td.logger.Error(\"api: failed to delete connector\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"delete connector: %v\", err)\n\t}\n\n\treturn &api.DeleteConnectorResp{}, nil\n}\n\nfunc (d dexAPI) ListConnectors(ctx context.Context, req *api.ListConnectorReq) (*api.ListConnectorResp, error) {\n\tif !featureflags.APIConnectorsCRUD.Enabled() {\n\t\treturn nil, fmt.Errorf(\"%s feature flag is not enabled\", featureflags.APIConnectorsCRUD.Name)\n\t}\n\n\tconnectorList, err := d.s.ListConnectors(ctx)\n\tif err != nil {\n\t\td.logger.Error(\"api: failed to list connectors\", \"err\", err)\n\t\treturn nil, fmt.Errorf(\"list connectors: %v\", err)\n\t}\n\n\tconnectors := make([]*api.Connector, 0, len(connectorList))\n\tfor _, connector := range connectorList {\n\t\tc := api.Connector{\n\t\t\tId:         connector.ID,\n\t\t\tName:       connector.Name,\n\t\t\tType:       connector.Type,\n\t\t\tConfig:     connector.Config,\n\t\t\tGrantTypes: connector.GrantTypes,\n\t\t}\n\t\tconnectors = append(connectors, &c)\n\t}\n\n\treturn &api.ListConnectorResp{\n\t\tConnectors: connectors,\n\t}, nil\n}\n\nfunc defaultTo[T comparable](v, def T) T {\n\tvar zeroT T\n\tif v == zeroT {\n\t\treturn def\n\t}\n\treturn v\n}\n"
  },
  {
    "path": "server/api_cache_test.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/api/v2\"\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/connector/mock\"\n\t\"github.com/dexidp/dex/storage/memory\"\n)\n\nfunc TestConnectorCacheInvalidation(t *testing.T) {\n\tt.Setenv(\"DEX_API_CONNECTORS_CRUD\", \"true\")\n\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tserv := &Server{\n\t\tstorage:    s,\n\t\tlogger:     logger,\n\t\tconnectors: make(map[string]Connector),\n\t}\n\n\tapiServer := NewAPI(s, logger, \"test\", serv)\n\tctx := context.Background()\n\n\tconnID := \"mock-conn\"\n\n\t// 1. Create a connector via API\n\tconfig1 := mock.PasswordConfig{\n\t\tUsername: \"user\",\n\t\tPassword: \"first-password\",\n\t}\n\tconfig1Bytes, _ := json.Marshal(config1)\n\n\t_, err := apiServer.CreateConnector(ctx, &api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     connID,\n\t\t\tType:   \"mockPassword\",\n\t\t\tName:   \"Mock\",\n\t\t\tConfig: config1Bytes,\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create connector: %v\", err)\n\t}\n\n\t// 2. Load it into server cache\n\tc1, err := serv.getConnector(ctx, connID)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get connector: %v\", err)\n\t}\n\n\tpc1 := c1.Connector.(connector.PasswordConnector)\n\t_, valid, err := pc1.Login(ctx, connector.Scopes{}, \"user\", \"first-password\")\n\tif err != nil || !valid {\n\t\tt.Fatalf(\"failed to login with first password: %v\", err)\n\t}\n\n\t// 3. Delete it via API\n\t_, err = apiServer.DeleteConnector(ctx, &api.DeleteConnectorReq{Id: connID})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to delete connector: %v\", err)\n\t}\n\n\t// 4. Create it again with different password\n\tconfig2 := mock.PasswordConfig{\n\t\tUsername: \"user\",\n\t\tPassword: \"second-password\",\n\t}\n\tconfig2Bytes, _ := json.Marshal(config2)\n\n\t_, err = apiServer.CreateConnector(ctx, &api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     connID,\n\t\t\tType:   \"mockPassword\",\n\t\t\tName:   \"Mock\",\n\t\t\tConfig: config2Bytes,\n\t\t},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create connector: %v\", err)\n\t}\n\n\t// 5. Load it again\n\tc2, err := serv.getConnector(ctx, connID)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get connector second time: %v\", err)\n\t}\n\n\tpc2 := c2.Connector.(connector.PasswordConnector)\n\n\t// If the fix works, it should now use the second password.\n\t_, valid2, err := pc2.Login(ctx, connector.Scopes{}, \"user\", \"second-password\")\n\tif err != nil || !valid2 {\n\t\tt.Errorf(\"failed to login with second password, cache might still be stale\")\n\t}\n\n\t_, valid1, _ := pc2.Login(ctx, connector.Scopes{}, \"user\", \"first-password\")\n\tif valid1 {\n\t\tt.Errorf(\"unexpectedly logged in with first password, cache is definitely stale\")\n\t}\n\n\t// 6. Update it via API with a third password\n\tconfig3 := mock.PasswordConfig{\n\t\tUsername: \"user\",\n\t\tPassword: \"third-password\",\n\t}\n\tconfig3Bytes, _ := json.Marshal(config3)\n\n\t_, err = apiServer.UpdateConnector(ctx, &api.UpdateConnectorReq{\n\t\tId:        connID,\n\t\tNewConfig: config3Bytes,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to update connector: %v\", err)\n\t}\n\n\t// 7. Load it again\n\tc3, err := serv.getConnector(ctx, connID)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get connector third time: %v\", err)\n\t}\n\n\tpc3 := c3.Connector.(connector.PasswordConnector)\n\n\t_, valid3, err := pc3.Login(ctx, connector.Scopes{}, \"user\", \"third-password\")\n\tif err != nil || !valid3 {\n\t\tt.Errorf(\"failed to login with third password, UpdateConnector might be missing cache invalidation\")\n\t}\n}\n"
  },
  {
    "path": "server/api_test.go",
    "content": "package server\n\nimport (\n\t\"log/slog\"\n\t\"net\"\n\t\"slices\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/credentials/insecure\"\n\n\t\"github.com/dexidp/dex/api/v2\"\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/memory\"\n)\n\n// apiClient is a test gRPC client. When constructed, it runs a server in\n// the background to exercise the serialization and network configuration\n// instead of just this package's server implementation.\ntype apiClient struct {\n\t// Embedded gRPC client to talk to the server.\n\tapi.DexClient\n\t// Close releases resources associated with this client, including shutting\n\t// down the background server.\n\tClose func()\n}\n\nfunc newLogger(t *testing.T) *slog.Logger {\n\treturn slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n}\n\n// newAPI constructs a gRCP client connected to a backing server.\nfunc newAPI(t *testing.T, s storage.Storage, logger *slog.Logger) *apiClient {\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tserv := grpc.NewServer()\n\tapi.RegisterDexServer(serv, NewAPI(s, logger, \"test\", nil))\n\tgo serv.Serve(l)\n\n\t// NewClient will retry automatically if the serv.Serve() goroutine\n\t// hasn't started yet.\n\tconn, err := grpc.NewClient(l.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\treturn &apiClient{\n\t\tDexClient: api.NewDexClient(conn),\n\t\tClose: func() {\n\t\t\tconn.Close()\n\t\t\tserv.Stop()\n\t\t\tl.Close()\n\t\t},\n\t}\n}\n\n// Attempts to create, update and delete a test Password\nfunc TestPassword(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\temail := \"test@example.com\"\n\tp := api.Password{\n\t\tEmail: email,\n\t\t// bcrypt hash of the value \"test1\" with cost 10\n\t\tHash:     []byte(\"$2a$10$XVMN/Fid.Ks4CXgzo8fpR.iU1khOMsP5g9xQeXuBm1wXjRX8pjUtO\"),\n\t\tUsername: \"test\",\n\t\tUserId:   \"test123\",\n\t}\n\n\tcreateReq := api.CreatePasswordReq{\n\t\tPassword: &p,\n\t}\n\n\tif resp, err := client.CreatePassword(ctx, &createReq); err != nil || resp.AlreadyExists {\n\t\tif resp.AlreadyExists {\n\t\t\tt.Fatalf(\"Unable to create password since %s already exists\", createReq.Password.Email)\n\t\t}\n\t\tt.Fatalf(\"Unable to create password: %v\", err)\n\t}\n\n\t// Attempt to create a password that already exists.\n\tif resp, _ := client.CreatePassword(ctx, &createReq); !resp.AlreadyExists {\n\t\tt.Fatalf(\"Created password %s twice\", createReq.Password.Email)\n\t}\n\n\t// Attempt to verify valid password and email\n\tgoodVerifyReq := &api.VerifyPasswordReq{\n\t\tEmail:    email,\n\t\tPassword: \"test1\",\n\t}\n\tgoodVerifyResp, err := client.VerifyPassword(ctx, goodVerifyReq)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to run verify password we expected to be valid for correct email: %v\", err)\n\t}\n\tif !goodVerifyResp.Verified {\n\t\tt.Fatalf(\"verify password failed for password expected to be valid for correct email. expected %t, found %t\", true, goodVerifyResp.Verified)\n\t}\n\tif goodVerifyResp.NotFound {\n\t\tt.Fatalf(\"verify password failed to return not found response. expected %t, found %t\", false, goodVerifyResp.NotFound)\n\t}\n\n\t// Check not found response for valid password with wrong email\n\tbadEmailVerifyReq := &api.VerifyPasswordReq{\n\t\tEmail:    \"somewrongaddress@email.com\",\n\t\tPassword: \"test1\",\n\t}\n\tbadEmailVerifyResp, err := client.VerifyPassword(ctx, badEmailVerifyReq)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to run verify password for incorrect email: %v\", err)\n\t}\n\tif badEmailVerifyResp.Verified {\n\t\tt.Fatalf(\"verify password passed for password expected to be not found. expected %t, found %t\", false, badEmailVerifyResp.Verified)\n\t}\n\tif !badEmailVerifyResp.NotFound {\n\t\tt.Fatalf(\"expected not found response for verify password with bad email. expected %t, found %t\", true, badEmailVerifyResp.NotFound)\n\t}\n\n\t// Check that wrong password fails\n\tbadPassVerifyReq := &api.VerifyPasswordReq{\n\t\tEmail:    email,\n\t\tPassword: \"wrong_password\",\n\t}\n\tbadPassVerifyResp, err := client.VerifyPassword(ctx, badPassVerifyReq)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to run verify password for password we expected to be invalid: %v\", err)\n\t}\n\tif badPassVerifyResp.Verified {\n\t\tt.Fatalf(\"verify password passed for password we expected to fail. expected %t, found %t\", false, badPassVerifyResp.Verified)\n\t}\n\tif badPassVerifyResp.NotFound {\n\t\tt.Fatalf(\"did not expect expected not found response for verify password with bad email. expected %t, found %t\", false, badPassVerifyResp.NotFound)\n\t}\n\n\tupdateReq := api.UpdatePasswordReq{\n\t\tEmail:       email,\n\t\tNewUsername: \"test1\",\n\t}\n\n\tif _, err := client.UpdatePassword(ctx, &updateReq); err != nil {\n\t\tt.Fatalf(\"Unable to update password: %v\", err)\n\t}\n\n\tpass, err := s.GetPassword(ctx, updateReq.Email)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to retrieve password: %v\", err)\n\t}\n\n\tif pass.Username != updateReq.NewUsername {\n\t\tt.Fatalf(\"UpdatePassword failed. Expected username %s retrieved %s\", updateReq.NewUsername, pass.Username)\n\t}\n\n\tdeleteReq := api.DeletePasswordReq{\n\t\tEmail: \"test@example.com\",\n\t}\n\n\tif _, err := client.DeletePassword(ctx, &deleteReq); err != nil {\n\t\tt.Fatalf(\"Unable to delete password: %v\", err)\n\t}\n}\n\n// Ensures checkCost returns expected values\nfunc TestCheckCost(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\ttests := []struct {\n\t\tname      string\n\t\tinputHash []byte\n\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"valid cost\",\n\t\t\t// bcrypt hash of the value \"test1\" with cost 12 (default)\n\t\t\tinputHash: []byte(\"$2a$12$M2Ot95Qty1MuQdubh1acWOiYadJDzeVg3ve4n5b.dgcgPdjCseKx2\"),\n\t\t},\n\t\t{\n\t\t\tname:      \"invalid hash\",\n\t\t\tinputHash: []byte(\"\"),\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname: \"cost below default\",\n\t\t\t// bcrypt hash of the value \"test1\" with cost 4\n\t\t\tinputHash: []byte(\"$2a$04$8bSTbuVCLpKzaqB3BmgI7edDigG5tIQKkjYUu/mEO9gQgIkw9m7eG\"),\n\t\t\twantErr:   true,\n\t\t},\n\t\t{\n\t\t\tname: \"cost above recommendation\",\n\t\t\t// bcrypt hash of the value \"test1\" with cost 17\n\t\t\tinputHash: []byte(\"$2a$17$tWuZkTxtSmRyWZAGWVHQE.7npdl.TgP8adjzLJD.SyjpFznKBftPe\"),\n\t\t\twantErr:   true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tif err := checkCost(tc.inputHash); err != nil {\n\t\t\tif !tc.wantErr {\n\t\t\t\tt.Errorf(\"%s: %s\", tc.name, err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif tc.wantErr {\n\t\t\tt.Errorf(\"%s: expected err\", tc.name)\n\t\t\tcontinue\n\t\t}\n\t}\n}\n\n// Attempts to list and revoke an existing refresh token.\nfunc TestRefreshToken(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\t// Creating a storage with an existing refresh token and offline session for the user.\n\tid := storage.NewID()\n\tr := storage.RefreshToken{\n\t\tID:          id,\n\t\tToken:       \"bar\",\n\t\tNonce:       \"foo\",\n\t\tClientID:    \"client_id\",\n\t\tConnectorID: \"client_secret\",\n\t\tScopes:      []string{\"openid\", \"email\", \"profile\"},\n\t\tCreatedAt:   time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:    time.Now().UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t}\n\n\tif err := s.CreateRefresh(ctx, r); err != nil {\n\t\tt.Fatalf(\"create refresh token: %v\", err)\n\t}\n\n\ttokenRef := storage.RefreshTokenRef{\n\t\tID:        r.ID,\n\t\tClientID:  r.ClientID,\n\t\tCreatedAt: r.CreatedAt,\n\t\tLastUsed:  r.LastUsed,\n\t}\n\n\tsession := storage.OfflineSessions{\n\t\tUserID:  r.Claims.UserID,\n\t\tConnID:  r.ConnectorID,\n\t\tRefresh: make(map[string]*storage.RefreshTokenRef),\n\t}\n\tsession.Refresh[tokenRef.ClientID] = &tokenRef\n\n\tif err := s.CreateOfflineSessions(ctx, session); err != nil {\n\t\tt.Fatalf(\"create offline session: %v\", err)\n\t}\n\n\tsubjectString, err := internal.Marshal(&internal.IDTokenSubject{\n\t\tUserId: r.Claims.UserID,\n\t\tConnId: r.ConnectorID,\n\t})\n\tif err != nil {\n\t\tt.Errorf(\"failed to marshal offline session ID: %v\", err)\n\t}\n\n\t// Testing the api.\n\tlistReq := api.ListRefreshReq{\n\t\tUserId: subjectString,\n\t}\n\n\tlistResp, err := client.ListRefresh(ctx, &listReq)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to list refresh tokens for user: %v\", err)\n\t}\n\n\tfor _, tok := range listResp.RefreshTokens {\n\t\tif tok.CreatedAt != r.CreatedAt.Unix() {\n\t\t\tt.Errorf(\"Expected CreatedAt timestamp %v, got %v\", r.CreatedAt.Unix(), tok.CreatedAt)\n\t\t}\n\n\t\tif tok.LastUsed != r.LastUsed.Unix() {\n\t\t\tt.Errorf(\"Expected LastUsed timestamp %v, got %v\", r.LastUsed.Unix(), tok.LastUsed)\n\t\t}\n\t}\n\n\trevokeReq := api.RevokeRefreshReq{\n\t\tUserId:   subjectString,\n\t\tClientId: r.ClientID,\n\t}\n\n\tresp, err := client.RevokeRefresh(ctx, &revokeReq)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to revoke refresh tokens for user: %v\", err)\n\t}\n\tif resp.NotFound {\n\t\tt.Errorf(\"refresh token session wasn't found\")\n\t}\n\n\t// Try to delete again.\n\t//\n\t// See https://github.com/dexidp/dex/issues/1055\n\tresp, err = client.RevokeRefresh(ctx, &revokeReq)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to revoke refresh tokens for user: %v\", err)\n\t}\n\tif !resp.NotFound {\n\t\tt.Errorf(\"refresh token session was found\")\n\t}\n\n\tif resp, _ := client.ListRefresh(ctx, &listReq); len(resp.RefreshTokens) != 0 {\n\t\tt.Fatalf(\"Refresh token returned in spite of revoking it.\")\n\t}\n}\n\nfunc TestUpdateClient(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\tcreateClient := func(t *testing.T, clientId string) {\n\t\tresp, err := client.CreateClient(ctx, &api.CreateClientReq{\n\t\t\tClient: &api.Client{\n\t\t\t\tId:           clientId,\n\t\t\t\tSecret:       \"\",\n\t\t\t\tRedirectUris: []string{},\n\t\t\t\tTrustedPeers: nil,\n\t\t\t\tPublic:       true,\n\t\t\t\tName:         \"\",\n\t\t\t\tLogoUrl:      \"\",\n\t\t\t},\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unable to create the client: %v\", err)\n\t\t}\n\n\t\tif resp == nil {\n\t\t\tt.Fatalf(\"create client returned no response\")\n\t\t}\n\t\tif resp.AlreadyExists {\n\t\t\tt.Error(\"existing client was found\")\n\t\t}\n\n\t\tif resp.Client == nil {\n\t\t\tt.Fatalf(\"no client created\")\n\t\t}\n\t}\n\n\tdeleteClient := func(t *testing.T, clientId string) {\n\t\tresp, err := client.DeleteClient(ctx, &api.DeleteClientReq{\n\t\t\tId: clientId,\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"unable to delete the client: %v\", err)\n\t\t}\n\t\tif resp == nil {\n\t\t\tt.Fatalf(\"delete client delete client returned no response\")\n\t\t}\n\t}\n\n\ttests := map[string]struct {\n\t\tsetup   func(t *testing.T, clientId string)\n\t\tcleanup func(t *testing.T, clientId string)\n\t\treq     *api.UpdateClientReq\n\t\twantErr bool\n\t\twant    *api.UpdateClientResp\n\t}{\n\t\t\"update client\": {\n\t\t\tsetup:   createClient,\n\t\t\tcleanup: deleteClient,\n\t\t\treq: &api.UpdateClientReq{\n\t\t\t\tId:           \"test\",\n\t\t\t\tRedirectUris: []string{\"https://redirect\"},\n\t\t\t\tTrustedPeers: []string{\"test\"},\n\t\t\t\tName:         \"test\",\n\t\t\t\tLogoUrl:      \"https://logout\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t\twant: &api.UpdateClientResp{\n\t\t\t\tNotFound: false,\n\t\t\t},\n\t\t},\n\t\t\"update client without ID\": {\n\t\t\tsetup:   createClient,\n\t\t\tcleanup: deleteClient,\n\t\t\treq: &api.UpdateClientReq{\n\t\t\t\tId:           \"\",\n\t\t\t\tRedirectUris: nil,\n\t\t\t\tTrustedPeers: nil,\n\t\t\t\tName:         \"test\",\n\t\t\t\tLogoUrl:      \"test\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t\twant: &api.UpdateClientResp{\n\t\t\t\tNotFound: false,\n\t\t\t},\n\t\t},\n\t\t\"update client which not exists \": {\n\t\t\treq: &api.UpdateClientReq{\n\t\t\t\tId:           \"test\",\n\t\t\t\tRedirectUris: nil,\n\t\t\t\tTrustedPeers: nil,\n\t\t\t\tName:         \"test\",\n\t\t\t\tLogoUrl:      \"test\",\n\t\t\t},\n\t\t\twantErr: true,\n\t\t\twant: &api.UpdateClientResp{\n\t\t\t\tNotFound: false,\n\t\t\t},\n\t\t},\n\t}\n\n\tfor name, tc := range tests {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\tif tc.setup != nil {\n\t\t\t\ttc.setup(t, tc.req.Id)\n\t\t\t}\n\t\t\tresp, err := client.UpdateClient(ctx, tc.req)\n\t\t\tif err != nil && !tc.wantErr {\n\t\t\t\tt.Fatalf(\"failed to update the client: %v\", err)\n\t\t\t}\n\n\t\t\tif !tc.wantErr {\n\t\t\t\tif resp == nil {\n\t\t\t\t\tt.Fatalf(\"update client response not found\")\n\t\t\t\t}\n\n\t\t\t\tif tc.want.NotFound != resp.NotFound {\n\t\t\t\t\tt.Errorf(\"expected in response NotFound: %t\", tc.want.NotFound)\n\t\t\t\t}\n\n\t\t\t\tclient, err := s.GetClient(ctx, tc.req.Id)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"no client found in the storage: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif tc.req.Id != client.ID {\n\t\t\t\t\tt.Errorf(\"expected stored client with ID: %s, found %s\", tc.req.Id, client.ID)\n\t\t\t\t}\n\t\t\t\tif tc.req.Name != client.Name {\n\t\t\t\t\tt.Errorf(\"expected stored client with Name: %s, found %s\", tc.req.Name, client.Name)\n\t\t\t\t}\n\t\t\t\tif tc.req.LogoUrl != client.LogoURL {\n\t\t\t\t\tt.Errorf(\"expected stored client with LogoURL: %s, found %s\", tc.req.LogoUrl, client.LogoURL)\n\t\t\t\t}\n\t\t\t\tfor _, redirectURI := range tc.req.RedirectUris {\n\t\t\t\t\tfound := slices.Contains(client.RedirectURIs, redirectURI)\n\t\t\t\t\tif !found {\n\t\t\t\t\t\tt.Errorf(\"expected redirect URI: %s\", redirectURI)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfor _, peer := range tc.req.TrustedPeers {\n\t\t\t\t\tfound := slices.Contains(client.TrustedPeers, peer)\n\t\t\t\t\tif !found {\n\t\t\t\t\t\tt.Errorf(\"expected trusted peer: %s\", peer)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif tc.cleanup != nil {\n\t\t\t\ttc.cleanup(t, tc.req.Id)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestCreateConnector(t *testing.T) {\n\tt.Setenv(\"DEX_API_CONNECTORS_CRUD\", \"true\")\n\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\tconnectorID := \"connector123\"\n\tconnectorName := \"TestConnector\"\n\tconnectorType := \"TestType\"\n\tconnectorConfig := []byte(`{\"key\": \"value\"}`)\n\n\tcreateReq := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     connectorID,\n\t\t\tName:   connectorName,\n\t\t\tType:   connectorType,\n\t\t\tConfig: connectorConfig,\n\t\t},\n\t}\n\n\t// Test valid connector creation\n\tif resp, err := client.CreateConnector(ctx, &createReq); err != nil || resp.AlreadyExists {\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"Unable to create connector: %v\", err)\n\t\t} else if resp.AlreadyExists {\n\t\t\tt.Fatalf(\"Unable to create connector since %s already exists\", connectorID)\n\t\t}\n\t\tt.Fatalf(\"Unable to create connector: %v\", err)\n\t}\n\n\t// Test creating the same connector again (expecting failure)\n\tif resp, _ := client.CreateConnector(ctx, &createReq); !resp.AlreadyExists {\n\t\tt.Fatalf(\"Created connector %s twice\", connectorID)\n\t}\n\n\tcreateReq.Connector.Config = []byte(\"invalid_json\")\n\n\t// Test invalid JSON config\n\tif _, err := client.CreateConnector(ctx, &createReq); err == nil {\n\t\tt.Fatal(\"Expected an error for invalid JSON config, but none occurred\")\n\t} else if !strings.Contains(err.Error(), \"invalid config supplied\") {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n}\n\nfunc TestUpdateConnector(t *testing.T) {\n\tt.Setenv(\"DEX_API_CONNECTORS_CRUD\", \"true\")\n\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\tconnectorID := \"connector123\"\n\tnewConnectorName := \"UpdatedConnector\"\n\tnewConnectorType := \"UpdatedType\"\n\tnewConnectorConfig := []byte(`{\"updated_key\": \"updated_value\"}`)\n\n\t// Create a connector for testing\n\tcreateReq := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     connectorID,\n\t\t\tName:   \"TestConnector\",\n\t\t\tType:   \"TestType\",\n\t\t\tConfig: []byte(`{\"key\": \"value\"}`),\n\t\t},\n\t}\n\tclient.CreateConnector(ctx, &createReq)\n\n\tupdateReq := api.UpdateConnectorReq{\n\t\tId:        connectorID,\n\t\tNewName:   newConnectorName,\n\t\tNewType:   newConnectorType,\n\t\tNewConfig: newConnectorConfig,\n\t}\n\n\t// Test valid connector update\n\tif _, err := client.UpdateConnector(ctx, &updateReq); err != nil {\n\t\tt.Fatalf(\"Unable to update connector: %v\", err)\n\t}\n\n\tresp, err := client.ListConnectors(ctx, &api.ListConnectorReq{})\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tfor _, connector := range resp.Connectors {\n\t\tif connector.Id == connectorID {\n\t\t\tif connector.Name != newConnectorName {\n\t\t\t\tt.Fatal(\"connector name should have been updated\")\n\t\t\t}\n\t\t\tif string(connector.Config) != string(newConnectorConfig) {\n\t\t\t\tt.Fatal(\"connector config should have been updated\")\n\t\t\t}\n\t\t\tif connector.Type != newConnectorType {\n\t\t\t\tt.Fatal(\"connector type should have been updated\")\n\t\t\t}\n\t\t}\n\t}\n\n\tupdateReq.NewConfig = []byte(\"invalid_json\")\n\n\t// Test invalid JSON config in update request\n\tif _, err := client.UpdateConnector(ctx, &updateReq); err == nil {\n\t\tt.Fatal(\"Expected an error for invalid JSON config in update, but none occurred\")\n\t} else if !strings.Contains(err.Error(), \"invalid config supplied\") {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n}\n\nfunc TestUpdateConnectorGrantTypes(t *testing.T) {\n\tt.Setenv(\"DEX_API_CONNECTORS_CRUD\", \"true\")\n\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\tconnectorID := \"connector-gt\"\n\n\t// Create a connector without grant types\n\tcreateReq := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     connectorID,\n\t\t\tName:   \"TestConnector\",\n\t\t\tType:   \"TestType\",\n\t\t\tConfig: []byte(`{\"key\": \"value\"}`),\n\t\t},\n\t}\n\t_, err := client.CreateConnector(ctx, &createReq)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create connector: %v\", err)\n\t}\n\n\t// Set grant types\n\t_, err = client.UpdateConnector(ctx, &api.UpdateConnectorReq{\n\t\tId:            connectorID,\n\t\tNewGrantTypes: &api.GrantTypes{GrantTypes: []string{\"authorization_code\", \"refresh_token\"}},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to update connector grant types: %v\", err)\n\t}\n\n\tresp, err := client.ListConnectors(ctx, &api.ListConnectorReq{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to list connectors: %v\", err)\n\t}\n\tfor _, c := range resp.Connectors {\n\t\tif c.Id == connectorID {\n\t\t\tif !slices.Equal(c.GrantTypes, []string{\"authorization_code\", \"refresh_token\"}) {\n\t\t\t\tt.Fatalf(\"expected grant types [authorization_code refresh_token], got %v\", c.GrantTypes)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Clear grant types by passing empty GrantTypes message\n\t_, err = client.UpdateConnector(ctx, &api.UpdateConnectorReq{\n\t\tId:            connectorID,\n\t\tNewGrantTypes: &api.GrantTypes{},\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to clear connector grant types: %v\", err)\n\t}\n\n\tresp, err = client.ListConnectors(ctx, &api.ListConnectorReq{})\n\tif err != nil {\n\t\tt.Fatalf(\"failed to list connectors: %v\", err)\n\t}\n\tfor _, c := range resp.Connectors {\n\t\tif c.Id == connectorID {\n\t\t\tif len(c.GrantTypes) != 0 {\n\t\t\t\tt.Fatalf(\"expected empty grant types after clear, got %v\", c.GrantTypes)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Reject invalid grant type on update\n\t_, err = client.UpdateConnector(ctx, &api.UpdateConnectorReq{\n\t\tId:            connectorID,\n\t\tNewGrantTypes: &api.GrantTypes{GrantTypes: []string{\"bogus\"}},\n\t})\n\tif err == nil {\n\t\tt.Fatal(\"expected error for invalid grant type, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), `unknown grant type \"bogus\"`) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n\n\t// Reject invalid grant type on create\n\t_, err = client.CreateConnector(ctx, &api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:         \"bad-gt\",\n\t\t\tName:       \"Bad\",\n\t\t\tType:       \"TestType\",\n\t\t\tConfig:     []byte(`{}`),\n\t\t\tGrantTypes: []string{\"invalid_type\"},\n\t\t},\n\t})\n\tif err == nil {\n\t\tt.Fatal(\"expected error for invalid grant type on create, got nil\")\n\t}\n\tif !strings.Contains(err.Error(), `unknown grant type \"invalid_type\"`) {\n\t\tt.Fatalf(\"unexpected error: %v\", err)\n\t}\n}\n\nfunc TestDeleteConnector(t *testing.T) {\n\tt.Setenv(\"DEX_API_CONNECTORS_CRUD\", \"true\")\n\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\tconnectorID := \"connector123\"\n\n\t// Create a connector for testing\n\tcreateReq := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     connectorID,\n\t\t\tName:   \"TestConnector\",\n\t\t\tType:   \"TestType\",\n\t\t\tConfig: []byte(`{\"key\": \"value\"}`),\n\t\t},\n\t}\n\tclient.CreateConnector(ctx, &createReq)\n\n\tdeleteReq := api.DeleteConnectorReq{\n\t\tId: connectorID,\n\t}\n\n\t// Test valid connector deletion\n\tif _, err := client.DeleteConnector(ctx, &deleteReq); err != nil {\n\t\tt.Fatalf(\"Unable to delete connector: %v\", err)\n\t}\n\n\t// Test non existent connector deletion\n\tresp, err := client.DeleteConnector(ctx, &deleteReq)\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to delete connector: %v\", err)\n\t}\n\n\tif !resp.NotFound {\n\t\tt.Fatal(\"Should return not found\")\n\t}\n}\n\nfunc TestListConnectors(t *testing.T) {\n\tt.Setenv(\"DEX_API_CONNECTORS_CRUD\", \"true\")\n\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\t// Create connectors for testing\n\tcreateReq1 := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     \"connector1\",\n\t\t\tName:   \"Connector1\",\n\t\t\tType:   \"Type1\",\n\t\t\tConfig: []byte(`{\"key\": \"value1\"}`),\n\t\t},\n\t}\n\tclient.CreateConnector(ctx, &createReq1)\n\n\tcreateReq2 := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     \"connector2\",\n\t\t\tName:   \"Connector2\",\n\t\t\tType:   \"Type2\",\n\t\t\tConfig: []byte(`{\"key\": \"value2\"}`),\n\t\t},\n\t}\n\tclient.CreateConnector(ctx, &createReq2)\n\n\tlistReq := api.ListConnectorReq{}\n\n\t// Test listing connectors\n\tif resp, err := client.ListConnectors(ctx, &listReq); err != nil {\n\t\tt.Fatalf(\"Unable to list connectors: %v\", err)\n\t} else if len(resp.Connectors) != 2 { // Check the number of connectors in the response\n\t\tt.Fatalf(\"Expected 2 connectors, found %d\", len(resp.Connectors))\n\t}\n}\n\nfunc TestMissingConnectorsCRUDFeatureFlag(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\t// Create connectors for testing\n\tcreateReq1 := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     \"connector1\",\n\t\t\tName:   \"Connector1\",\n\t\t\tType:   \"Type1\",\n\t\t\tConfig: []byte(`{\"key\": \"value1\"}`),\n\t\t},\n\t}\n\tclient.CreateConnector(ctx, &createReq1)\n\n\tcreateReq2 := api.CreateConnectorReq{\n\t\tConnector: &api.Connector{\n\t\t\tId:     \"connector2\",\n\t\t\tName:   \"Connector2\",\n\t\t\tType:   \"Type2\",\n\t\t\tConfig: []byte(`{\"key\": \"value2\"}`),\n\t\t},\n\t}\n\tclient.CreateConnector(ctx, &createReq2)\n\n\tlistReq := api.ListConnectorReq{}\n\n\tif _, err := client.ListConnectors(ctx, &listReq); err == nil {\n\t\tt.Fatal(\"ListConnectors should have returned an error\")\n\t}\n}\n\nfunc TestListClients(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\tclient := newAPI(t, s, logger)\n\tdefer client.Close()\n\n\tctx := t.Context()\n\n\t// List Clients\n\tlistResp, err := client.ListClients(ctx, &api.ListClientReq{})\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to list clients: %v\", err)\n\t}\n\tif len(listResp.Clients) != 0 {\n\t\tt.Fatalf(\"Expected 0 clients, got %d\", len(listResp.Clients))\n\t}\n\n\tclient1 := &api.Client{\n\t\tId:           \"client1\",\n\t\tSecret:       \"secret1\",\n\t\tRedirectUris: []string{\"http://localhost:8080/callback\"},\n\t\tTrustedPeers: []string{\"peer1\"},\n\t\tPublic:       false,\n\t\tName:         \"Test Client 1\",\n\t\tLogoUrl:      \"http://example.com/logo1.png\",\n\t}\n\n\tclient2 := &api.Client{\n\t\tId:           \"client2\",\n\t\tSecret:       \"secret2\",\n\t\tRedirectUris: []string{\"http://localhost:8081/callback\"},\n\t\tTrustedPeers: []string{\"peer2\"},\n\t\tPublic:       true,\n\t\tName:         \"Test Client 2\",\n\t\tLogoUrl:      \"http://example.com/logo2.png\",\n\t}\n\n\t_, err = client.CreateClient(ctx, &api.CreateClientReq{Client: client1})\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create client1: %v\", err)\n\t}\n\n\t_, err = client.CreateClient(ctx, &api.CreateClientReq{Client: client2})\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to create client2: %v\", err)\n\t}\n\n\tlistResp, err = client.ListClients(ctx, &api.ListClientReq{})\n\tif err != nil {\n\t\tt.Fatalf(\"Unable to list clients: %v\", err)\n\t}\n\n\tif len(listResp.Clients) != 2 {\n\t\tt.Fatalf(\"Expected 2 clients, got %d\", len(listResp.Clients))\n\t}\n\n\tclientMap := make(map[string]*api.ClientInfo)\n\tfor _, c := range listResp.Clients {\n\t\tclientMap[c.Id] = c\n\t}\n\n\tif c1, exists := clientMap[\"client1\"]; !exists {\n\t\tt.Fatal(\"client1 not found in list\")\n\t} else {\n\t\tif c1.Name != \"Test Client 1\" {\n\t\t\tt.Errorf(\"Expected client1 name 'Test Client 1', got '%s'\", c1.Name)\n\t\t}\n\t\tif len(c1.RedirectUris) != 1 || c1.RedirectUris[0] != \"http://localhost:8080/callback\" {\n\t\t\tt.Errorf(\"Expected client1 redirect URIs ['http://localhost:8080/callback'], got %v\", c1.RedirectUris)\n\t\t}\n\t\tif c1.Public != false {\n\t\t\tt.Errorf(\"Expected client1 public false, got %v\", c1.Public)\n\t\t}\n\t\tif c1.LogoUrl != \"http://example.com/logo1.png\" {\n\t\t\tt.Errorf(\"Expected client1 logo URL 'http://example.com/logo1.png', got '%s'\", c1.LogoUrl)\n\t\t}\n\t}\n\n\tif c2, exists := clientMap[\"client2\"]; !exists {\n\t\tt.Fatal(\"client2 not found in list\")\n\t} else {\n\t\tif c2.Name != \"Test Client 2\" {\n\t\t\tt.Errorf(\"Expected client2 name 'Test Client 2', got '%s'\", c2.Name)\n\t\t}\n\t\tif len(c2.RedirectUris) != 1 || c2.RedirectUris[0] != \"http://localhost:8081/callback\" {\n\t\t\tt.Errorf(\"Expected client2 redirect URIs ['http://localhost:8081/callback'], got %v\", c2.RedirectUris)\n\t\t}\n\t\tif c2.Public != true {\n\t\t\tt.Errorf(\"Expected client2 public true, got %v\", c2.Public)\n\t\t}\n\t\tif c2.LogoUrl != \"http://example.com/logo2.png\" {\n\t\t\tt.Errorf(\"Expected client2 logo URL 'http://example.com/logo2.png', got '%s'\", c2.LogoUrl)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/deviceflowhandlers.go",
    "content": "package server\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\ntype deviceCodeResponse struct {\n\t// The unique device code for device authentication\n\tDeviceCode string `json:\"device_code\"`\n\t// The code the user will exchange via a browser and log in\n\tUserCode string `json:\"user_code\"`\n\t// The url to verify the user code.\n\tVerificationURI string `json:\"verification_uri\"`\n\t// The verification uri with the user code appended for pre-filling form\n\tVerificationURIComplete string `json:\"verification_uri_complete\"`\n\t// The lifetime of the device code\n\tExpireTime int `json:\"expires_in\"`\n\t// How often the device is allowed to poll to verify that the user login occurred\n\tPollInterval int `json:\"interval\"`\n}\n\nfunc (s *Server) getDeviceVerificationURI() string {\n\treturn path.Join(s.issuerURL.Path, \"/device/auth/verify_code\")\n}\n\nfunc (s *Server) handleDeviceExchange(w http.ResponseWriter, r *http.Request) {\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\t// Grab the parameter(s) from the query.\n\t\t// If \"user_code\" is set, pre-populate the user code text field.\n\t\t// If \"invalid\" is set, set the invalidAttempt boolean, which will display a message to the user that they\n\t\t// attempted to redeem an invalid or expired user code.\n\t\tuserCode := r.URL.Query().Get(\"user_code\")\n\t\tinvalidAttempt, err := strconv.ParseBool(r.URL.Query().Get(\"invalid\"))\n\t\tif err != nil {\n\t\t\tinvalidAttempt = false\n\t\t}\n\t\tif err := s.templates.device(r, w, s.getDeviceVerificationURI(), userCode, invalidAttempt); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusNotFound, \"Page not found\")\n\t\t}\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist.\")\n\t}\n}\n\nfunc (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tpollIntervalSeconds := 5\n\n\tswitch r.Method {\n\tcase http.MethodPost:\n\t\terr := r.ParseForm()\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"could not parse Device Request body\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusNotFound)\n\t\t\treturn\n\t\t}\n\n\t\t// Get the client id and scopes from the post\n\t\tclientID := r.Form.Get(\"client_id\")\n\t\tclientSecret := r.Form.Get(\"client_secret\")\n\t\tscopes := strings.Fields(r.Form.Get(\"scope\"))\n\t\tcodeChallenge := r.Form.Get(\"code_challenge\")\n\t\tcodeChallengeMethod := r.Form.Get(\"code_challenge_method\")\n\n\t\tif codeChallengeMethod == \"\" {\n\t\t\tcodeChallengeMethod = codeChallengeMethodPlain\n\t\t}\n\t\tif codeChallengeMethod != codeChallengeMethodS256 && codeChallengeMethod != codeChallengeMethodPlain {\n\t\t\tdescription := fmt.Sprintf(\"Unsupported PKCE challenge method (%q).\", codeChallengeMethod)\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, description, http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tif len(scopes) == 0 {\n\t\t\t// per RFC8628 section 3.1, https://datatracker.ietf.org/doc/html/rfc8628#section-3.1\n\t\t\t// scope is optional but dex requires that it is always at least 'openid' so default it\n\t\t\tscopes = []string{\"openid\"}\n\t\t}\n\n\t\ts.logger.InfoContext(r.Context(), \"received device request\", \"client_id\", clientID, \"scoped\", scopes)\n\n\t\t// Make device code\n\t\tdeviceCode := storage.NewDeviceCode()\n\n\t\t// make user code\n\t\tuserCode := storage.NewUserCode()\n\n\t\t// Generate the expire time\n\t\texpireTime := time.Now().Add(s.deviceRequestsValidFor)\n\n\t\t// Store the Device Request\n\t\tdeviceReq := storage.DeviceRequest{\n\t\t\tUserCode:     userCode,\n\t\t\tDeviceCode:   deviceCode,\n\t\t\tClientID:     clientID,\n\t\t\tClientSecret: clientSecret,\n\t\t\tScopes:       scopes,\n\t\t\tExpiry:       expireTime,\n\t\t}\n\n\t\tif err := s.storage.CreateDeviceRequest(ctx, deviceReq); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to store device request\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\t// Store the device token\n\t\tdeviceToken := storage.DeviceToken{\n\t\t\tDeviceCode:          deviceCode,\n\t\t\tStatus:              deviceTokenPending,\n\t\t\tExpiry:              expireTime,\n\t\t\tLastRequestTime:     s.now(),\n\t\t\tPollIntervalSeconds: 0,\n\t\t\tPKCE: storage.PKCE{\n\t\t\t\tCodeChallenge:       codeChallenge,\n\t\t\t\tCodeChallengeMethod: codeChallengeMethod,\n\t\t\t},\n\t\t}\n\n\t\tif err := s.storage.CreateDeviceToken(ctx, deviceToken); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to store device token\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tu, err := url.Parse(s.issuerURL.String())\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"could not parse issuer URL\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tu.Path = path.Join(u.Path, \"device\")\n\t\tvURI := u.String()\n\n\t\tq := u.Query()\n\t\tq.Set(\"user_code\", userCode)\n\t\tu.RawQuery = q.Encode()\n\t\tvURIComplete := u.String()\n\n\t\tcode := deviceCodeResponse{\n\t\t\tDeviceCode:              deviceCode,\n\t\t\tUserCode:                userCode,\n\t\t\tVerificationURI:         vURI,\n\t\t\tVerificationURIComplete: vURIComplete,\n\t\t\tExpireTime:              int(s.deviceRequestsValidFor.Seconds()),\n\t\t\tPollInterval:            pollIntervalSeconds,\n\t\t}\n\n\t\t// Device Authorization Response can contain cache control header according to\n\t\t// https://tools.ietf.org/html/rfc8628#section-3.2\n\t\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\n\t\t// Response type should be application/json according to\n\t\t// https://datatracker.ietf.org/doc/html/rfc6749#section-5.1\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\n\t\tenc := json.NewEncoder(w)\n\t\tenc.SetEscapeHTML(false)\n\t\tenc.SetIndent(\"\", \"   \")\n\t\tenc.Encode(code)\n\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Invalid device code request type\")\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusBadRequest)\n\t}\n}\n\nfunc (s *Server) handleDeviceTokenDeprecated(w http.ResponseWriter, r *http.Request) {\n\ts.logger.Warn(`the /device/token endpoint was called. It will be removed, use /token instead.`, \"deprecated\", true)\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tswitch r.Method {\n\tcase http.MethodPost:\n\t\terr := r.ParseForm()\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"could not parse Device Token Request body\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\tgrantType := r.PostFormValue(\"grant_type\")\n\t\tif grantType != grantTypeDeviceCode {\n\t\t\ts.tokenErrHelper(w, errInvalidGrant, \"\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\ts.handleDeviceToken(w, r)\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist.\")\n\t}\n}\n\nfunc (s *Server) handleDeviceToken(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tdeviceCode := r.Form.Get(\"device_code\")\n\tif deviceCode == \"\" {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"No device code received\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tnow := s.now()\n\n\t// Grab the device token, check validity\n\tdeviceToken, err := s.storage.GetDeviceToken(ctx, deviceCode)\n\tif err != nil {\n\t\tif err != storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get device code\", \"err\", err)\n\t\t}\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Invalid Device code.\", http.StatusBadRequest)\n\t\treturn\n\t} else if now.After(deviceToken.Expiry) {\n\t\ts.tokenErrHelper(w, deviceTokenExpired, \"\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Rate Limiting check\n\tslowDown := false\n\tpollInterval := deviceToken.PollIntervalSeconds\n\tminRequestTime := deviceToken.LastRequestTime.Add(time.Second * time.Duration(pollInterval))\n\tif now.Before(minRequestTime) {\n\t\tslowDown = true\n\t\t// Continually increase the poll interval until the user waits the proper time\n\t\tpollInterval += 5\n\t} else {\n\t\tpollInterval = 5\n\t}\n\n\tswitch deviceToken.Status {\n\tcase deviceTokenPending:\n\t\tupdater := func(old storage.DeviceToken) (storage.DeviceToken, error) {\n\t\t\told.PollIntervalSeconds = pollInterval\n\t\t\told.LastRequestTime = now\n\t\t\treturn old, nil\n\t\t}\n\t\t// Update device token last request time in storage\n\t\tif err := s.storage.UpdateDeviceToken(ctx, deviceCode, updater); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to update device token\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"\")\n\t\t\treturn\n\t\t}\n\t\tif slowDown {\n\t\t\ts.tokenErrHelper(w, deviceTokenSlowDown, \"\", http.StatusBadRequest)\n\t\t} else {\n\t\t\ts.tokenErrHelper(w, deviceTokenPending, \"\", http.StatusBadRequest)\n\t\t}\n\tcase deviceTokenComplete:\n\t\tcodeChallengeFromStorage := deviceToken.PKCE.CodeChallenge\n\t\tprovidedCodeVerifier := r.Form.Get(\"code_verifier\")\n\n\t\tswitch {\n\t\tcase providedCodeVerifier != \"\" && codeChallengeFromStorage != \"\":\n\t\t\tcalculatedCodeChallenge, err := s.calculateCodeChallenge(providedCodeVerifier, deviceToken.PKCE.CodeChallengeMethod)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to calculate code challenge\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif codeChallengeFromStorage != calculatedCodeChallenge {\n\t\t\t\ts.tokenErrHelper(w, errInvalidGrant, \"Invalid code_verifier.\", http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase providedCodeVerifier != \"\":\n\t\t\t// Received no code_challenge on /auth, but a code_verifier on /token\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"No PKCE flow started. Cannot check code_verifier.\", http.StatusBadRequest)\n\t\t\treturn\n\t\tcase codeChallengeFromStorage != \"\":\n\t\t\t// Received PKCE request on /auth, but no code_verifier on /token\n\t\t\ts.tokenErrHelper(w, errInvalidGrant, \"Expecting parameter code_verifier in PKCE flow.\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tw.Write([]byte(deviceToken.Token))\n\t}\n}\n\nfunc (s *Server) handleDeviceCallback(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\tuserCode := r.FormValue(\"state\")\n\t\tcode := r.FormValue(\"code\")\n\n\t\tif userCode == \"\" || code == \"\" {\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"Request was missing parameters\")\n\t\t\treturn\n\t\t}\n\n\t\t// Authorization redirect callback from OAuth2 auth flow.\n\t\tif errMsg := r.FormValue(\"error\"); errMsg != \"\" {\n\t\t\t// Log the error details but don't expose them to the user\n\t\t\ts.logger.ErrorContext(r.Context(), \"OAuth2 authorization error\",\n\t\t\t\t\"error\", errMsg,\n\t\t\t\t\"error_description\", r.FormValue(\"error_description\"))\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"Authorization failed. Please try again.\")\n\t\t\treturn\n\t\t}\n\n\t\tauthCode, err := s.storage.GetAuthCode(ctx, code)\n\t\tif err != nil || s.now().After(authCode.Expiry) {\n\t\t\terrCode := http.StatusBadRequest\n\t\t\tif err != nil && err != storage.ErrNotFound {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get auth code\", \"err\", err)\n\t\t\t\terrCode = http.StatusInternalServerError\n\t\t\t}\n\t\t\ts.renderError(r, w, errCode, \"Invalid or expired auth code.\")\n\t\t\treturn\n\t\t}\n\n\t\t// Grab the device request from storage\n\t\tdeviceReq, err := s.storage.GetDeviceRequest(ctx, userCode)\n\t\tif err != nil || s.now().After(deviceReq.Expiry) {\n\t\t\terrCode := http.StatusBadRequest\n\t\t\tif err != nil && err != storage.ErrNotFound {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get device code\", \"err\", err)\n\t\t\t\terrCode = http.StatusInternalServerError\n\t\t\t}\n\t\t\ts.renderError(r, w, errCode, \"Invalid or expired user code.\")\n\t\t\treturn\n\t\t}\n\n\t\tclient, err := s.storage.GetClient(ctx, deviceReq.ClientID)\n\t\tif err != nil {\n\t\t\tif err != storage.ErrNotFound {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get client\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t} else {\n\t\t\t\ts.tokenErrHelper(w, errInvalidClient, \"Invalid client credentials.\", http.StatusUnauthorized)\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tif client.Secret != deviceReq.ClientSecret {\n\t\t\ts.tokenErrHelper(w, errInvalidClient, \"Invalid client credentials.\", http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\n\t\tresp, err := s.exchangeAuthCode(ctx, w, authCode, client)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"could not exchange auth code for clien\", \"client_id\", deviceReq.ClientID, \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Failed to exchange auth code.\")\n\t\t\treturn\n\t\t}\n\n\t\t// Grab the device token from storage\n\t\told, err := s.storage.GetDeviceToken(ctx, deviceReq.DeviceCode)\n\t\tif err != nil || s.now().After(old.Expiry) {\n\t\t\terrCode := http.StatusBadRequest\n\t\t\tif err != nil && err != storage.ErrNotFound {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get device token\", \"err\", err)\n\t\t\t\terrCode = http.StatusInternalServerError\n\t\t\t}\n\t\t\ts.renderError(r, w, errCode, \"Invalid or expired device code.\")\n\t\t\treturn\n\t\t}\n\n\t\tupdater := func(old storage.DeviceToken) (storage.DeviceToken, error) {\n\t\t\tif old.Status == deviceTokenComplete {\n\t\t\t\treturn old, errors.New(\"device token already complete\")\n\t\t\t}\n\t\t\trespStr, err := json.MarshalIndent(resp, \"\", \"  \")\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to marshal device token response\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"\")\n\t\t\t\treturn old, err\n\t\t\t}\n\n\t\t\told.Token = string(respStr)\n\t\t\told.Status = deviceTokenComplete\n\t\t\treturn old, nil\n\t\t}\n\n\t\t// Update refresh token in the storage, store the token and mark as complete\n\t\tif err := s.storage.UpdateDeviceToken(ctx, deviceReq.DeviceCode, updater); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to update device token\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"\")\n\t\t\treturn\n\t\t}\n\n\t\tif err := s.templates.deviceSuccess(r, w, client.Name); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"Server template error\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusNotFound, \"Page not found\")\n\t\t}\n\n\tdefault:\n\t\ts.logger.ErrorContext(r.Context(), \"unsupported method in device callback\", \"method\", r.Method)\n\t\ts.renderError(r, w, http.StatusBadRequest, ErrMsgMethodNotAllowed)\n\t\treturn\n\t}\n}\n\nfunc (s *Server) verifyUserCode(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tswitch r.Method {\n\tcase http.MethodPost:\n\t\terr := r.ParseForm()\n\t\tif err != nil {\n\t\t\ts.logger.Warn(\"could not parse user code verification request body\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"\")\n\t\t\treturn\n\t\t}\n\n\t\tuserCode := r.Form.Get(\"user_code\")\n\t\tif userCode == \"\" {\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"No user code received\")\n\t\t\treturn\n\t\t}\n\n\t\tuserCode = strings.ToUpper(userCode)\n\n\t\t// Find the user code in the available requests\n\t\tdeviceRequest, err := s.storage.GetDeviceRequest(ctx, userCode)\n\t\tif err != nil || s.now().After(deviceRequest.Expiry) {\n\t\t\tif err != nil && err != storage.ErrNotFound {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get device request\", \"err\", err)\n\t\t\t}\n\t\t\tif err := s.templates.device(r, w, s.getDeviceVerificationURI(), userCode, true); err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"Server template error\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusNotFound, \"Page not found\")\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\t// Redirect to Dex Auth Endpoint\n\t\tauthURL := s.absURL(\"/auth\")\n\t\tu, err := url.Parse(authURL)\n\t\tif err != nil {\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Invalid auth URI.\")\n\t\t\treturn\n\t\t}\n\t\tq := u.Query()\n\t\tq.Set(\"client_id\", deviceRequest.ClientID)\n\t\tq.Set(\"client_secret\", deviceRequest.ClientSecret)\n\t\tq.Set(\"state\", deviceRequest.UserCode)\n\t\tq.Set(\"response_type\", \"code\")\n\t\tq.Set(\"redirect_uri\", s.absPath(deviceCallbackURI))\n\t\tq.Set(\"scope\", strings.Join(deviceRequest.Scopes, \" \"))\n\t\tu.RawQuery = q.Encode()\n\n\t\thttp.Redirect(w, r, u.String(), http.StatusFound)\n\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist.\")\n\t}\n}\n"
  },
  {
    "path": "server/deviceflowhandlers_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"path\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\nfunc TestDeviceVerificationURI(t *testing.T) {\n\tt0 := time.Now()\n\n\tnow := func() time.Time { return t0 }\n\t// Setup a dex server.\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t\tc.Now = now\n\t})\n\tdefer httpServer.Close()\n\n\tu, err := url.Parse(s.issuerURL.String())\n\tif err != nil {\n\t\tt.Fatalf(\"Could not parse issuer URL %v\", err)\n\t}\n\tu.Path = path.Join(u.Path, \"/device/auth/verify_code\")\n\n\turi := s.getDeviceVerificationURI()\n\tif uri != u.Path {\n\t\tt.Errorf(\"Invalid verification URI.  Expected %v got %v\", u.Path, uri)\n\t}\n}\n\nfunc TestHandleDeviceCode(t *testing.T) {\n\tt0 := time.Now()\n\n\tnow := func() time.Time { return t0 }\n\n\ttests := []struct {\n\t\ttestName               string\n\t\tclientID               string\n\t\tcodeChallengeMethod    string\n\t\trequestType            string\n\t\tscopes                 []string\n\t\texpectedResponseCode   int\n\t\texpectedContentType    string\n\t\texpectedServerResponse string\n\t}{\n\t\t{\n\t\t\ttestName:             \"New Code\",\n\t\t\tclientID:             \"test\",\n\t\t\trequestType:          \"POST\",\n\t\t\tscopes:               []string{\"openid\", \"profile\", \"email\"},\n\t\t\texpectedResponseCode: http.StatusOK,\n\t\t\texpectedContentType:  \"application/json\",\n\t\t},\n\t\t{\n\t\t\ttestName:             \"Invalid request Type (GET)\",\n\t\t\tclientID:             \"test\",\n\t\t\trequestType:          \"GET\",\n\t\t\tscopes:               []string{\"openid\", \"profile\", \"email\"},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t\texpectedContentType:  \"application/json\",\n\t\t},\n\t\t{\n\t\t\ttestName:             \"New Code with valid PKCE\",\n\t\t\tclientID:             \"test\",\n\t\t\trequestType:          \"POST\",\n\t\t\tscopes:               []string{\"openid\", \"profile\", \"email\"},\n\t\t\tcodeChallengeMethod:  \"S256\",\n\t\t\texpectedResponseCode: http.StatusOK,\n\t\t\texpectedContentType:  \"application/json\",\n\t\t},\n\t\t{\n\t\t\ttestName:             \"Invalid code challenge method\",\n\t\t\tclientID:             \"test\",\n\t\t\trequestType:          \"POST\",\n\t\t\tcodeChallengeMethod:  \"invalid\",\n\t\t\tscopes:               []string{\"openid\", \"profile\", \"email\"},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t\texpectedContentType:  \"application/json\",\n\t\t},\n\t\t{\n\t\t\ttestName:             \"New Code without scope\",\n\t\t\tclientID:             \"test\",\n\t\t\trequestType:          \"POST\",\n\t\t\tscopes:               []string{},\n\t\t\texpectedResponseCode: http.StatusOK,\n\t\t\texpectedContentType:  \"application/json\",\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\t// Setup a dex server.\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Issuer += \"/non-root-path\"\n\t\t\t\tc.Now = now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Could not parse issuer URL %v\", err)\n\t\t\t}\n\t\t\tu.Path = path.Join(u.Path, \"device/code\")\n\n\t\t\tdata := url.Values{}\n\t\t\tdata.Set(\"client_id\", tc.clientID)\n\t\t\tdata.Set(\"code_challenge_method\", tc.codeChallengeMethod)\n\t\t\tfor _, scope := range tc.scopes {\n\t\t\t\tdata.Add(\"scope\", scope)\n\t\t\t}\n\t\t\treq, _ := http.NewRequest(tc.requestType, u.String(), bytes.NewBufferString(data.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded; param=value\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\t\t\tif rr.Code != tc.expectedResponseCode {\n\t\t\t\tt.Errorf(\"Unexpected Response Type.  Expected %v got %v\", tc.expectedResponseCode, rr.Code)\n\t\t\t}\n\n\t\t\tif rr.Header().Get(\"content-type\") != tc.expectedContentType {\n\t\t\t\tt.Errorf(\"Unexpected Response Content Type.  Expected %v got %v\", tc.expectedContentType, rr.Header().Get(\"content-type\"))\n\t\t\t}\n\n\t\t\tbody, err := io.ReadAll(rr.Body)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Could read token response %v\", err)\n\t\t\t}\n\t\t\tif tc.expectedResponseCode == http.StatusOK {\n\t\t\t\tvar resp deviceCodeResponse\n\t\t\t\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\t\t\t\tt.Errorf(\"Unexpected Device Code Response Format %v\", string(body))\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeviceCallback(t *testing.T) {\n\tt0 := time.Now()\n\n\tnow := func() time.Time { return t0 }\n\n\ttype formValues struct {\n\t\tstate string\n\t\tcode  string\n\t\terror string\n\t}\n\n\t// Base \"Control\" test values\n\tbaseFormValues := formValues{\n\t\tstate: \"XXXX-XXXX\",\n\t\tcode:  \"somecode\",\n\t}\n\tbaseAuthCode := storage.AuthCode{\n\t\tID:            \"somecode\",\n\t\tClientID:      \"testclient\",\n\t\tRedirectURI:   deviceCallbackURI,\n\t\tNonce:         \"\",\n\t\tScopes:        []string{\"openid\", \"profile\", \"email\"},\n\t\tConnectorID:   \"mock\",\n\t\tConnectorData: nil,\n\t\tClaims:        storage.Claims{},\n\t\tExpiry:        now().Add(5 * time.Minute),\n\t}\n\tbaseDeviceRequest := storage.DeviceRequest{\n\t\tUserCode:     \"XXXX-XXXX\",\n\t\tDeviceCode:   \"devicecode\",\n\t\tClientID:     \"testclient\",\n\t\tClientSecret: \"\",\n\t\tScopes:       []string{\"openid\", \"profile\", \"email\"},\n\t\tExpiry:       now().Add(5 * time.Minute),\n\t}\n\tbaseDeviceToken := storage.DeviceToken{\n\t\tDeviceCode:          \"devicecode\",\n\t\tStatus:              deviceTokenPending,\n\t\tToken:               \"\",\n\t\tExpiry:              now().Add(5 * time.Minute),\n\t\tLastRequestTime:     time.Time{},\n\t\tPollIntervalSeconds: 0,\n\t}\n\n\ttests := []struct {\n\t\ttestName               string\n\t\texpectedResponseCode   int\n\t\texpectedServerResponse string\n\t\tvalues                 formValues\n\t\ttestAuthCode           storage.AuthCode\n\t\ttestDeviceRequest      storage.DeviceRequest\n\t\ttestDeviceToken        storage.DeviceToken\n\t}{\n\t\t{\n\t\t\ttestName: \"Missing State\",\n\t\t\tvalues: formValues{\n\t\t\t\tstate: \"\",\n\t\t\t\tcode:  \"somecode\",\n\t\t\t\terror: \"\",\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Missing Code\",\n\t\t\tvalues: formValues{\n\t\t\t\tstate: \"XXXX-XXXX\",\n\t\t\t\tcode:  \"\",\n\t\t\t\terror: \"\",\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Error During Authorization\",\n\t\t\tvalues: formValues{\n\t\t\t\tstate: \"XXXX-XXXX\",\n\t\t\t\tcode:  \"somecode\",\n\t\t\t\terror: \"Error Condition\",\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t\t// Note: Error details should NOT be displayed to user anymore.\n\t\t\t// Instead, a safe generic message is shown.\n\t\t},\n\t\t{\n\t\t\ttestName: \"Expired Auth Code\",\n\t\t\tvalues:   baseFormValues,\n\t\t\ttestAuthCode: storage.AuthCode{\n\t\t\t\tID:            \"somecode\",\n\t\t\t\tClientID:      \"testclient\",\n\t\t\t\tRedirectURI:   deviceCallbackURI,\n\t\t\t\tNonce:         \"\",\n\t\t\t\tScopes:        []string{\"openid\", \"profile\", \"email\"},\n\t\t\t\tConnectorID:   \"pic\",\n\t\t\t\tConnectorData: nil,\n\t\t\t\tClaims:        storage.Claims{},\n\t\t\t\tExpiry:        now().Add(-5 * time.Minute),\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Invalid Auth Code\",\n\t\t\tvalues:   baseFormValues,\n\t\t\ttestAuthCode: storage.AuthCode{\n\t\t\t\tID:            \"somecode\",\n\t\t\t\tClientID:      \"testclient\",\n\t\t\t\tRedirectURI:   deviceCallbackURI,\n\t\t\t\tNonce:         \"\",\n\t\t\t\tScopes:        []string{\"openid\", \"profile\", \"email\"},\n\t\t\t\tConnectorID:   \"pic\",\n\t\t\t\tConnectorData: nil,\n\t\t\t\tClaims:        storage.Claims{},\n\t\t\t\tExpiry:        now().Add(5 * time.Minute),\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:     \"Expired Device Request\",\n\t\t\tvalues:       baseFormValues,\n\t\t\ttestAuthCode: baseAuthCode,\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:   \"XXXX-XXXX\",\n\t\t\t\tDeviceCode: \"devicecode\",\n\t\t\t\tClientID:   \"testclient\",\n\t\t\t\tScopes:     []string{\"openid\", \"profile\", \"email\"},\n\t\t\t\tExpiry:     now().Add(-5 * time.Minute),\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:     \"Non-Existent User Code\",\n\t\t\tvalues:       baseFormValues,\n\t\t\ttestAuthCode: baseAuthCode,\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:   \"ZZZZ-ZZZZ\",\n\t\t\t\tDeviceCode: \"devicecode\",\n\t\t\t\tScopes:     []string{\"openid\", \"profile\", \"email\"},\n\t\t\t\tExpiry:     now().Add(5 * time.Minute),\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:     \"Bad Device Request Client\",\n\t\t\tvalues:       baseFormValues,\n\t\t\ttestAuthCode: baseAuthCode,\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:   \"XXXX-XXXX\",\n\t\t\t\tDeviceCode: \"devicecode\",\n\t\t\t\tScopes:     []string{\"openid\", \"profile\", \"email\"},\n\t\t\t\tExpiry:     now().Add(5 * time.Minute),\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusUnauthorized,\n\t\t},\n\t\t{\n\t\t\ttestName:     \"Bad Device Request Secret\",\n\t\t\tvalues:       baseFormValues,\n\t\t\ttestAuthCode: baseAuthCode,\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:     \"XXXX-XXXX\",\n\t\t\t\tDeviceCode:   \"devicecode\",\n\t\t\t\tClientSecret: \"foobar\",\n\t\t\t\tScopes:       []string{\"openid\", \"profile\", \"email\"},\n\t\t\t\tExpiry:       now().Add(5 * time.Minute),\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusUnauthorized,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Expired Device Token\",\n\t\t\tvalues:            baseFormValues,\n\t\t\ttestAuthCode:      baseAuthCode,\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"devicecode\",\n\t\t\t\tStatus:              deviceTokenPending,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(-5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Device Code Already Redeemed\",\n\t\t\tvalues:            baseFormValues,\n\t\t\ttestAuthCode:      baseAuthCode,\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"devicecode\",\n\t\t\t\tStatus:              deviceTokenComplete,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:             \"Successful Exchange\",\n\t\t\tvalues:               baseFormValues,\n\t\t\ttestAuthCode:         baseAuthCode,\n\t\t\ttestDeviceRequest:    baseDeviceRequest,\n\t\t\ttestDeviceToken:      baseDeviceToken,\n\t\t\texpectedResponseCode: http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Prevent cross-site scripting\",\n\t\t\tvalues: formValues{\n\t\t\t\tstate: \"XXXX-XXXX\",\n\t\t\t\tcode:  \"somecode\",\n\t\t\t\terror: \"<script>console.log(window);</script>\",\n\t\t\t},\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t\t// Note: XSS data should NOT be displayed to user anymore.\n\t\t\t// Instead, a safe generic message is shown.\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\n\t\t\t// Setup a dex server.\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Issuer = c.Issuer + \"/non-root-path\"\n\t\t\t\tc.Now = now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tif err := s.storage.CreateAuthCode(ctx, tc.testAuthCode); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create auth code: %v\", err)\n\t\t\t}\n\n\t\t\tif err := s.storage.CreateDeviceRequest(ctx, tc.testDeviceRequest); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create device request: %v\", err)\n\t\t\t}\n\n\t\t\tif err := s.storage.CreateDeviceToken(ctx, tc.testDeviceToken); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create device token: %v\", err)\n\t\t\t}\n\n\t\t\tclient := storage.Client{\n\t\t\t\tID:           \"testclient\",\n\t\t\t\tSecret:       \"\",\n\t\t\t\tRedirectURIs: []string{deviceCallbackURI},\n\t\t\t}\n\t\t\tif err := s.storage.CreateClient(ctx, client); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t\t\t}\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Could not parse issuer URL %v\", err)\n\t\t\t}\n\t\t\tu.Path = path.Join(u.Path, \"device/callback\")\n\t\t\tq := u.Query()\n\t\t\tq.Set(\"state\", tc.values.state)\n\t\t\tq.Set(\"code\", tc.values.code)\n\t\t\tq.Set(\"error\", tc.values.error)\n\t\t\tu.RawQuery = q.Encode()\n\t\t\treq, _ := http.NewRequest(\"GET\", u.String(), nil)\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded; param=value\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\t\t\tif rr.Code != tc.expectedResponseCode {\n\t\t\t\tt.Errorf(\"%s: Unexpected Response Type.  Expected %v got %v\", tc.testName, tc.expectedResponseCode, rr.Code)\n\t\t\t}\n\n\t\t\tif len(tc.expectedServerResponse) > 0 {\n\t\t\t\tresult, _ := io.ReadAll(rr.Body)\n\t\t\t\tif string(result) != tc.expectedServerResponse {\n\t\t\t\t\tt.Errorf(\"%s: Unexpected Response.  Expected %q got %q\", tc.testName, tc.expectedServerResponse, result)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Special check for error message safety tests\n\t\t\tif tc.testName == \"Prevent cross-site scripting\" || tc.testName == \"Error During Authorization\" {\n\t\t\t\tresult, _ := io.ReadAll(rr.Body)\n\t\t\t\tresponseBody := string(result)\n\n\t\t\t\t// Error details should NOT be present in the response (for security)\n\t\t\t\tif tc.testName == \"Prevent cross-site scripting\" {\n\t\t\t\t\tif strings.Contains(responseBody, \"<script>\") || strings.Contains(responseBody, \"console.log(window)\") {\n\t\t\t\t\t\tt.Errorf(\"%s: XSS script found in response, but should be blocked: %q\", tc.testName, responseBody)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif tc.testName == \"Error During Authorization\" {\n\t\t\t\t\tif strings.Contains(responseBody, \"Error Condition\") {\n\t\t\t\t\t\tt.Errorf(\"%s: Error details found in response, but should be hidden: %q\", tc.testName, responseBody)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Safe message should be present\n\t\t\t\tif !strings.Contains(responseBody, \"Authorization failed. Please try again.\") {\n\t\t\t\t\tt.Errorf(\"%s: Safe error message not found in response: %q\", tc.testName, responseBody)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDeviceTokenResponse(t *testing.T) {\n\tt0 := time.Now()\n\n\tnow := func() time.Time { return t0 }\n\n\t// Base PKCE values\n\t// base64-urlencoded, sha256 digest of code_verifier\n\tcodeChallenge := \"L7ZqsT_zNwvrH8E7J0CqPHx1wgBaFiaE-fAZcKUUAbc\"\n\tcodeChallengeMethod := \"S256\"\n\t// \"random\" string between 43 & 128 ASCII characters\n\tcodeVerifier := \"66114650f56cc45dee7ee03c49f048ddf9aa53cbf5b09985832fa4f790ff2604\"\n\n\tbaseDeviceRequest := storage.DeviceRequest{\n\t\tUserCode:   \"ABCD-WXYZ\",\n\t\tDeviceCode: \"foo\",\n\t\tClientID:   \"testclient\",\n\t\tScopes:     []string{\"openid\", \"profile\", \"offline_access\"},\n\t\tExpiry:     now().Add(5 * time.Minute),\n\t}\n\n\ttests := []struct {\n\t\ttestName               string\n\t\ttestDeviceRequest      storage.DeviceRequest\n\t\ttestDeviceToken        storage.DeviceToken\n\t\ttestGrantType          string\n\t\ttestDeviceCode         string\n\t\ttestCodeVerifier       string\n\t\texpectedServerResponse string\n\t\texpectedResponseCode   int\n\t}{\n\t\t{\n\t\t\ttestName:          \"Valid but pending token\",\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"f00bar\",\n\t\t\t\tStatus:              deviceTokenPending,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"f00bar\",\n\t\t\texpectedServerResponse: deviceTokenPending,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Invalid Grant Type\",\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"f00bar\",\n\t\t\t\tStatus:              deviceTokenPending,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"f00bar\",\n\t\t\ttestGrantType:          grantTypeAuthorizationCode,\n\t\t\texpectedServerResponse: errInvalidGrant,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Test Slow Down State\",\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"f00bar\",\n\t\t\t\tStatus:              deviceTokenPending,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     now(),\n\t\t\t\tPollIntervalSeconds: 10,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"f00bar\",\n\t\t\texpectedServerResponse: deviceTokenSlowDown,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Test Expired Device Token\",\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"f00bar\",\n\t\t\t\tStatus:              deviceTokenPending,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(-5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"f00bar\",\n\t\t\texpectedServerResponse: deviceTokenExpired,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Test Nonexistent Device Code\",\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"foo\",\n\t\t\t\tStatus:              deviceTokenPending,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(-5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"bar\",\n\t\t\texpectedServerResponse: errInvalidRequest,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Empty Device Code in Request\",\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"bar\",\n\t\t\t\tStatus:              deviceTokenPending,\n\t\t\t\tToken:               \"\",\n\t\t\t\tExpiry:              now().Add(-5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"\",\n\t\t\texpectedServerResponse: errInvalidRequest,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName:          \"Claim validated token from Device Code\",\n\t\t\ttestDeviceRequest: baseDeviceRequest,\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"foo\",\n\t\t\t\tStatus:              deviceTokenComplete,\n\t\t\t\tToken:               \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"foo\",\n\t\t\texpectedServerResponse: \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\texpectedResponseCode:   http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Successful Exchange with PKCE\",\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"foo\",\n\t\t\t\tStatus:              deviceTokenComplete,\n\t\t\t\tToken:               \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t\tPKCE: storage.PKCE{\n\t\t\t\t\tCodeChallenge:       codeChallenge,\n\t\t\t\t\tCodeChallengeMethod: codeChallengeMethod,\n\t\t\t\t},\n\t\t\t},\n\t\t\ttestDeviceCode:         \"foo\",\n\t\t\ttestCodeVerifier:       codeVerifier,\n\t\t\ttestDeviceRequest:      baseDeviceRequest,\n\t\t\texpectedServerResponse: \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\texpectedResponseCode:   http.StatusOK,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Test Exchange started with PKCE but without verifier provided\",\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"foo\",\n\t\t\t\tStatus:              deviceTokenComplete,\n\t\t\t\tToken:               \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t\tPKCE: storage.PKCE{\n\t\t\t\t\tCodeChallenge:       codeChallenge,\n\t\t\t\t\tCodeChallengeMethod: codeChallengeMethod,\n\t\t\t\t},\n\t\t\t},\n\t\t\ttestDeviceCode:         \"foo\",\n\t\t\ttestDeviceRequest:      baseDeviceRequest,\n\t\t\texpectedServerResponse: errInvalidGrant,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Test Exchange not started with PKCE but verifier provided\",\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"foo\",\n\t\t\t\tStatus:              deviceTokenComplete,\n\t\t\t\tToken:               \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t},\n\t\t\ttestDeviceCode:         \"foo\",\n\t\t\ttestCodeVerifier:       codeVerifier,\n\t\t\ttestDeviceRequest:      baseDeviceRequest,\n\t\t\texpectedServerResponse: errInvalidRequest,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Test with PKCE but incorrect verifier provided\",\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"foo\",\n\t\t\t\tStatus:              deviceTokenComplete,\n\t\t\t\tToken:               \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t\tPKCE: storage.PKCE{\n\t\t\t\t\tCodeChallenge:       codeChallenge,\n\t\t\t\t\tCodeChallengeMethod: codeChallengeMethod,\n\t\t\t\t},\n\t\t\t},\n\t\t\ttestDeviceCode:         \"foo\",\n\t\t\ttestCodeVerifier:       \"invalid\",\n\t\t\ttestDeviceRequest:      baseDeviceRequest,\n\t\t\texpectedServerResponse: errInvalidGrant,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Test with PKCE but incorrect challenge provided\",\n\t\t\ttestDeviceToken: storage.DeviceToken{\n\t\t\t\tDeviceCode:          \"foo\",\n\t\t\t\tStatus:              deviceTokenComplete,\n\t\t\t\tToken:               \"{\\\"access_token\\\": \\\"foobar\\\"}\",\n\t\t\t\tExpiry:              now().Add(5 * time.Minute),\n\t\t\t\tLastRequestTime:     time.Time{},\n\t\t\t\tPollIntervalSeconds: 0,\n\t\t\t\tPKCE: storage.PKCE{\n\t\t\t\t\tCodeChallenge:       \"invalid\",\n\t\t\t\t\tCodeChallengeMethod: codeChallengeMethod,\n\t\t\t\t},\n\t\t\t},\n\t\t\ttestDeviceCode:         \"foo\",\n\t\t\ttestCodeVerifier:       codeVerifier,\n\t\t\ttestDeviceRequest:      baseDeviceRequest,\n\t\t\texpectedServerResponse: errInvalidGrant,\n\t\t\texpectedResponseCode:   http.StatusBadRequest,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\n\t\t\t// Setup a dex server.\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Issuer += \"/non-root-path\"\n\t\t\t\tc.Now = now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tif err := s.storage.CreateDeviceRequest(ctx, tc.testDeviceRequest); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to store device token %v\", err)\n\t\t\t}\n\n\t\t\tif err := s.storage.CreateDeviceToken(ctx, tc.testDeviceToken); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to store device token %v\", err)\n\t\t\t}\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Could not parse issuer URL %v\", err)\n\t\t\t}\n\t\t\tu.Path = path.Join(u.Path, \"device/token\")\n\n\t\t\tdata := url.Values{}\n\t\t\tgrantType := grantTypeDeviceCode\n\t\t\tif tc.testGrantType != \"\" {\n\t\t\t\tgrantType = tc.testGrantType\n\t\t\t}\n\t\t\tdata.Set(\"grant_type\", grantType)\n\t\t\tdata.Set(\"device_code\", tc.testDeviceCode)\n\t\t\tif tc.testCodeVerifier != \"\" {\n\t\t\t\tdata.Set(\"code_verifier\", tc.testCodeVerifier)\n\t\t\t}\n\t\t\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(data.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded; param=value\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\t\t\tif rr.Code != tc.expectedResponseCode {\n\t\t\t\tt.Errorf(\"Unexpected Response Type.  Expected %v got %v\", tc.expectedResponseCode, rr.Code)\n\t\t\t}\n\n\t\t\tbody, err := io.ReadAll(rr.Body)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Could read token response %v\", err)\n\t\t\t}\n\t\t\tif tc.expectedResponseCode == http.StatusBadRequest || tc.expectedResponseCode == http.StatusUnauthorized {\n\t\t\t\texpectJSONErrorResponse(tc.testName, body, tc.expectedServerResponse, t)\n\t\t\t} else if string(body) != tc.expectedServerResponse {\n\t\t\t\tt.Errorf(\"Unexpected Server Response.  Expected %v got %v\", tc.expectedServerResponse, string(body))\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc expectJSONErrorResponse(testCase string, body []byte, expectedError string, t *testing.T) {\n\tjsonMap := make(map[string]any)\n\terr := json.Unmarshal(body, &jsonMap)\n\tif err != nil {\n\t\tt.Errorf(\"Unexpected error unmarshalling response: %v\", err)\n\t}\n\tif jsonMap[\"error\"] != expectedError {\n\t\tt.Errorf(\"Test Case %s expected error %v, received %v\", testCase, expectedError, jsonMap[\"error\"])\n\t}\n}\n\nfunc TestVerifyCodeResponse(t *testing.T) {\n\tt0 := time.Now()\n\n\tnow := func() time.Time { return t0 }\n\n\ttests := []struct {\n\t\ttestName             string\n\t\ttestDeviceRequest    storage.DeviceRequest\n\t\tuserCode             string\n\t\texpectedResponseCode int\n\t\texpectedAuthPath     string\n\t\tshouldRedirectToAuth bool\n\t}{\n\t\t{\n\t\t\ttestName: \"Unknown user code\",\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:   \"ABCD-WXYZ\",\n\t\t\t\tDeviceCode: \"f00bar\",\n\t\t\t\tClientID:   \"testclient\",\n\t\t\t\tScopes:     []string{\"openid\", \"profile\", \"offline_access\"},\n\t\t\t\tExpiry:     now().Add(5 * time.Minute),\n\t\t\t},\n\t\t\tuserCode:             \"CODE-TEST\",\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Expired user code\",\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:   \"ABCD-WXYZ\",\n\t\t\t\tDeviceCode: \"f00bar\",\n\t\t\t\tClientID:   \"testclient\",\n\t\t\t\tScopes:     []string{\"openid\", \"profile\", \"offline_access\"},\n\t\t\t\tExpiry:     now().Add(-5 * time.Minute),\n\t\t\t},\n\t\t\tuserCode:             \"ABCD-WXYZ\",\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"No user code\",\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:   \"ABCD-WXYZ\",\n\t\t\t\tDeviceCode: \"f00bar\",\n\t\t\t\tClientID:   \"testclient\",\n\t\t\t\tScopes:     []string{\"openid\", \"profile\", \"offline_access\"},\n\t\t\t\tExpiry:     now().Add(-5 * time.Minute),\n\t\t\t},\n\t\t\tuserCode:             \"\",\n\t\t\texpectedResponseCode: http.StatusBadRequest,\n\t\t},\n\t\t{\n\t\t\ttestName: \"Valid user code, expect redirect to auth endpoint with device callback\",\n\t\t\ttestDeviceRequest: storage.DeviceRequest{\n\t\t\t\tUserCode:   \"ABCD-WXYZ\",\n\t\t\t\tDeviceCode: \"f00bar\",\n\t\t\t\tClientID:   \"testclient\",\n\t\t\t\tScopes:     []string{\"openid\", \"profile\", \"offline_access\"},\n\t\t\t\tExpiry:     now().Add(5 * time.Minute),\n\t\t\t},\n\t\t\tuserCode:             \"ABCD-WXYZ\",\n\t\t\texpectedResponseCode: http.StatusFound,\n\t\t\texpectedAuthPath:     \"/auth\",\n\t\t\tshouldRedirectToAuth: true,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\n\t\t\t// Setup a dex server.\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Issuer += \"/non-root-path\"\n\t\t\t\tc.Now = now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tif err := s.storage.CreateDeviceRequest(ctx, tc.testDeviceRequest); err != nil {\n\t\t\t\tt.Fatalf(\"Failed to store device token %v\", err)\n\t\t\t}\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Could not parse issuer URL %v\", err)\n\t\t\t}\n\n\t\t\tu.Path = path.Join(u.Path, \"device/auth/verify_code\")\n\t\t\tdata := url.Values{}\n\t\t\tdata.Set(\"user_code\", tc.userCode)\n\t\t\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(data.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded; param=value\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\t\t\tif rr.Code != tc.expectedResponseCode {\n\t\t\t\tt.Errorf(\"Unexpected Response Type.  Expected %v got %v\", tc.expectedResponseCode, rr.Code)\n\t\t\t}\n\n\t\t\tlocation := rr.Header().Get(\"Location\")\n\t\t\tif rr.Code == http.StatusFound && tc.shouldRedirectToAuth {\n\t\t\t\t// Parse the redirect location\n\t\t\t\tredirectURL, err := url.Parse(location)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Could not parse redirect URL: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Check that the redirect path contains /auth\n\t\t\t\tif !strings.Contains(redirectURL.Path, tc.expectedAuthPath) {\n\t\t\t\t\tt.Errorf(\"Invalid Redirect Path. Expected to contain %q got %q\", tc.expectedAuthPath, redirectURL.Path)\n\t\t\t\t}\n\n\t\t\t\t// Check that redirect_uri parameter contains /device/callback\n\t\t\t\tif !strings.Contains(location, \"redirect_uri=%2Fnon-root-path%2Fdevice%2Fcallback\") {\n\t\t\t\t\tt.Errorf(\"Invalid redirect_uri parameter. Expected to contain /device/callback (URL encoded), got %v\", location)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/doc.go",
    "content": "// Package server implements an OpenID Connect server with federated logins.\npackage server\n"
  },
  {
    "path": "server/errors.go",
    "content": "package server\n\n// Safe error messages for user-facing responses.\n// These messages are intentionally generic to avoid leaking internal details.\n// All actual error details should be logged server-side.\n\nconst (\n\t// ErrMsgLoginError is a generic login error message shown to users.\n\t// Used when authentication fails due to internal server errors.\n\tErrMsgLoginError = \"Login error. Please contact your administrator or try again later.\"\n\n\t// ErrMsgAuthenticationFailed is shown when callback/SAML authentication fails.\n\tErrMsgAuthenticationFailed = \"Authentication failed. Please contact your administrator or try again later.\"\n\n\t// ErrMsgInternalServerError is a generic internal server error message.\n\tErrMsgInternalServerError = \"Internal server error. Please contact your administrator or try again later.\"\n\n\t// ErrMsgDatabaseError is shown when database operations fail.\n\tErrMsgDatabaseError = \"A database error occurred. Please try again later.\"\n\n\t// ErrMsgInvalidRequest is shown when request parsing fails.\n\tErrMsgInvalidRequest = \"Invalid request. Please try again.\"\n\n\t// ErrMsgMethodNotAllowed is shown when an unsupported HTTP method is used.\n\tErrMsgMethodNotAllowed = \"Method not allowed.\"\n\n\t// ErrMsgNotInRequiredGroups is shown when a user authenticates successfully\n\t// but is not a member of any of the groups required by the connector.\n\tErrMsgNotInRequiredGroups = \"You are not a member of any of the required groups to authenticate.\"\n)\n"
  },
  {
    "path": "server/errors_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// TestErrorMessagesDoNotLeakInternalDetails verifies that error responses\n// do not contain internal error details that could be exploited by attackers.\nfunc TestErrorMessagesDoNotLeakInternalDetails(t *testing.T) {\n\t// List of sensitive patterns that should never appear in user-facing errors\n\tsensitivePatterns := []string{\n\t\t\"panic\",\n\t\t\"runtime error\",\n\t\t\"nil pointer\",\n\t\t\"stack trace\",\n\t\t\"goroutine\",\n\t\t\".go:\",       // file paths like \"server.go:123\"\n\t\t\"sql:\",       // SQL errors\n\t\t\"connection\", // Connection errors\n\t\t\"timeout\",    // Unless it's a user-friendly timeout message\n\t\t\"ECONNREFUSED\",\n\t\t\"EOF\",\n\t\t\"broken pipe\",\n\t}\n\n\ttests := []struct {\n\t\tname        string\n\t\tpath        string\n\t\tmethod      string\n\t\tbody        string\n\t\tcontentType string\n\t\tsetupFunc   func(t *testing.T, s *Server)\n\t\tcheckFunc   func(t *testing.T, resp *http.Response, body string)\n\t}{\n\t\t{\n\t\t\tname:        \"Invalid authorization request parse error\",\n\t\t\tpath:        \"/auth\",\n\t\t\tmethod:      \"POST\",\n\t\t\tbody:        \"invalid%body\",\n\t\t\tcontentType: \"application/x-www-form-urlencoded\",\n\t\t\tcheckFunc: func(t *testing.T, resp *http.Response, body string) {\n\t\t\t\t// Should return a safe error message, not the parse error details\n\t\t\t\tfor _, pattern := range sensitivePatterns {\n\t\t\t\t\trequire.NotContains(t, body, pattern,\n\t\t\t\t\t\t\"Response should not contain sensitive pattern: %s\", pattern)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"Invalid callback state\",\n\t\t\tpath:   \"/callback?state=invalid_state\",\n\t\t\tmethod: \"GET\",\n\t\t\tcheckFunc: func(t *testing.T, resp *http.Response, body string) {\n\t\t\t\trequire.Equal(t, http.StatusBadRequest, resp.StatusCode)\n\t\t\t\t// Should not leak storage error details\n\t\t\t\trequire.NotContains(t, body, \"storage\")\n\t\t\t\trequire.NotContains(t, body, \"not found\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"Invalid token request\",\n\t\t\tpath:        \"/token\",\n\t\t\tmethod:      \"POST\",\n\t\t\tbody:        \"grant_type=authorization_code&code=invalid\",\n\t\t\tcontentType: \"application/x-www-form-urlencoded\",\n\t\t\tcheckFunc: func(t *testing.T, resp *http.Response, body string) {\n\t\t\t\t// Token endpoint returns JSON errors which is correct OAuth2 behavior\n\t\t\t\t// Just verify no internal details leak\n\t\t\t\tfor _, pattern := range sensitivePatterns {\n\t\t\t\t\trequire.NotContains(t, body, pattern,\n\t\t\t\t\t\t\"Response should not contain sensitive pattern: %s\", pattern)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"Invalid introspection request - no token\",\n\t\t\tpath:        \"/token/introspect\",\n\t\t\tmethod:      \"POST\",\n\t\t\tbody:        \"\",\n\t\t\tcontentType: \"application/x-www-form-urlencoded\",\n\t\t\tcheckFunc: func(t *testing.T, resp *http.Response, body string) {\n\t\t\t\tfor _, pattern := range sensitivePatterns {\n\t\t\t\t\trequire.NotContains(t, body, pattern,\n\t\t\t\t\t\t\"Response should not contain sensitive pattern: %s\", pattern)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:   \"Device flow invalid user code\",\n\t\t\tpath:   \"/device/auth/verify_code\",\n\t\t\tmethod: \"POST\",\n\t\t\tbody:   \"user_code=INVALID\",\n\t\t\tcheckFunc: func(t *testing.T, resp *http.Response, body string) {\n\t\t\t\tfor _, pattern := range sensitivePatterns {\n\t\t\t\t\trequire.NotContains(t, body, pattern,\n\t\t\t\t\t\t\"Response should not contain sensitive pattern: %s\", pattern)\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thttpServer, s := newTestServer(t, nil)\n\t\t\tdefer httpServer.Close()\n\n\t\t\tif tc.setupFunc != nil {\n\t\t\t\ttc.setupFunc(t, s)\n\t\t\t}\n\n\t\t\tvar reqBody io.Reader\n\t\t\tif tc.body != \"\" {\n\t\t\t\treqBody = strings.NewReader(tc.body)\n\t\t\t}\n\n\t\t\treq := httptest.NewRequest(tc.method, tc.path, reqBody)\n\t\t\tif tc.contentType != \"\" {\n\t\t\t\treq.Header.Set(\"Content-Type\", tc.contentType)\n\t\t\t}\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\n\t\t\tresp := rr.Result()\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tbodyBytes, err := io.ReadAll(resp.Body)\n\t\t\trequire.NoError(t, err)\n\t\t\tbody := string(bodyBytes)\n\n\t\t\tif tc.checkFunc != nil {\n\t\t\t\ttc.checkFunc(t, resp, body)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestLoginErrorMessageIsSafe verifies that the login error page\n// shows a safe, user-friendly message.\nfunc TestLoginErrorMessageIsSafe(t *testing.T) {\n\thttpServer, s := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\t// Create a request that will trigger a login error\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/auth/nonexistent/login?state=test\", nil)\n\ts.ServeHTTP(rr, req)\n\n\tresp := rr.Result()\n\tdefer resp.Body.Close()\n\n\tbody, _ := io.ReadAll(resp.Body)\n\tbodyStr := string(body)\n\n\t// Should not contain error stack traces or internal details\n\trequire.NotContains(t, bodyStr, \"panic\")\n\trequire.NotContains(t, bodyStr, \".go:\")\n\trequire.NotContains(t, bodyStr, \"goroutine\")\n}\n\n// TestCallbackErrorMessageIsSafe verifies that callback errors\n// do not leak internal details.\nfunc TestCallbackErrorMessageIsSafe(t *testing.T) {\n\thttpServer, s := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\t// Test OAuth2 callback with invalid state\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", \"/callback?code=test&state=invalid\", nil)\n\ts.ServeHTTP(rr, req)\n\n\tresp := rr.Result()\n\tdefer resp.Body.Close()\n\n\tbody, _ := io.ReadAll(resp.Body)\n\tbodyStr := string(body)\n\n\t// Should not contain storage error details\n\trequire.NotContains(t, bodyStr, \"storage.ErrNotFound\")\n\trequire.NotContains(t, bodyStr, \"database\")\n}\n\n// TestDeviceCallbackMethodError verifies that unsupported methods\n// return safe error messages.\nfunc TestDeviceCallbackMethodError(t *testing.T) {\n\thttpServer, s := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\t// Test with unsupported method\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"PUT\", \"/device/callback\", nil)\n\ts.ServeHTTP(rr, req)\n\n\tresp := rr.Result()\n\tdefer resp.Body.Close()\n\n\tbody, _ := io.ReadAll(resp.Body)\n\tbodyStr := string(body)\n\n\t// Should not expose the method name in error\n\trequire.Equal(t, http.StatusBadRequest, resp.StatusCode)\n\trequire.NotContains(t, bodyStr, \"PUT\")\n\trequire.NotContains(t, bodyStr, \"method not implemented\")\n}\n\n// TestRenderErrorSafeMessages tests that renderError uses safe messages\nfunc TestRenderErrorSafeMessages(t *testing.T) {\n\ttests := []struct {\n\t\tname           string\n\t\tstatusCode     int\n\t\tmessage        string\n\t\texpectedInBody []string\n\t\tnotInBody      []string\n\t}{\n\t\t{\n\t\t\tname:           \"Login error message\",\n\t\t\tstatusCode:     http.StatusInternalServerError,\n\t\t\tmessage:        ErrMsgLoginError,\n\t\t\texpectedInBody: []string{\"Login error\", \"administrator\"},\n\t\t\tnotInBody:      []string{\"stack\", \"panic\", \".go:\"},\n\t\t},\n\t\t{\n\t\t\tname:           \"Authentication failed message\",\n\t\t\tstatusCode:     http.StatusInternalServerError,\n\t\t\tmessage:        ErrMsgAuthenticationFailed,\n\t\t\texpectedInBody: []string{\"Authentication failed\", \"administrator\"},\n\t\t\tnotInBody:      []string{\"stack\", \"panic\", \".go:\"},\n\t\t},\n\t\t{\n\t\t\tname:           \"Database error message\",\n\t\t\tstatusCode:     http.StatusInternalServerError,\n\t\t\tmessage:        ErrMsgDatabaseError,\n\t\t\texpectedInBody: []string{\"database error\"},\n\t\t\tnotInBody:      []string{\"sql:\", \"connection\", \"timeout\"},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thttpServer, s := newTestServer(t, nil)\n\t\t\tdefer httpServer.Close()\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\treq := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\t\ts.renderError(req, rr, tc.statusCode, tc.message)\n\n\t\t\tresp := rr.Result()\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tbody, _ := io.ReadAll(resp.Body)\n\t\t\tbodyStr := string(body)\n\n\t\t\trequire.Equal(t, tc.statusCode, resp.StatusCode)\n\n\t\t\tfor _, expected := range tc.expectedInBody {\n\t\t\t\trequire.Contains(t, bodyStr, expected,\n\t\t\t\t\t\"Response should contain: %s\", expected)\n\t\t\t}\n\n\t\t\tfor _, notExpected := range tc.notInBody {\n\t\t\t\trequire.NotContains(t, bodyStr, notExpected,\n\t\t\t\t\t\"Response should not contain: %s\", notExpected)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestTokenErrorDoesNotLeakDetails tests that token errors don't leak internal details\nfunc TestTokenErrorDoesNotLeakDetails(t *testing.T) {\n\thttpServer, s := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\t// Create a token request with invalid credentials\n\tbody := bytes.NewBufferString(\"grant_type=authorization_code&code=invalid_code\")\n\treq := httptest.NewRequest(\"POST\", \"/token\", body)\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.SetBasicAuth(\"invalid_client\", \"invalid_secret\")\n\n\trr := httptest.NewRecorder()\n\ts.ServeHTTP(rr, req)\n\n\tresp := rr.Result()\n\tdefer resp.Body.Close()\n\n\trespBody, _ := io.ReadAll(resp.Body)\n\tbodyStr := string(respBody)\n\n\t// Should not contain internal error details\n\trequire.NotContains(t, bodyStr, \"storage\")\n\trequire.NotContains(t, bodyStr, \"not found\")\n\trequire.NotContains(t, bodyStr, \".go:\")\n}\n"
  },
  {
    "path": "server/handlers.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"crypto/subtle\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"github.com/go-jose/go-jose/v4\"\n\t\"github.com/gorilla/mux\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/pkg/featureflags\"\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\nconst (\n\tcodeChallengeMethodPlain = \"plain\"\n\tcodeChallengeMethodS256  = \"S256\"\n)\n\nfunc (s *Server) handlePublicKeys(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\t// TODO(ericchiang): Cache this.\n\tkeys, err := s.signer.ValidationKeys(ctx)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get keys\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\treturn\n\t}\n\n\tif len(keys) == 0 {\n\t\ts.logger.ErrorContext(r.Context(), \"no public keys found.\")\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\treturn\n\t}\n\n\tjwks := jose.JSONWebKeySet{\n\t\tKeys: make([]jose.JSONWebKey, len(keys)),\n\t}\n\tfor i, key := range keys {\n\t\tjwks.Keys[i] = *key\n\t}\n\n\tdata, err := json.MarshalIndent(jwks, \"\", \"  \")\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to marshal discovery data\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\treturn\n\t}\n\n\t// We don't have NextRotation info from Signer interface easily,\n\t// so we'll just set a reasonable default cache time.\n\tmaxAge := time.Minute * 10\n\n\tw.Header().Set(\"Cache-Control\", fmt.Sprintf(\"max-age=%d, must-revalidate\", int(maxAge.Seconds())))\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(data)))\n\tw.Write(data)\n}\n\ntype discovery struct {\n\tIssuer            string   `json:\"issuer\"`\n\tAuth              string   `json:\"authorization_endpoint\"`\n\tToken             string   `json:\"token_endpoint\"`\n\tKeys              string   `json:\"jwks_uri\"`\n\tUserInfo          string   `json:\"userinfo_endpoint\"`\n\tDeviceEndpoint    string   `json:\"device_authorization_endpoint\"`\n\tIntrospect        string   `json:\"introspection_endpoint\"`\n\tGrantTypes        []string `json:\"grant_types_supported\"`\n\tResponseTypes     []string `json:\"response_types_supported\"`\n\tSubjects          []string `json:\"subject_types_supported\"`\n\tIDTokenAlgs       []string `json:\"id_token_signing_alg_values_supported\"`\n\tCodeChallengeAlgs []string `json:\"code_challenge_methods_supported\"`\n\tScopes            []string `json:\"scopes_supported\"`\n\tAuthMethods       []string `json:\"token_endpoint_auth_methods_supported\"`\n\tClaims            []string `json:\"claims_supported\"`\n}\n\nfunc (s *Server) discoveryHandler(ctx context.Context) (http.HandlerFunc, error) {\n\td := s.constructDiscovery(ctx)\n\n\tdata, err := json.MarshalIndent(d, \"\", \"  \")\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to marshal discovery data: %v\", err)\n\t}\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(data)))\n\t\tw.Write(data)\n\t}), nil\n}\n\nfunc (s *Server) constructDiscovery(ctx context.Context) discovery {\n\td := discovery{\n\t\tIssuer:            s.issuerURL.String(),\n\t\tAuth:              s.absURL(\"/auth\"),\n\t\tToken:             s.absURL(\"/token\"),\n\t\tKeys:              s.absURL(\"/keys\"),\n\t\tUserInfo:          s.absURL(\"/userinfo\"),\n\t\tDeviceEndpoint:    s.absURL(\"/device/code\"),\n\t\tIntrospect:        s.absURL(\"/token/introspect\"),\n\t\tSubjects:          []string{\"public\"},\n\t\tIDTokenAlgs:       []string{string(jose.RS256)},\n\t\tCodeChallengeAlgs: s.pkce.CodeChallengeMethodsSupported,\n\t\tScopes:            []string{\"openid\", \"email\", \"groups\", \"profile\", \"offline_access\"},\n\t\tAuthMethods:       []string{\"client_secret_basic\", \"client_secret_post\"},\n\t\tClaims: []string{\n\t\t\t\"iss\", \"sub\", \"aud\", \"iat\", \"exp\", \"email\", \"email_verified\",\n\t\t\t\"locale\", \"name\", \"preferred_username\", \"at_hash\",\n\t\t},\n\t}\n\n\t// Determine signing algorithm from signer\n\tsigningAlg, err := s.signer.Algorithm(ctx)\n\tif err != nil {\n\t\ts.logger.Error(\"failed to get signing algorithm\", \"err\", err)\n\t} else {\n\t\td.IDTokenAlgs = []string{string(signingAlg)}\n\t}\n\n\tfor responseType := range s.supportedResponseTypes {\n\t\td.ResponseTypes = append(d.ResponseTypes, responseType)\n\t}\n\tsort.Strings(d.ResponseTypes)\n\n\td.GrantTypes = s.supportedGrantTypes\n\treturn d\n}\n\n// grantTypeFromAuthRequest determines the grant type from the authorization request parameters.\nfunc (s *Server) grantTypeFromAuthRequest(r *http.Request) string {\n\tredirectURI := r.Form.Get(\"redirect_uri\")\n\tif redirectURI == deviceCallbackURI || strings.HasSuffix(redirectURI, deviceCallbackURI) {\n\t\treturn grantTypeDeviceCode\n\t}\n\tresponseType := r.Form.Get(\"response_type\")\n\tfor _, rt := range strings.Fields(responseType) {\n\t\tif rt == \"token\" || rt == \"id_token\" {\n\t\t\treturn grantTypeImplicit\n\t\t}\n\t}\n\treturn grantTypeAuthorizationCode\n}\n\n// handleAuthorization handles the OAuth2 auth endpoint.\nfunc (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\t// Extract the arguments\n\tif err := r.ParseForm(); err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to parse arguments\", \"err\", err)\n\n\t\ts.renderError(r, w, http.StatusBadRequest, ErrMsgInvalidRequest)\n\t\treturn\n\t}\n\n\tconnectorID := r.Form.Get(\"connector_id\")\n\tallConnectors, err := s.storage.ListConnectors(ctx)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get list of connectors\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Failed to retrieve connector list.\")\n\t\treturn\n\t}\n\n\t// Determine the grant type from the authorization request to filter connectors.\n\tgrantType := s.grantTypeFromAuthRequest(r)\n\tconnectors := make([]storage.Connector, 0, len(allConnectors))\n\tfor _, c := range allConnectors {\n\t\tif GrantTypeAllowed(c.GrantTypes, grantType) {\n\t\t\tconnectors = append(connectors, c)\n\t\t}\n\t}\n\n\t// Filter connectors based on the client's allowed connectors list.\n\t// client_id is required per RFC 6749 §4.1.1.\n\tclient, authErr := s.getClientWithAuthError(ctx, r.Form.Get(\"client_id\"))\n\tif authErr != nil {\n\t\ts.renderError(r, w, authErr.Status, authErr.Error())\n\t\treturn\n\t}\n\tconnectors = filterConnectors(connectors, client.AllowedConnectors)\n\n\tif len(connectors) == 0 {\n\t\ts.renderError(r, w, http.StatusBadRequest, \"No connectors available for this client.\")\n\t\treturn\n\t}\n\n\t// We don't need connector_id any more\n\tr.Form.Del(\"connector_id\")\n\n\t// Construct a URL with all of the arguments in its query\n\tconnURL := url.URL{\n\t\tRawQuery: r.Form.Encode(),\n\t}\n\n\t// Redirect if a client chooses a specific connector_id\n\tif connectorID != \"\" {\n\t\tfor _, c := range connectors {\n\t\t\tif c.ID == connectorID {\n\t\t\t\tconnURL.Path = s.absPath(\"/auth\", url.PathEscape(c.ID))\n\t\t\t\thttp.Redirect(w, r, connURL.String(), http.StatusFound)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Connector ID does not match a valid Connector\")\n\t\treturn\n\t}\n\n\tif len(connectors) == 1 && !s.alwaysShowLogin {\n\t\tconnURL.Path = s.absPath(\"/auth\", url.PathEscape(connectors[0].ID))\n\t\thttp.Redirect(w, r, connURL.String(), http.StatusFound)\n\t}\n\n\tconnectorInfos := make([]connectorInfo, 0, len(connectors))\n\tfor _, conn := range connectors {\n\t\tconnURL.Path = s.absPath(\"/auth\", url.PathEscape(conn.ID))\n\t\tconnectorInfos = append(connectorInfos, connectorInfo{\n\t\t\tID:   conn.ID,\n\t\t\tName: conn.Name,\n\t\t\tType: conn.Type,\n\t\t\tURL:  template.URL(connURL.String()),\n\t\t})\n\t}\n\n\tif err := s.templates.login(r, w, connectorInfos); err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t}\n}\n\n// filterConnectors filters the list of connectors by the allowed connector IDs.\n// If allowedConnectors is empty, all connectors are returned (no filtering).\nfunc filterConnectors(connectors []storage.Connector, allowedConnectors []string) []storage.Connector {\n\tif len(allowedConnectors) == 0 {\n\t\treturn connectors\n\t}\n\n\tallowed := make(map[string]bool, len(allowedConnectors))\n\tfor _, id := range allowedConnectors {\n\t\tallowed[id] = true\n\t}\n\n\tfiltered := make([]storage.Connector, 0, len(connectors))\n\tfor _, c := range connectors {\n\t\tif allowed[c.ID] {\n\t\t\tfiltered = append(filtered, c)\n\t\t}\n\t}\n\treturn filtered\n}\n\n// isConnectorAllowed checks if a connector ID is in the client's allowed connectors list.\n// If allowedConnectors is empty, all connectors are allowed.\nfunc isConnectorAllowed(allowedConnectors []string, connectorID string) bool {\n\tif len(allowedConnectors) == 0 {\n\t\treturn true\n\t}\n\tfor _, id := range allowedConnectors {\n\t\tif id == connectorID {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// getClientWithAuthError retrieves a client by ID and returns a displayedAuthErr on failure.\n// Invalid client_id is not treated as a redirect error per RFC 6749 §4.1.2.1.\n// https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1\nfunc (s *Server) getClientWithAuthError(ctx context.Context, clientID string) (storage.Client, *displayedAuthErr) {\n\tclient, err := s.storage.GetClient(ctx, clientID)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(ctx, \"invalid client_id provided\", \"client_id\", clientID)\n\t\t\treturn storage.Client{}, newDisplayedErr(http.StatusBadRequest, \"Invalid client_id provided.\")\n\t\t}\n\t\ts.logger.ErrorContext(ctx, \"failed to get client\", \"client_id\", clientID, \"err\", err)\n\t\treturn storage.Client{}, newDisplayedErr(http.StatusInternalServerError, \"Database error.\")\n\t}\n\treturn client, nil\n}\n\nfunc (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tauthReq, hintSubject, err := s.parseAuthorizationRequest(r)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to parse authorization request\", \"err\", err)\n\n\t\tswitch authErr := err.(type) {\n\t\tcase *redirectedAuthErr:\n\t\t\tauthErr.Handler().ServeHTTP(w, r)\n\t\tcase *displayedAuthErr:\n\t\t\ts.renderError(r, w, authErr.Status, err.Error())\n\t\tdefault:\n\t\t\tpanic(\"unsupported error type\")\n\t\t}\n\n\t\treturn\n\t}\n\n\tconnID, err := url.PathUnescape(mux.Vars(r)[\"connector\"])\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to parse connector\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist\")\n\t\treturn\n\t}\n\n\t// Validate that the connector is allowed for this client.\n\tclient, authErr := s.getClientWithAuthError(ctx, authReq.ClientID)\n\tif authErr != nil {\n\t\ts.renderError(r, w, authErr.Status, authErr.Error())\n\t\treturn\n\t}\n\tif !isConnectorAllowed(client.AllowedConnectors, connID) {\n\t\ts.logger.ErrorContext(r.Context(), \"connector not allowed for client\",\n\t\t\t\"connector_id\", connID, \"client_id\", authReq.ClientID)\n\t\ts.renderError(r, w, http.StatusForbidden, \"Connector not allowed for this client.\")\n\t\treturn\n\t}\n\n\tconn, err := s.getConnector(ctx, connID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"Failed to get connector\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Connector failed to initialize\")\n\t\treturn\n\t}\n\n\t// Check if the connector allows the requested grant type.\n\tgrantType := s.grantTypeFromAuthRequest(r)\n\tif !GrantTypeAllowed(conn.GrantTypes, grantType) {\n\t\ts.logger.ErrorContext(r.Context(), \"connector does not allow requested grant type\",\n\t\t\t\"connector_id\", connID, \"grant_type\", grantType)\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested connector does not support this grant type.\")\n\t\treturn\n\t}\n\n\t// Set the connector being used for the login.\n\tif authReq.ConnectorID != \"\" && authReq.ConnectorID != connID {\n\t\ts.logger.ErrorContext(r.Context(), \"mismatched connector ID in auth request\",\n\t\t\t\"auth_request_connector_id\", authReq.ConnectorID, \"connector_id\", connID)\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Bad connector ID\")\n\t\treturn\n\t}\n\n\tauthReq.ConnectorID = connID\n\n\t// Actually create the auth request\n\tauthReq.Expiry = s.now().Add(s.authRequestsValidFor)\n\tif err := s.storage.CreateAuthRequest(ctx, *authReq); err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to create authorization request\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Failed to connect to the database.\")\n\t\treturn\n\t}\n\n\t// Handle OIDC prompt parameter and session-based login.\n\tprompt, err := ParsePrompt(authReq.Prompt)\n\tif err != nil {\n\t\t// Server error because authReq was validated before saving it to database.\n\t\ts.redirectWithError(w, r, authReq, errServerError, \"Invalid authentication request\")\n\t\treturn\n\t}\n\t// handle prompt only if sessions are enabled\n\tif s.sessionConfig != nil {\n\t\t// Retrieve the session once for use in both hint and prompt logic.\n\t\tsession := s.getValidAuthSession(ctx, w, r, authReq)\n\n\t\t// id_token_hint logic (OIDC Core 1.0 3.1.2.1):\n\t\t// When a hint is provided, verify that the session user matches.\n\t\tif hintSubject != \"\" {\n\t\t\tif !sessionMatchesHint(session, hintSubject) {\n\t\t\t\t// Clear the session if the user is different from the hint.\n\t\t\t\tsession = nil\n\t\t\t}\n\t\t\tif session == nil && prompt.None() {\n\t\t\t\t// Cannot authenticate silently with prompt=none.\n\t\t\t\ts.redirectWithError(w, r, authReq, errLoginRequired, \"id_token_hint does not match authenticated user\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// prompt=none: no UI allowed.\n\t\tif prompt.None() {\n\t\t\tredirectURL, ok := s.trySessionLoginWithSession(ctx, r, w, authReq, session)\n\t\t\tif !ok {\n\t\t\t\ts.redirectWithError(w, r, authReq, errLoginRequired, \"User not authenticated\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif redirectURL != \"\" {\n\t\t\t\t// Session found but consent required — no UI allowed.\n\t\t\t\ts.redirectWithError(w, r, authReq, errInteractionRequired, \"Consent required\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tif !prompt.Login() {\n\t\t\t// Normal flow: try session-based login (skip if prompt=login forces re-auth).\n\t\t\tif redirectURL, ok := s.trySessionLoginWithSession(ctx, r, w, authReq, session); ok {\n\t\t\t\tif redirectURL != \"\" {\n\t\t\t\t\thttp.Redirect(w, r, redirectURL, http.StatusSeeOther)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tscopes := parseScopes(authReq.Scopes)\n\n\t// Work out where the \"Select another login method\" link should go.\n\tbackLink := \"\"\n\tif len(s.connectors) > 1 {\n\t\tbackLinkURL := url.URL{\n\t\t\tPath:     s.absPath(\"/auth\"),\n\t\t\tRawQuery: r.Form.Encode(),\n\t\t}\n\t\tbackLink = backLinkURL.String()\n\t}\n\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\tswitch conn := conn.Connector.(type) {\n\t\tcase connector.CallbackConnector:\n\t\t\t// Use the auth request ID as the \"state\" token.\n\t\t\t//\n\t\t\t// TODO(ericchiang): Is this appropriate or should we also be using a nonce?\n\t\t\tcallbackURL, connData, err := conn.LoginURL(scopes, s.absURL(\"/callback\"), authReq.ID)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"connector returned error when creating callback\", \"connector_id\", connID, \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login error.\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif len(connData) > 0 {\n\t\t\t\tupdater := func(a storage.AuthRequest) (storage.AuthRequest, error) {\n\t\t\t\t\ta.ConnectorData = connData\n\t\t\t\t\treturn a, nil\n\t\t\t\t}\n\t\t\t\terr := s.storage.UpdateAuthRequest(ctx, authReq.ID, updater)\n\t\t\t\tif err != nil {\n\t\t\t\t\ts.logger.ErrorContext(r.Context(), \"Failed to set connector data on auth request\", \"connector_id\", connID, \"err\", err)\n\t\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Database error.\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\thttp.Redirect(w, r, callbackURL, http.StatusFound)\n\t\tcase connector.PasswordConnector:\n\t\t\tloginURL := url.URL{\n\t\t\t\tPath: s.absPath(\"/auth\", connID, \"login\"),\n\t\t\t}\n\t\t\tq := loginURL.Query()\n\t\t\tq.Set(\"state\", authReq.ID)\n\t\t\tq.Set(\"back\", backLink)\n\t\t\tloginURL.RawQuery = q.Encode()\n\n\t\t\thttp.Redirect(w, r, loginURL.String(), http.StatusFound)\n\t\tcase connector.SAMLConnector:\n\t\t\taction, value, err := conn.POSTData(scopes, authReq.ID)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"creating SAML data\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Connector Login Error\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// TODO(ericchiang): Don't inline this.\n\t\t\tfmt.Fprintf(w, `<!DOCTYPE html>\n\t\t\t  <html lang=\"en\">\n\t\t\t  <head>\n\t\t\t    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n\t\t\t    <title>SAML login</title>\n\t\t\t  </head>\n\t\t\t  <body>\n\t\t\t    <form method=\"post\" action=\"%s\" >\n\t\t\t\t    <input type=\"hidden\" name=\"SAMLRequest\" value=\"%s\" />\n\t\t\t\t    <input type=\"hidden\" name=\"RelayState\" value=\"%s\" />\n\t\t\t    </form>\n\t\t\t\t<script>\n\t\t\t\t    document.forms[0].submit();\n\t\t\t\t</script>\n\t\t\t  </body>\n\t\t\t  </html>`, action, value, authReq.ID)\n\t\tdefault:\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist.\")\n\t\t}\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Unsupported request method.\")\n\t}\n}\n\nfunc (s *Server) handlePasswordLogin(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tauthID := r.URL.Query().Get(\"state\")\n\tif authID == \"\" {\n\t\ts.renderError(r, w, http.StatusBadRequest, \"User session error.\")\n\t\treturn\n\t}\n\n\tbackLink := r.URL.Query().Get(\"back\")\n\n\tauthReq, err := s.storage.GetAuthRequest(ctx, authID)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(r.Context(), \"invalid 'state' parameter provided\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist.\")\n\t\t\treturn\n\t\t}\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get auth request\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Database error.\")\n\t\treturn\n\t}\n\n\tconnID, err := url.PathUnescape(mux.Vars(r)[\"connector\"])\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to parse connector\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist\")\n\t\treturn\n\t} else if connID != \"\" && connID != authReq.ConnectorID {\n\t\ts.logger.ErrorContext(r.Context(), \"connector mismatch: password login triggered for different connector from authentication start\", \"start_connector_id\", authReq.ConnectorID, \"password_connector_id\", connID)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Requested resource does not exist.\")\n\t\treturn\n\t}\n\n\tconn, err := s.getConnector(ctx, authReq.ConnectorID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get connector\", \"connector_id\", authReq.ConnectorID, \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Connector failed to initialize.\")\n\t\treturn\n\t}\n\n\tpwConn, ok := conn.Connector.(connector.PasswordConnector)\n\tif !ok {\n\t\ts.logger.ErrorContext(r.Context(), \"expected password connector in handlePasswordLogin()\", \"password_connector\", pwConn)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Requested resource does not exist.\")\n\t\treturn\n\t}\n\n\trememberMe := s.rememberMeDefault()\n\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\tif err := s.templates.password(r, w, r.URL.String(), \"\", usernamePrompt(pwConn), false, backLink, rememberMe); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t\t}\n\tcase http.MethodPost:\n\t\tusername := r.FormValue(\"login\")\n\t\tpassword := r.FormValue(\"password\")\n\t\tscopes := parseScopes(authReq.Scopes)\n\n\t\tidentity, ok, err := pwConn.Login(r.Context(), scopes, username, password)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to login user\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, ErrMsgLoginError)\n\t\t\treturn\n\t\t}\n\t\tif !ok {\n\t\t\tif err := s.templates.password(r, w, r.URL.String(), username, usernamePrompt(pwConn), true, backLink, rememberMe); err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t\t\t}\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed login attempt: Invalid credentials.\", \"user\", username)\n\t\t\treturn\n\t\t}\n\t\tredirectURL, canSkipApproval, err := s.finalizeLogin(r.Context(), identity, authReq, conn.Connector)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to finalize login\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login error.\")\n\t\t\treturn\n\t\t}\n\n\t\t// Re-read auth request after finalizeLogin populated Claims.\n\t\tauthReq, err = s.storage.GetAuthRequest(ctx, authReq.ID)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get finalized auth request\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login error.\")\n\t\t\treturn\n\t\t}\n\n\t\trememberMe := r.FormValue(\"remember_me\") == \"on\"\n\t\tif err := s.createOrUpdateAuthSession(ctx, r, w, authReq, rememberMe); err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to create/update auth session\", \"err\", err)\n\t\t}\n\n\t\tif canSkipApproval {\n\t\t\t// authReq was already re-read after finalizeLogin above.\n\t\t\ts.sendCodeResponse(w, r, authReq)\n\t\t\treturn\n\t\t}\n\n\t\thttp.Redirect(w, r, redirectURL, http.StatusSeeOther)\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Unsupported request method.\")\n\t}\n}\n\nfunc (s *Server) handleConnectorCallback(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tvar authID string\n\tswitch r.Method {\n\tcase http.MethodGet: // OAuth2 callback\n\t\tif authID = r.URL.Query().Get(\"state\"); authID == \"\" {\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"User session error.\")\n\t\t\treturn\n\t\t}\n\tcase http.MethodPost: // SAML POST binding\n\t\tif authID = r.PostFormValue(\"RelayState\"); authID == \"\" {\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"User session error.\")\n\t\t\treturn\n\t\t}\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Method not supported\")\n\t\treturn\n\t}\n\n\tauthReq, err := s.storage.GetAuthRequest(ctx, authID)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(r.Context(), \"invalid 'state' parameter provided\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"Requested resource does not exist.\")\n\t\t\treturn\n\t\t}\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get auth request\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Database error.\")\n\t\treturn\n\t}\n\n\tconnID, err := url.PathUnescape(mux.Vars(r)[\"connector\"])\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get connector\", \"connector_id\", authReq.ConnectorID, \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Requested resource does not exist.\")\n\t\treturn\n\t} else if connID != \"\" && connID != authReq.ConnectorID {\n\t\ts.logger.ErrorContext(r.Context(), \"connector mismatch: callback triggered for different connector than authentication start\", \"authentication_start_connector_id\", authReq.ConnectorID, \"connector_id\", connID)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Requested resource does not exist.\")\n\t\treturn\n\t}\n\n\tconn, err := s.getConnector(ctx, authReq.ConnectorID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get connector\", \"connector_id\", authReq.ConnectorID, \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Requested resource does not exist.\")\n\t\treturn\n\t}\n\n\tvar identity connector.Identity\n\tswitch conn := conn.Connector.(type) {\n\tcase connector.CallbackConnector:\n\t\tif r.Method != http.MethodGet {\n\t\t\ts.logger.ErrorContext(r.Context(), \"SAML request mapped to OAuth2 connector\")\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"Invalid request\")\n\t\t\treturn\n\t\t}\n\t\tidentity, err = conn.HandleCallback(parseScopes(authReq.Scopes), authReq.ConnectorData, r)\n\tcase connector.SAMLConnector:\n\t\tif r.Method != http.MethodPost {\n\t\t\ts.logger.ErrorContext(r.Context(), \"OAuth2 request mapped to SAML connector\")\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"Invalid request\")\n\t\t\treturn\n\t\t}\n\t\tidentity, err = conn.HandlePOST(parseScopes(authReq.Scopes), r.PostFormValue(\"SAMLResponse\"), authReq.ID)\n\tdefault:\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Requested resource does not exist.\")\n\t\treturn\n\t}\n\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to authenticate\", \"err\", err)\n\t\tvar groupsErr *connector.UserNotInRequiredGroupsError\n\t\tif errors.As(err, &groupsErr) {\n\t\t\ts.renderError(r, w, http.StatusForbidden, ErrMsgNotInRequiredGroups)\n\t\t} else {\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, ErrMsgAuthenticationFailed)\n\t\t}\n\t\treturn\n\t}\n\n\tredirectURL, canSkipApproval, err := s.finalizeLogin(ctx, identity, authReq, conn.Connector)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to finalize login\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login error.\")\n\t\treturn\n\t}\n\n\t// Re-read auth request after finalizeLogin populated Claims.\n\tauthReq, err = s.storage.GetAuthRequest(ctx, authReq.ID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get finalized auth request\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login error.\")\n\t\treturn\n\t}\n\n\t// Connector callbacks don't render the remember_me checkbox, so we use the server default.\n\t// The password login handler reads r.FormValue(\"remember_me\") from the submitted form instead.\n\tif err := s.createOrUpdateAuthSession(ctx, r, w, authReq, s.sessionConfig != nil && s.sessionConfig.RememberMeCheckedByDefault); err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to create/update auth session\", \"err\", err)\n\t}\n\n\tif canSkipApproval {\n\t\t// authReq was already re-read after finalizeLogin above.\n\t\ts.sendCodeResponse(w, r, authReq)\n\t\treturn\n\t}\n\n\thttp.Redirect(w, r, redirectURL, http.StatusSeeOther)\n}\n\n// finalizeLogin associates the user's identity with the current AuthRequest, then returns\n// the approval page's path.\nfunc (s *Server) finalizeLogin(ctx context.Context, identity connector.Identity, authReq storage.AuthRequest, conn connector.Connector) (string, bool, error) {\n\tclaims := storage.Claims{\n\t\tUserID:            identity.UserID,\n\t\tUsername:          identity.Username,\n\t\tPreferredUsername: identity.PreferredUsername,\n\t\tEmail:             identity.Email,\n\t\tEmailVerified:     identity.EmailVerified,\n\t\tGroups:            identity.Groups,\n\t}\n\n\tupdater := func(a storage.AuthRequest) (storage.AuthRequest, error) {\n\t\ta.LoggedIn = true\n\t\ta.Claims = claims\n\t\ta.ConnectorData = identity.ConnectorData\n\t\ta.AuthTime = s.now()\n\t\treturn a, nil\n\t}\n\tif err := s.storage.UpdateAuthRequest(ctx, authReq.ID, updater); err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"failed to update auth request: %v\", err)\n\t}\n\n\temail := claims.Email\n\tif !claims.EmailVerified {\n\t\temail += \" (unverified)\"\n\t}\n\n\ts.logger.InfoContext(ctx, \"login successful\",\n\t\t\"connector_id\", authReq.ConnectorID, \"user_id\", claims.UserID,\n\t\t\"username\", claims.Username, \"preferred_username\", claims.PreferredUsername,\n\t\t\"email\", email, \"groups\", claims.Groups)\n\n\tofflineAccessRequested := false\n\tfor _, scope := range authReq.Scopes {\n\t\tif scope == scopeOfflineAccess {\n\t\t\tofflineAccessRequested = true\n\t\t\tbreak\n\t\t}\n\t}\n\t_, canRefresh := conn.(connector.RefreshConnector)\n\n\tif offlineAccessRequested && canRefresh {\n\t\t// Try to retrieve an existing OfflineSession object for the corresponding user.\n\t\tsession, err := s.storage.GetOfflineSessions(ctx, identity.UserID, authReq.ConnectorID)\n\t\tswitch {\n\t\tcase err != nil && err == storage.ErrNotFound:\n\t\t\tofflineSessions := storage.OfflineSessions{\n\t\t\t\tUserID:        identity.UserID,\n\t\t\t\tConnID:        authReq.ConnectorID,\n\t\t\t\tRefresh:       make(map[string]*storage.RefreshTokenRef),\n\t\t\t\tConnectorData: identity.ConnectorData,\n\t\t\t}\n\n\t\t\t// Create a new OfflineSession object for the user and add a reference object for\n\t\t\t// the newly received refreshtoken.\n\t\t\tif err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to create offline session\", \"err\", err)\n\t\t\t\treturn \"\", false, err\n\t\t\t}\n\t\tcase err == nil:\n\t\t\t// Update existing OfflineSession obj with new RefreshTokenRef.\n\t\t\tif err := s.storage.UpdateOfflineSessions(ctx, session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {\n\t\t\t\tif len(identity.ConnectorData) > 0 {\n\t\t\t\t\told.ConnectorData = identity.ConnectorData\n\t\t\t\t}\n\t\t\t\treturn old, nil\n\t\t\t}); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to update offline session\", \"err\", err)\n\t\t\t\treturn \"\", false, err\n\t\t\t}\n\t\tdefault:\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get offline session\", \"err\", err)\n\t\t\treturn \"\", false, err\n\t\t}\n\t}\n\n\t// Create or update UserIdentity to persist user claims across sessions.\n\tvar userIdentity *storage.UserIdentity\n\tif featureflags.SessionsEnabled.Enabled() {\n\t\tnow := s.now()\n\n\t\tui, err := s.storage.GetUserIdentity(ctx, identity.UserID, authReq.ConnectorID)\n\t\tswitch {\n\t\tcase err != nil && errors.Is(err, storage.ErrNotFound):\n\t\t\tui = storage.UserIdentity{\n\t\t\t\tUserID:      identity.UserID,\n\t\t\t\tConnectorID: authReq.ConnectorID,\n\t\t\t\tClaims:      claims,\n\t\t\t\tConsents:    make(map[string][]string),\n\t\t\t\tCreatedAt:   now,\n\t\t\t\tLastLogin:   now,\n\t\t\t}\n\t\t\tif err := s.storage.CreateUserIdentity(ctx, ui); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to create user identity\", \"err\", err)\n\t\t\t\treturn \"\", false, err\n\t\t\t}\n\t\tcase err == nil:\n\t\t\tif err := s.storage.UpdateUserIdentity(ctx, identity.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) {\n\t\t\t\told.Claims = claims\n\t\t\t\told.LastLogin = now\n\t\t\t\treturn old, nil\n\t\t\t}); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to update user identity\", \"err\", err)\n\t\t\t\treturn \"\", false, err\n\t\t\t}\n\t\t\t// Update the existing UserIdentity obj with new claims to use them later in the flow.\n\t\t\tui.Claims = claims\n\t\t\tui.LastLogin = now\n\t\tdefault:\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get user identity\", \"err\", err)\n\t\t\treturn \"\", false, err\n\t\t}\n\t\tuserIdentity = &ui\n\t}\n\n\t// an HMAC is used here to ensure that the request ID is unpredictable, ensuring that an attacker who intercepted the original\n\t// flow would be unable to poll for the result at the /approval endpoint\n\th := hmac.New(sha256.New, authReq.HMACKey)\n\th.Write([]byte(authReq.ID))\n\tmac := h.Sum(nil)\n\thmacParam := base64.RawURLEncoding.EncodeToString(mac)\n\n\t// Check if the client requires MFA.\n\tmfaChain, err := s.mfaChainForClient(ctx, authReq.ClientID, authReq.ConnectorID)\n\tif err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"failed to get MFA chain for client: %v\", err)\n\t}\n\tif len(mfaChain) > 0 {\n\t\t// Redirect to MFA verification starting with the first authenticator.\n\t\t// Each authenticator redirects to the next one in the chain upon success.\n\t\t// HMAC includes authenticatorID to prevent skipping steps by URL manipulation.\n\t\th.Reset()\n\t\th.Write([]byte(authReq.ID + \"|\" + mfaChain[0]))\n\t\tv := url.Values{}\n\t\tv.Set(\"req\", authReq.ID)\n\t\tv.Set(\"hmac\", base64.RawURLEncoding.EncodeToString(h.Sum(nil)))\n\t\tv.Set(\"authenticator\", mfaChain[0])\n\t\treturnURL := path.Join(s.issuerURL.Path, \"/mfa/verify\") + \"?\" + v.Encode()\n\t\treturn returnURL, false, nil\n\t}\n\n\t// No MFA required — mark as validated.\n\tif err := s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) {\n\t\ta.MFAValidated = true\n\t\treturn a, nil\n\t}); err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"failed to update auth request MFA status: %v\", err)\n\t}\n\n\t// Skip approval if globally configured.\n\tif s.skipApproval && !authReq.ForceApprovalPrompt {\n\t\treturn \"\", true, nil\n\t}\n\n\t// Skip approval if user already consented to the requested scopes for this client.\n\tif !authReq.ForceApprovalPrompt && userIdentity != nil {\n\t\tif scopesCoveredByConsent(userIdentity.Consents[authReq.ClientID], authReq.Scopes) {\n\t\t\treturn \"\", true, nil\n\t\t}\n\t}\n\n\treturnURL := path.Join(s.issuerURL.Path, \"/approval\") + \"?req=\" + authReq.ID + \"&hmac=\" + hmacParam\n\treturn returnURL, false, nil\n}\n\nfunc (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tmacEncoded := r.FormValue(\"hmac\")\n\tif macEncoded == \"\" {\n\t\ts.renderError(r, w, http.StatusUnauthorized, \"Unauthorized request\")\n\t\treturn\n\t}\n\tmac, err := base64.RawURLEncoding.DecodeString(macEncoded)\n\tif err != nil {\n\t\ts.renderError(r, w, http.StatusUnauthorized, \"Unauthorized request\")\n\t\treturn\n\t}\n\n\tauthReq, err := s.storage.GetAuthRequest(ctx, r.FormValue(\"req\"))\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"User session error.\")\n\t\t\treturn\n\t\t}\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get auth request\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Database error.\")\n\t\treturn\n\t}\n\tif !authReq.LoggedIn {\n\t\ts.logger.ErrorContext(r.Context(), \"auth request does not have an identity for approval\")\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login process not yet finalized.\")\n\t\treturn\n\t}\n\n\th := hmac.New(sha256.New, authReq.HMACKey)\n\tif !authReq.MFAValidated {\n\t\t// Check if MFA is actually required — if so, redirect to TOTP instead of blocking.\n\t\t// This handles the case where MFA was enabled after the auth flow started.\n\t\tmfaChain, err := s.mfaChainForClient(ctx, authReq.ClientID, authReq.ConnectorID)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get MFA chain\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\treturn\n\t\t}\n\t\tif len(mfaChain) > 0 {\n\t\t\th.Write([]byte(authReq.ID + \"|\" + mfaChain[0]))\n\t\t\tv := url.Values{}\n\t\t\tv.Set(\"req\", authReq.ID)\n\t\t\tv.Set(\"hmac\", base64.RawURLEncoding.EncodeToString(h.Sum(nil)))\n\t\t\tv.Set(\"authenticator\", mfaChain[0])\n\t\t\th.Reset()\n\t\t\ttotpURL := path.Join(s.issuerURL.Path, \"/mfa/verify\") + \"?\" + v.Encode()\n\t\t\thttp.Redirect(w, r, totpURL, http.StatusSeeOther)\n\t\t\treturn\n\t\t}\n\t\t// No MFA required but flag not set — allow through (backward compat).\n\t}\n\n\t// build expected hmac with secret key\n\th.Write([]byte(authReq.ID))\n\texpectedMAC := h.Sum(nil)\n\t// constant time comparison\n\tif !hmac.Equal(mac, expectedMAC) {\n\t\ts.renderError(r, w, http.StatusUnauthorized, \"Unauthorized request\")\n\t\treturn\n\t}\n\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\tclient, err := s.storage.GetClient(ctx, authReq.ClientID)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"Failed to get client\", \"client_id\", authReq.ClientID, \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Failed to retrieve client.\")\n\t\t\treturn\n\t\t}\n\t\tif err := s.templates.approval(r, w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t\t}\n\tcase http.MethodPost:\n\t\tif r.FormValue(\"approval\") != \"approve\" {\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Approval rejected.\")\n\t\t\treturn\n\t\t}\n\t\t// Persist user-approved scopes as consent for this client.\n\t\tif featureflags.SessionsEnabled.Enabled() {\n\t\t\tif err := s.storage.UpdateUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) {\n\t\t\t\tif old.Consents == nil {\n\t\t\t\t\told.Consents = make(map[string][]string)\n\t\t\t\t}\n\t\t\t\told.Consents[authReq.ClientID] = authReq.Scopes\n\t\t\t\treturn old, nil\n\t\t\t}); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to update user identity consents\", \"err\", err)\n\t\t\t}\n\t\t}\n\t\ts.sendCodeResponse(w, r, authReq)\n\t}\n}\n\nfunc (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest) {\n\ts.updateSessionTokenIssuedAt(r, authReq.ClientID)\n\n\tctx := r.Context()\n\tif s.now().After(authReq.Expiry) {\n\t\ts.renderError(r, w, http.StatusBadRequest, \"User session has expired.\")\n\t\treturn\n\t}\n\n\tif err := s.storage.DeleteAuthRequest(ctx, authReq.ID); err != nil {\n\t\tif err != storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(r.Context(), \"Failed to delete authorization request\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t} else {\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"User session error.\")\n\t\t}\n\t\treturn\n\t}\n\tu, err := url.Parse(authReq.RedirectURI)\n\tif err != nil {\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Invalid redirect URI.\")\n\t\treturn\n\t}\n\n\tvar (\n\t\t// Was the initial request using the implicit or hybrid flow instead of\n\t\t// the \"normal\" code flow?\n\t\timplicitOrHybrid = false\n\n\t\t// Only present in hybrid or code flow. code.ID == \"\" if this is not set.\n\t\tcode storage.AuthCode\n\n\t\t// ID token returned immediately if the response_type includes \"id_token\".\n\t\t// Only valid for implicit and hybrid flows.\n\t\tidToken       string\n\t\tidTokenExpiry time.Time\n\n\t\t// Access token\n\t\taccessToken string\n\t)\n\n\tfor _, responseType := range authReq.ResponseTypes {\n\t\tswitch responseType {\n\t\tcase responseTypeCode:\n\t\t\tcode = storage.AuthCode{\n\t\t\t\tID:            storage.NewID(),\n\t\t\t\tClientID:      authReq.ClientID,\n\t\t\t\tConnectorID:   authReq.ConnectorID,\n\t\t\t\tNonce:         authReq.Nonce,\n\t\t\t\tScopes:        authReq.Scopes,\n\t\t\t\tClaims:        authReq.Claims,\n\t\t\t\tExpiry:        s.now().Add(time.Minute * 30),\n\t\t\t\tRedirectURI:   authReq.RedirectURI,\n\t\t\t\tConnectorData: authReq.ConnectorData,\n\t\t\t\tPKCE:          authReq.PKCE,\n\t\t\t\tAuthTime:      authReq.AuthTime,\n\t\t\t}\n\t\t\tif err := s.storage.CreateAuthCode(ctx, code); err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"Failed to create auth code\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Implicit and hybrid flows that try to use the OOB redirect URI are\n\t\t\t// rejected earlier. If we got here we're using the code flow.\n\t\t\tif authReq.RedirectURI == redirectURIOOB {\n\t\t\t\tif err := s.templates.oob(r, w, code.ID); err != nil {\n\t\t\t\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\tcase responseTypeToken:\n\t\t\timplicitOrHybrid = true\n\t\t\tvar err error\n\n\t\t\taccessToken, _, err = s.newAccessToken(r.Context(), authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, authReq.ConnectorID, authReq.AuthTime)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to create new access token\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase responseTypeIDToken:\n\t\t\timplicitOrHybrid = true\n\t\t\tvar err error\n\n\t\t\tidToken, idTokenExpiry, err = s.newIDToken(r.Context(), authReq.ClientID, authReq.Claims, authReq.Scopes, authReq.Nonce, accessToken, code.ID, authReq.ConnectorID, authReq.AuthTime)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to create ID token\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tif implicitOrHybrid {\n\t\tv := url.Values{}\n\t\tif accessToken != \"\" {\n\t\t\tv.Set(\"access_token\", accessToken)\n\t\t\tv.Set(\"token_type\", \"bearer\")\n\t\t\t// The hybrid flow with \"code token\" or \"code id_token token\" doesn't return an\n\t\t\t// \"expires_in\" value. If \"code\" wasn't provided, indicating the implicit flow,\n\t\t\t// don't add it.\n\t\t\t//\n\t\t\t// https://openid.net/specs/openid-connect-core-1_0.html#HybridAuthResponse\n\t\t\tif code.ID == \"\" {\n\t\t\t\tv.Set(\"expires_in\", strconv.Itoa(int(idTokenExpiry.Sub(s.now()).Seconds())))\n\t\t\t}\n\t\t}\n\t\tv.Set(\"state\", authReq.State)\n\t\tif idToken != \"\" {\n\t\t\tv.Set(\"id_token\", idToken)\n\t\t}\n\t\tif code.ID != \"\" {\n\t\t\tv.Set(\"code\", code.ID)\n\t\t}\n\n\t\t// Implicit and hybrid flows return their values as part of the fragment.\n\t\t//\n\t\t//   HTTP/1.1 303 See Other\n\t\t//   Location: https://client.example.org/cb#\n\t\t//     access_token=SlAV32hkKG\n\t\t//     &token_type=bearer\n\t\t//     &id_token=eyJ0 ... NiJ9.eyJ1c ... I6IjIifX0.DeWt4Qu ... ZXso\n\t\t//     &expires_in=3600\n\t\t//     &state=af0ifjsldkj\n\t\t//\n\t\tu.Fragment = v.Encode()\n\t} else {\n\t\t// The code flow add values to the URL query.\n\t\t//\n\t\t//   HTTP/1.1 303 See Other\n\t\t//   Location: https://client.example.org/cb?\n\t\t//     code=SplxlOBeZQQYbYS6WxSbIA\n\t\t//     &state=af0ifjsldkj\n\t\t//\n\t\tq := u.Query()\n\t\tq.Set(\"code\", code.ID)\n\t\tq.Set(\"state\", authReq.State)\n\t\tu.RawQuery = q.Encode()\n\t}\n\n\thttp.Redirect(w, r, u.String(), http.StatusSeeOther)\n}\n\n// scopesCoveredByConsent checks whether the approved scopes cover all requested scopes.\n// The openid scope is excluded from the comparison as it is a technical scope\n// that does not require user consent.\nfunc scopesCoveredByConsent(approved, requested []string) bool {\n\tapprovedSet := make(map[string]struct{}, len(approved))\n\tfor _, s := range approved {\n\t\tapprovedSet[s] = struct{}{}\n\t}\n\n\tfor _, scope := range requested {\n\t\tif scope == scopeOpenID {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := approvedSet[scope]; !ok {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc (s *Server) withClientFromStorage(w http.ResponseWriter, r *http.Request, handler func(http.ResponseWriter, *http.Request, storage.Client)) {\n\tctx := r.Context()\n\tclientID, clientSecret, ok := r.BasicAuth()\n\tif ok {\n\t\tvar err error\n\t\tif clientID, err = url.QueryUnescape(clientID); err != nil {\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"client_id improperly encoded\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t\tif clientSecret, err = url.QueryUnescape(clientSecret); err != nil {\n\t\t\ts.tokenErrHelper(w, errInvalidRequest, \"client_secret improperly encoded\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\t} else {\n\t\tclientID = r.PostFormValue(\"client_id\")\n\t\tclientSecret = r.PostFormValue(\"client_secret\")\n\t}\n\n\tclient, err := s.storage.GetClient(ctx, clientID)\n\tif err != nil {\n\t\tif err != storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get client\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t} else {\n\t\t\ts.tokenErrHelper(w, errInvalidClient, \"Invalid client credentials.\", http.StatusUnauthorized)\n\t\t}\n\t\treturn\n\t}\n\n\tif subtle.ConstantTimeCompare([]byte(client.Secret), []byte(clientSecret)) != 1 {\n\t\tif clientSecret == \"\" {\n\t\t\ts.logger.InfoContext(r.Context(), \"missing client_secret on token request\", \"client_id\", client.ID)\n\t\t} else {\n\t\t\ts.logger.InfoContext(r.Context(), \"invalid client_secret on token request\", \"client_id\", client.ID)\n\t\t}\n\t\ts.tokenErrHelper(w, errInvalidClient, \"Invalid client credentials.\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\thandler(w, r, client)\n}\n\nfunc (s *Server) handleToken(w http.ResponseWriter, r *http.Request) {\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tif r.Method != http.MethodPost {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"method not allowed\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\terr := r.ParseForm()\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"could not parse request body\", \"err\", err)\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tgrantType := r.PostFormValue(\"grant_type\")\n\tif !contains(s.supportedGrantTypes, grantType) {\n\t\ts.logger.ErrorContext(r.Context(), \"unsupported grant type\", \"grant_type\", grantType)\n\t\ts.tokenErrHelper(w, errUnsupportedGrantType, \"\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tswitch grantType {\n\tcase grantTypeDeviceCode:\n\t\ts.handleDeviceToken(w, r)\n\tcase grantTypeAuthorizationCode:\n\t\ts.withClientFromStorage(w, r, s.handleAuthCode)\n\tcase grantTypeRefreshToken:\n\t\ts.withClientFromStorage(w, r, s.handleRefreshToken)\n\tcase grantTypePassword:\n\t\ts.withClientFromStorage(w, r, s.handlePasswordGrant)\n\tcase grantTypeTokenExchange:\n\t\ts.withClientFromStorage(w, r, s.handleTokenExchange)\n\tcase grantTypeClientCredentials:\n\t\ts.withClientFromStorage(w, r, s.handleClientCredentialsGrant)\n\tdefault:\n\t\ts.tokenErrHelper(w, errUnsupportedGrantType, \"\", http.StatusBadRequest)\n\t}\n}\n\nfunc (s *Server) calculateCodeChallenge(codeVerifier, codeChallengeMethod string) (string, error) {\n\tswitch codeChallengeMethod {\n\tcase codeChallengeMethodPlain:\n\t\treturn codeVerifier, nil\n\tcase codeChallengeMethodS256:\n\t\tshaSum := sha256.Sum256([]byte(codeVerifier))\n\t\treturn base64.RawURLEncoding.EncodeToString(shaSum[:]), nil\n\tdefault:\n\t\treturn \"\", fmt.Errorf(\"unknown challenge method (%v)\", codeChallengeMethod)\n\t}\n}\n\n// handle an access token request https://tools.ietf.org/html/rfc6749#section-4.1.3\nfunc (s *Server) handleAuthCode(w http.ResponseWriter, r *http.Request, client storage.Client) {\n\tctx := r.Context()\n\tcode := r.PostFormValue(\"code\")\n\tredirectURI := r.PostFormValue(\"redirect_uri\")\n\n\tif code == \"\" {\n\t\ts.tokenErrHelper(w, errInvalidRequest, `Required param: code.`, http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tauthCode, err := s.storage.GetAuthCode(ctx, code)\n\tif err != nil || s.now().After(authCode.Expiry) || authCode.ClientID != client.ID {\n\t\tif err != storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get auth code\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t} else {\n\t\t\ts.tokenErrHelper(w, errInvalidGrant, \"Invalid or expired code parameter.\", http.StatusBadRequest)\n\t\t}\n\t\treturn\n\t}\n\n\t// RFC 7636 (PKCE)\n\tcodeChallengeFromStorage := authCode.PKCE.CodeChallenge\n\tprovidedCodeVerifier := r.PostFormValue(\"code_verifier\")\n\n\tswitch {\n\tcase providedCodeVerifier != \"\" && codeChallengeFromStorage != \"\":\n\t\tcalculatedCodeChallenge, err := s.calculateCodeChallenge(providedCodeVerifier, authCode.PKCE.CodeChallengeMethod)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to calculate code challenge\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t\tif codeChallengeFromStorage != calculatedCodeChallenge {\n\t\t\ts.tokenErrHelper(w, errInvalidGrant, \"Invalid code_verifier.\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\tcase providedCodeVerifier != \"\":\n\t\t// Received no code_challenge on /auth, but a code_verifier on /token\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"No PKCE flow started. Cannot check code_verifier.\", http.StatusBadRequest)\n\t\treturn\n\tcase codeChallengeFromStorage != \"\":\n\t\t// Received PKCE request on /auth, but no code_verifier on /token\n\t\ts.tokenErrHelper(w, errInvalidGrant, \"Expecting parameter code_verifier in PKCE flow.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif authCode.RedirectURI != redirectURI {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"redirect_uri did not match URI from initial request.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\ttokenResponse, err := s.exchangeAuthCode(ctx, w, authCode, client)\n\tif err != nil {\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\ts.writeAccessToken(w, tokenResponse)\n}\n\nfunc (s *Server) exchangeAuthCode(ctx context.Context, w http.ResponseWriter, authCode storage.AuthCode, client storage.Client) (*accessTokenResponse, error) {\n\taccessToken, _, err := s.newAccessToken(ctx, client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, authCode.ConnectorID, authCode.AuthTime)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to create new access token\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn nil, err\n\t}\n\n\tidToken, expiry, err := s.newIDToken(ctx, client.ID, authCode.Claims, authCode.Scopes, authCode.Nonce, accessToken, authCode.ID, authCode.ConnectorID, authCode.AuthTime)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to create ID token\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn nil, err\n\t}\n\n\tif err := s.storage.DeleteAuthCode(ctx, authCode.ID); err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to delete auth code\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn nil, err\n\t}\n\n\treqRefresh := func() bool {\n\t\t// Determine whether to issue a refresh token. A refresh token is only\n\t\t// issued when all of the following are true:\n\t\t//   1. The connector implements RefreshConnector.\n\t\t//   2. The connector's grantTypes config allows refresh_token.\n\t\t//   3. The client requested the offline_access scope.\n\t\t//\n\t\t// When any condition is not met, the refresh token is silently omitted\n\t\t// rather than returning an error. This matches the OAuth2 spec: the\n\t\t// server is never required to issue a refresh token (RFC 6749 §1.5).\n\t\t// https://datatracker.ietf.org/doc/html/rfc6749#section-1.5\n\t\tconn, err := s.getConnector(ctx, authCode.ConnectorID)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"connector not found\", \"connector_id\", authCode.ConnectorID, \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\treturn false\n\t\t}\n\n\t\t_, ok := conn.Connector.(connector.RefreshConnector)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tif !GrantTypeAllowed(conn.GrantTypes, grantTypeRefreshToken) {\n\t\t\treturn false\n\t\t}\n\n\t\tfor _, scope := range authCode.Scopes {\n\t\t\tif scope == scopeOfflineAccess {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}()\n\tvar refreshToken string\n\tif reqRefresh {\n\t\trefresh := storage.RefreshToken{\n\t\t\tID:            storage.NewID(),\n\t\t\tToken:         storage.NewID(),\n\t\t\tClientID:      authCode.ClientID,\n\t\t\tConnectorID:   authCode.ConnectorID,\n\t\t\tScopes:        authCode.Scopes,\n\t\t\tClaims:        authCode.Claims,\n\t\t\tNonce:         authCode.Nonce,\n\t\t\tConnectorData: authCode.ConnectorData,\n\t\t\tCreatedAt:     s.now(),\n\t\t\tLastUsed:      s.now(),\n\t\t}\n\t\ttoken := &internal.RefreshToken{\n\t\t\tRefreshId: refresh.ID,\n\t\t\tToken:     refresh.Token,\n\t\t}\n\t\tif refreshToken, err = internal.Marshal(token); err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to marshal refresh token\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\treturn nil, err\n\t\t}\n\n\t\tif err := s.storage.CreateRefresh(ctx, refresh); err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to create refresh token\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// deleteToken determines if we need to delete the newly created refresh token\n\t\t// due to a failure in updating/creating the OfflineSession object for the\n\t\t// corresponding user.\n\t\tvar deleteToken bool\n\t\tdefer func() {\n\t\t\tif deleteToken {\n\t\t\t\t// Delete newly created refresh token from storage.\n\t\t\t\tif err := s.storage.DeleteRefresh(ctx, refresh.ID); err != nil {\n\t\t\t\t\ts.logger.ErrorContext(ctx, \"failed to delete refresh token\", \"err\", err)\n\t\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\ttokenRef := storage.RefreshTokenRef{\n\t\t\tID:        refresh.ID,\n\t\t\tClientID:  refresh.ClientID,\n\t\t\tCreatedAt: refresh.CreatedAt,\n\t\t\tLastUsed:  refresh.LastUsed,\n\t\t}\n\n\t\t// Try to retrieve an existing OfflineSession object for the corresponding user.\n\t\tif session, err := s.storage.GetOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID); err != nil {\n\t\t\tif err != storage.ErrNotFound {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to get offline session\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\tdeleteToken = true\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tofflineSessions := storage.OfflineSessions{\n\t\t\t\tUserID:        refresh.Claims.UserID,\n\t\t\t\tConnID:        refresh.ConnectorID,\n\t\t\t\tRefresh:       make(map[string]*storage.RefreshTokenRef),\n\t\t\t\tConnectorData: refresh.ConnectorData,\n\t\t\t}\n\t\t\tofflineSessions.Refresh[tokenRef.ClientID] = &tokenRef\n\n\t\t\t// Create a new OfflineSession object for the user and add a reference object for\n\t\t\t// the newly received refreshtoken.\n\t\t\tif err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to create offline session\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\tdeleteToken = true\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\tif oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok {\n\t\t\t\t// Delete old refresh token from storage.\n\t\t\t\tif err := s.storage.DeleteRefresh(ctx, oldTokenRef.ID); err != nil && err != storage.ErrNotFound {\n\t\t\t\t\ts.logger.ErrorContext(ctx, \"failed to delete refresh token\", \"err\", err)\n\t\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\t\tdeleteToken = true\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update existing OfflineSession obj with new RefreshTokenRef.\n\t\t\tif err := s.storage.UpdateOfflineSessions(ctx, session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {\n\t\t\t\told.Refresh[tokenRef.ClientID] = &tokenRef\n\t\t\t\tif len(refresh.ConnectorData) > 0 {\n\t\t\t\t\told.ConnectorData = refresh.ConnectorData\n\t\t\t\t}\n\t\t\t\treturn old, nil\n\t\t\t}); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to update offline session\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\tdeleteToken = true\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\treturn s.toAccessTokenResponse(idToken, accessToken, refreshToken, expiry), nil\n}\n\nfunc (s *Server) handleUserInfo(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\tconst prefix = \"Bearer \"\n\n\tauth := r.Header.Get(\"authorization\")\n\tif len(auth) < len(prefix) || !strings.EqualFold(prefix, auth[:len(prefix)]) {\n\t\tw.Header().Set(\"WWW-Authenticate\", \"Bearer\")\n\t\ts.tokenErrHelper(w, errAccessDenied, \"Invalid bearer token.\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\trawIDToken := auth[len(prefix):]\n\n\tverifier := oidc.NewVerifier(s.issuerURL.String(), &signerKeySet{s.signer}, &oidc.Config{SkipClientIDCheck: true})\n\tidToken, err := verifier.Verify(ctx, rawIDToken)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to verify ID token\", \"err\", err)\n\t\ts.tokenErrHelper(w, errAccessDenied, \"Invalid bearer token.\", http.StatusForbidden)\n\t\treturn\n\t}\n\n\tvar claims json.RawMessage\n\tif err := idToken.Claims(&claims); err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to decode ID token claims\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(claims)\n}\n\nfunc (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, client storage.Client) {\n\tctx := r.Context()\n\t// Parse the fields\n\tif err := r.ParseForm(); err != nil {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Couldn't parse data\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tq := r.Form\n\n\tnonce := q.Get(\"nonce\")\n\t// Some clients, like the old go-oidc, provide extra whitespace. Tolerate this.\n\tscopes := strings.Fields(q.Get(\"scope\"))\n\n\t// Parse the scopes if they are passed\n\tvar (\n\t\tunrecognized  []string\n\t\tinvalidScopes []string\n\t)\n\thasOpenIDScope := false\n\tfor _, scope := range scopes {\n\t\tswitch scope {\n\t\tcase scopeOpenID:\n\t\t\thasOpenIDScope = true\n\t\tcase scopeOfflineAccess, scopeEmail, scopeProfile, scopeGroups, scopeFederatedID:\n\t\tdefault:\n\t\t\tpeerID, ok := parseCrossClientScope(scope)\n\t\t\tif !ok {\n\t\t\t\tunrecognized = append(unrecognized, scope)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tisTrusted, err := s.validateCrossClientTrust(ctx, client.ID, peerID)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"error validating cross client trust\", \"client_id\", client.ID, \"peer_id\", peerID, \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errInvalidClient, \"Error validating cross client trust.\", http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !isTrusted {\n\t\t\t\tinvalidScopes = append(invalidScopes, scope)\n\t\t\t}\n\t\t}\n\t}\n\tif !hasOpenIDScope {\n\t\ts.tokenErrHelper(w, errInvalidRequest, `Missing required scope(s) [\"openid\"].`, http.StatusBadRequest)\n\t\treturn\n\t}\n\tif len(unrecognized) > 0 {\n\t\ts.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf(\"Unrecognized scope(s) %q\", unrecognized), http.StatusBadRequest)\n\t\treturn\n\t}\n\tif len(invalidScopes) > 0 {\n\t\ts.tokenErrHelper(w, errInvalidRequest, fmt.Sprintf(\"Client can't request scope(s) %q\", invalidScopes), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Which connector\n\tconnID := s.passwordConnector\n\tconn, err := s.getConnector(ctx, connID)\n\tif err != nil {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Requested connector does not exist.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif !GrantTypeAllowed(conn.GrantTypes, grantTypePassword) {\n\t\ts.logger.ErrorContext(r.Context(), \"connector does not allow password grant\", \"connector_id\", connID)\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Requested connector does not support password grant.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tpasswordConnector, ok := conn.Connector.(connector.PasswordConnector)\n\tif !ok {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Requested password connector does not correct type.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Login\n\tusername := q.Get(\"username\")\n\tpassword := q.Get(\"password\")\n\tidentity, ok, err := passwordConnector.Login(ctx, parseScopes(scopes), username, password)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to login user\", \"err\", err)\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Could not login user\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif !ok {\n\t\ts.tokenErrHelper(w, errAccessDenied, \"Invalid username or password\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\t// Build the claims to send the id token\n\tclaims := storage.Claims{\n\t\tUserID:            identity.UserID,\n\t\tUsername:          identity.Username,\n\t\tPreferredUsername: identity.PreferredUsername,\n\t\tEmail:             identity.Email,\n\t\tEmailVerified:     identity.EmailVerified,\n\t\tGroups:            identity.Groups,\n\t}\n\n\taccessToken, _, err := s.newAccessToken(ctx, client.ID, claims, scopes, nonce, connID, time.Time{})\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"password grant failed to create new access token\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tidToken, expiry, err := s.newIDToken(ctx, client.ID, claims, scopes, nonce, accessToken, \"\", connID, time.Time{})\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"password grant failed to create new ID token\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\treqRefresh := func() bool {\n\t\t// Same logic as in exchangeAuthCode: silently omit refresh token\n\t\t// when the connector doesn't support it or grantTypes forbids it.\n\t\t// See RFC 6749 §1.5 — refresh tokens are never mandatory.\n\t\t// https://datatracker.ietf.org/doc/html/rfc6749#section-1.5\n\t\tif _, ok := conn.Connector.(connector.RefreshConnector); !ok {\n\t\t\treturn false\n\t\t}\n\n\t\tif !GrantTypeAllowed(conn.GrantTypes, grantTypeRefreshToken) {\n\t\t\treturn false\n\t\t}\n\n\t\tfor _, scope := range scopes {\n\t\t\tif scope == scopeOfflineAccess {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}()\n\tvar refreshToken string\n\tif reqRefresh {\n\t\trefresh := storage.RefreshToken{\n\t\t\tID:          storage.NewID(),\n\t\t\tToken:       storage.NewID(),\n\t\t\tClientID:    client.ID,\n\t\t\tConnectorID: connID,\n\t\t\tScopes:      scopes,\n\t\t\tClaims:      claims,\n\t\t\tNonce:       nonce,\n\t\t\t// ConnectorData: authCode.ConnectorData,\n\t\t\tCreatedAt: s.now(),\n\t\t\tLastUsed:  s.now(),\n\t\t}\n\t\ttoken := &internal.RefreshToken{\n\t\t\tRefreshId: refresh.ID,\n\t\t\tToken:     refresh.Token,\n\t\t}\n\t\tif refreshToken, err = internal.Marshal(token); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to marshal refresh token\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\tif err := s.storage.CreateRefresh(ctx, refresh); err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to create refresh token\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\t// deleteToken determines if we need to delete the newly created refresh token\n\t\t// due to a failure in updating/creating the OfflineSession object for the\n\t\t// corresponding user.\n\t\tvar deleteToken bool\n\t\tdefer func() {\n\t\t\tif deleteToken {\n\t\t\t\t// Delete newly created refresh token from storage.\n\t\t\t\tif err := s.storage.DeleteRefresh(ctx, refresh.ID); err != nil {\n\t\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to delete refresh token\", \"err\", err)\n\t\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\ttokenRef := storage.RefreshTokenRef{\n\t\t\tID:        refresh.ID,\n\t\t\tClientID:  refresh.ClientID,\n\t\t\tCreatedAt: refresh.CreatedAt,\n\t\t\tLastUsed:  refresh.LastUsed,\n\t\t}\n\n\t\t// Try to retrieve an existing OfflineSession object for the corresponding user.\n\t\tif session, err := s.storage.GetOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID); err != nil {\n\t\t\tif err != storage.ErrNotFound {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get offline session\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\tdeleteToken = true\n\t\t\t\treturn\n\t\t\t}\n\t\t\tofflineSessions := storage.OfflineSessions{\n\t\t\t\tUserID:        refresh.Claims.UserID,\n\t\t\t\tConnID:        refresh.ConnectorID,\n\t\t\t\tRefresh:       make(map[string]*storage.RefreshTokenRef),\n\t\t\t\tConnectorData: identity.ConnectorData,\n\t\t\t}\n\t\t\tofflineSessions.Refresh[tokenRef.ClientID] = &tokenRef\n\n\t\t\t// Create a new OfflineSession object for the user and add a reference object for\n\t\t\t// the newly received refreshtoken.\n\t\t\tif err := s.storage.CreateOfflineSessions(ctx, offlineSessions); err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to create offline session\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\tdeleteToken = true\n\t\t\t\treturn\n\t\t\t}\n\t\t} else {\n\t\t\tif oldTokenRef, ok := session.Refresh[tokenRef.ClientID]; ok {\n\t\t\t\t// Delete old refresh token from storage.\n\t\t\t\tif err := s.storage.DeleteRefresh(ctx, oldTokenRef.ID); err != nil {\n\t\t\t\t\tif err == storage.ErrNotFound {\n\t\t\t\t\t\ts.logger.Warn(\"database inconsistent, refresh token missing\", \"token_id\", oldTokenRef.ID)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to delete refresh token\", \"err\", err)\n\t\t\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\t\t\tdeleteToken = true\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Update existing OfflineSession obj with new RefreshTokenRef.\n\t\t\tif err := s.storage.UpdateOfflineSessions(ctx, session.UserID, session.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {\n\t\t\t\told.Refresh[tokenRef.ClientID] = &tokenRef\n\t\t\t\told.ConnectorData = identity.ConnectorData\n\t\t\t\treturn old, nil\n\t\t\t}); err != nil {\n\t\t\t\ts.logger.ErrorContext(r.Context(), \"failed to update offline session\", \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\t\tdeleteToken = true\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\n\tresp := s.toAccessTokenResponse(idToken, accessToken, refreshToken, expiry)\n\ts.writeAccessToken(w, resp)\n}\n\nfunc (s *Server) handleTokenExchange(w http.ResponseWriter, r *http.Request, client storage.Client) {\n\tctx := r.Context()\n\n\tif err := r.ParseForm(); err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"could not parse request body\", \"err\", err)\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tq := r.Form\n\n\tscopes := strings.Fields(q.Get(\"scope\"))            // OPTIONAL, map to issued token scope\n\trequestedTokenType := q.Get(\"requested_token_type\") // OPTIONAL, default to access token\n\tif requestedTokenType == \"\" {\n\t\trequestedTokenType = tokenTypeAccess\n\t}\n\tsubjectToken := q.Get(\"subject_token\")          // REQUIRED\n\tsubjectTokenType := q.Get(\"subject_token_type\") // REQUIRED\n\tconnID := q.Get(\"connector_id\")                 // REQUIRED, not in RFC\n\n\tswitch subjectTokenType {\n\tcase tokenTypeID, tokenTypeAccess: // ok, continue\n\tdefault:\n\t\ts.tokenErrHelper(w, errRequestNotSupported, \"Invalid subject_token_type.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif subjectToken == \"\" {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Missing subject_token\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tconn, err := s.getConnector(ctx, connID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get connector\", \"err\", err)\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Requested connector does not exist.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif !GrantTypeAllowed(conn.GrantTypes, grantTypeTokenExchange) {\n\t\ts.logger.ErrorContext(r.Context(), \"connector does not allow token exchange\", \"connector_id\", connID)\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Requested connector does not support token exchange.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tteConn, ok := conn.Connector.(connector.TokenIdentityConnector)\n\tif !ok {\n\t\ts.logger.ErrorContext(r.Context(), \"connector doesn't implement token exchange\", \"connector_id\", connID)\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Requested connector does not exist.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tidentity, err := teConn.TokenIdentity(ctx, subjectTokenType, subjectToken)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to verify subject token\", \"err\", err)\n\t\ts.tokenErrHelper(w, errAccessDenied, \"\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tclaims := storage.Claims{\n\t\tUserID:            identity.UserID,\n\t\tUsername:          identity.Username,\n\t\tPreferredUsername: identity.PreferredUsername,\n\t\tEmail:             identity.Email,\n\t\tEmailVerified:     identity.EmailVerified,\n\t\tGroups:            identity.Groups,\n\t}\n\tresp := accessTokenResponse{\n\t\tIssuedTokenType: requestedTokenType,\n\t\tTokenType:       \"bearer\",\n\t}\n\tvar expiry time.Time\n\tswitch requestedTokenType {\n\tcase tokenTypeID:\n\t\tresp.AccessToken, expiry, err = s.newIDToken(r.Context(), client.ID, claims, scopes, \"\", \"\", \"\", connID, time.Time{})\n\tcase tokenTypeAccess:\n\t\tresp.AccessToken, expiry, err = s.newAccessToken(r.Context(), client.ID, claims, scopes, \"\", connID, time.Time{})\n\tdefault:\n\t\ts.tokenErrHelper(w, errRequestNotSupported, \"Invalid requested_token_type.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"token exchange failed to create new token\", \"requested_token_type\", requestedTokenType, \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tresp.ExpiresIn = int(time.Until(expiry).Seconds())\n\n\t// Token response must include cache headers https://tools.ietf.org/html/rfc6749#section-5.1\n\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\tw.Header().Set(\"Pragma\", \"no-cache\")\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(resp)\n}\n\nfunc (s *Server) handleClientCredentialsGrant(w http.ResponseWriter, r *http.Request, client storage.Client) {\n\tctx := r.Context()\n\n\t// client_credentials requires a confidential client.\n\tif client.Public {\n\t\ts.tokenErrHelper(w, errUnauthorizedClient, \"Public clients cannot use client_credentials grant.\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Parse scopes from request.\n\tif err := r.ParseForm(); err != nil {\n\t\ts.tokenErrHelper(w, errInvalidRequest, \"Couldn't parse data\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tscopes := strings.Fields(r.Form.Get(\"scope\"))\n\n\t// Validate scopes.\n\tvar (\n\t\tunrecognized  []string\n\t\tinvalidScopes []string\n\t)\n\thasOpenIDScope := false\n\tfor _, scope := range scopes {\n\t\tswitch scope {\n\t\tcase scopeOpenID:\n\t\t\thasOpenIDScope = true\n\t\tcase scopeEmail, scopeProfile, scopeGroups:\n\t\t\t// allowed\n\t\tcase scopeOfflineAccess:\n\t\t\ts.tokenErrHelper(w, errInvalidScope, \"client_credentials grant does not support offline_access scope.\", http.StatusBadRequest)\n\t\t\treturn\n\t\tcase scopeFederatedID:\n\t\t\ts.tokenErrHelper(w, errInvalidScope, \"client_credentials grant does not support federated:id scope.\", http.StatusBadRequest)\n\t\t\treturn\n\t\tdefault:\n\t\t\tpeerID, ok := parseCrossClientScope(scope)\n\t\t\tif !ok {\n\t\t\t\tunrecognized = append(unrecognized, scope)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tisTrusted, err := s.validateCrossClientTrust(ctx, client.ID, peerID)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"error validating cross client trust\", \"client_id\", client.ID, \"peer_id\", peerID, \"err\", err)\n\t\t\t\ts.tokenErrHelper(w, errInvalidClient, \"Error validating cross client trust.\", http.StatusBadRequest)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !isTrusted {\n\t\t\t\tinvalidScopes = append(invalidScopes, scope)\n\t\t\t}\n\t\t}\n\t}\n\tif len(unrecognized) > 0 {\n\t\ts.tokenErrHelper(w, errInvalidScope, fmt.Sprintf(\"Unrecognized scope(s) %q\", unrecognized), http.StatusBadRequest)\n\t\treturn\n\t}\n\tif len(invalidScopes) > 0 {\n\t\ts.tokenErrHelper(w, errInvalidScope, fmt.Sprintf(\"Client can't request scope(s) %q\", invalidScopes), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\t// Build claims from the client itself — no user involved.\n\tclaims := storage.Claims{\n\t\tUserID: client.ID,\n\t}\n\n\t// Only populate Username/PreferredUsername when the profile scope is requested.\n\tfor _, scope := range scopes {\n\t\tif scope == scopeProfile {\n\t\t\tclaims.Username = client.Name\n\t\t\tclaims.PreferredUsername = client.Name\n\t\t\tbreak\n\t\t}\n\t}\n\n\tnonce := r.Form.Get(\"nonce\")\n\n\t// Empty connector ID is unique for cluster credentials grant\n\t// Creating connectors with an empty ID with the config and API is prohibited\n\tconnID := \"\"\n\n\taccessToken, expiry, err := s.newAccessToken(ctx, client.ID, claims, scopes, nonce, connID, time.Time{})\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"client_credentials grant failed to create new access token\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tvar idToken string\n\tif hasOpenIDScope {\n\t\tidToken, expiry, err = s.newIDToken(ctx, client.ID, claims, scopes, nonce, accessToken, \"\", connID, time.Time{})\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"client_credentials grant failed to create new ID token\", \"err\", err)\n\t\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\t}\n\n\tresp := s.toAccessTokenResponse(idToken, accessToken, \"\", expiry)\n\ts.writeAccessToken(w, resp)\n}\n\ntype accessTokenResponse struct {\n\tAccessToken     string `json:\"access_token\"`\n\tIssuedTokenType string `json:\"issued_token_type,omitempty\"`\n\tTokenType       string `json:\"token_type\"`\n\tExpiresIn       int    `json:\"expires_in,omitempty\"`\n\tRefreshToken    string `json:\"refresh_token,omitempty\"`\n\tIDToken         string `json:\"id_token,omitempty\"`\n\tScope           string `json:\"scope,omitempty\"`\n}\n\nfunc (s *Server) toAccessTokenResponse(idToken, accessToken, refreshToken string, expiry time.Time) *accessTokenResponse {\n\treturn &accessTokenResponse{\n\t\tAccessToken:  accessToken,\n\t\tTokenType:    \"bearer\",\n\t\tExpiresIn:    int(expiry.Sub(s.now()).Seconds()),\n\t\tRefreshToken: refreshToken,\n\t\tIDToken:      idToken,\n\t}\n}\n\nfunc (s *Server) writeAccessToken(w http.ResponseWriter, resp *accessTokenResponse) {\n\tdata, err := json.Marshal(resp)\n\tif err != nil {\n\t\t// TODO(nabokihms): error with context\n\t\ts.logger.Error(\"failed to marshal access token response\", \"err\", err)\n\t\ts.tokenErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(data)))\n\n\t// Token response must include cache headers https://tools.ietf.org/html/rfc6749#section-5.1\n\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\tw.Header().Set(\"Pragma\", \"no-cache\")\n\tw.Write(data)\n}\n\nfunc (s *Server) renderError(r *http.Request, w http.ResponseWriter, status int, description string) {\n\tif err := s.templates.err(r, w, status, description); err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t}\n}\n\nfunc (s *Server) tokenErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) {\n\tif err := tokenErr(w, typ, description, statusCode); err != nil {\n\t\t// TODO(nabokihms): error with context\n\t\ts.logger.Error(\"token error response\", \"err\", err)\n\t}\n}\n\n// Check for username prompt override from connector. Defaults to \"Username\".\nfunc usernamePrompt(conn connector.PasswordConnector) string {\n\tif attr := conn.Prompt(); attr != \"\" {\n\t\treturn attr\n\t}\n\treturn \"Username\"\n}\n"
  },
  {
    "path": "server/handlers_approval_test.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\ntype getAuthRequestErrorStorage struct {\n\tstorage.Storage\n\terr error\n}\n\nfunc (s *getAuthRequestErrorStorage) GetAuthRequest(context.Context, string) (storage.AuthRequest, error) {\n\treturn storage.AuthRequest{}, s.err\n}\n\nfunc TestHandleApprovalGetAuthRequestErrorGET(t *testing.T) {\n\thttpServer, server := newTestServer(t, func(c *Config) {\n\t\tc.Storage = &getAuthRequestErrorStorage{Storage: c.Storage, err: errors.New(\"storage unavailable\")}\n\t})\n\tdefer httpServer.Close()\n\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(http.MethodGet, \"/approval?req=any&hmac=AQ\", nil)\n\n\tserver.ServeHTTP(rr, req)\n\n\trequire.Equal(t, http.StatusInternalServerError, rr.Code)\n\trequire.Contains(t, rr.Body.String(), \"Database error.\")\n}\n\nfunc TestHandleApprovalGetAuthRequestNotFoundGET(t *testing.T) {\n\thttpServer, server := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(http.MethodGet, \"/approval?req=does-not-exist&hmac=AQ\", nil)\n\n\tserver.ServeHTTP(rr, req)\n\n\trequire.Equal(t, http.StatusBadRequest, rr.Code)\n\trequire.Contains(t, rr.Body.String(), \"User session error.\")\n\trequire.NotContains(t, rr.Body.String(), \"Database error.\")\n}\n\nfunc TestHandleApprovalGetAuthRequestNotFoundPOST(t *testing.T) {\n\thttpServer, server := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\tbody := strings.NewReader(\"approval=approve&req=does-not-exist&hmac=AQ\")\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(http.MethodPost, \"/approval\", body)\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\tserver.ServeHTTP(rr, req)\n\n\trequire.Equal(t, http.StatusBadRequest, rr.Code)\n\trequire.Contains(t, rr.Body.String(), \"User session error.\")\n\trequire.NotContains(t, rr.Body.String(), \"Database error.\")\n}\n\nfunc TestHandleApprovalDoubleSubmitPOST(t *testing.T) {\n\tctx := t.Context()\n\thttpServer, server := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\tauthReq := storage.AuthRequest{\n\t\tID:            \"approval-double-submit\",\n\t\tClientID:      \"test\",\n\t\tResponseTypes: []string{responseTypeCode},\n\t\tRedirectURI:   \"https://client.example/callback\",\n\t\tExpiry:        time.Now().Add(time.Minute),\n\t\tLoggedIn:      true,\n\t\tMFAValidated:  true,\n\t\tHMACKey:       []byte(\"approval-double-submit-key\"),\n\t}\n\trequire.NoError(t, server.storage.CreateAuthRequest(ctx, authReq))\n\n\th := hmac.New(sha256.New, authReq.HMACKey)\n\th.Write([]byte(authReq.ID))\n\tmac := base64.RawURLEncoding.EncodeToString(h.Sum(nil))\n\n\tform := url.Values{\n\t\t\"approval\": {\"approve\"},\n\t\t\"req\":      {authReq.ID},\n\t\t\"hmac\":     {mac},\n\t}\n\n\tfirstRR := httptest.NewRecorder()\n\tfirstReq := httptest.NewRequest(http.MethodPost, \"/approval\", strings.NewReader(form.Encode()))\n\tfirstReq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\tserver.ServeHTTP(firstRR, firstReq)\n\n\trequire.Equal(t, http.StatusSeeOther, firstRR.Code)\n\trequire.Contains(t, firstRR.Header().Get(\"Location\"), \"https://client.example/callback\")\n\n\tsecondRR := httptest.NewRecorder()\n\tsecondReq := httptest.NewRequest(http.MethodPost, \"/approval\", strings.NewReader(form.Encode()))\n\tsecondReq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\tserver.ServeHTTP(secondRR, secondReq)\n\n\trequire.Equal(t, http.StatusBadRequest, secondRR.Code)\n\trequire.Contains(t, secondRR.Body.String(), \"User session error.\")\n\trequire.NotContains(t, secondRR.Body.String(), \"Database error.\")\n}\n"
  },
  {
    "path": "server/handlers_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"path\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tgosundheit \"github.com/AppsFlyer/go-sundheit\"\n\t\"github.com/AppsFlyer/go-sundheit/checks\"\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/bcrypt\"\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\nfunc boolPtr(v bool) *bool {\n\treturn &v\n}\n\nfunc TestHandleHealth(t *testing.T) {\n\thttpServer, server := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\trr := httptest.NewRecorder()\n\tserver.ServeHTTP(rr, httptest.NewRequest(\"GET\", \"/healthz\", nil))\n\tif rr.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200 got %d\", rr.Code)\n\t}\n}\n\nfunc TestHandleDiscovery(t *testing.T) {\n\thttpServer, server := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\trr := httptest.NewRecorder()\n\tserver.ServeHTTP(rr, httptest.NewRequest(\"GET\", \"/.well-known/openid-configuration\", nil))\n\tif rr.Code != http.StatusOK {\n\t\tt.Errorf(\"expected 200 got %d\", rr.Code)\n\t}\n\n\tvar res discovery\n\terr := json.NewDecoder(rr.Result().Body).Decode(&res)\n\trequire.NoError(t, err)\n\trequire.Equal(t, discovery{\n\t\tIssuer:         httpServer.URL,\n\t\tAuth:           fmt.Sprintf(\"%s/auth\", httpServer.URL),\n\t\tToken:          fmt.Sprintf(\"%s/token\", httpServer.URL),\n\t\tKeys:           fmt.Sprintf(\"%s/keys\", httpServer.URL),\n\t\tUserInfo:       fmt.Sprintf(\"%s/userinfo\", httpServer.URL),\n\t\tDeviceEndpoint: fmt.Sprintf(\"%s/device/code\", httpServer.URL),\n\t\tIntrospect:     fmt.Sprintf(\"%s/token/introspect\", httpServer.URL),\n\t\tGrantTypes: []string{\n\t\t\t\"authorization_code\",\n\t\t\t\"client_credentials\",\n\t\t\t\"refresh_token\",\n\t\t\t\"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\"urn:ietf:params:oauth:grant-type:token-exchange\",\n\t\t},\n\t\tResponseTypes: []string{\n\t\t\t\"code\",\n\t\t},\n\t\tSubjects: []string{\n\t\t\t\"public\",\n\t\t},\n\t\tIDTokenAlgs: []string{\n\t\t\t\"RS256\",\n\t\t},\n\t\tCodeChallengeAlgs: []string{\n\t\t\t\"S256\",\n\t\t\t\"plain\",\n\t\t},\n\t\tScopes: []string{\n\t\t\t\"openid\",\n\t\t\t\"email\",\n\t\t\t\"groups\",\n\t\t\t\"profile\",\n\t\t\t\"offline_access\",\n\t\t},\n\t\tAuthMethods: []string{\n\t\t\t\"client_secret_basic\",\n\t\t\t\"client_secret_post\",\n\t\t},\n\t\tClaims: []string{\n\t\t\t\"iss\",\n\t\t\t\"sub\",\n\t\t\t\"aud\",\n\t\t\t\"iat\",\n\t\t\t\"exp\",\n\t\t\t\"email\",\n\t\t\t\"email_verified\",\n\t\t\t\"locale\",\n\t\t\t\"name\",\n\t\t\t\"preferred_username\",\n\t\t\t\"at_hash\",\n\t\t},\n\t}, res)\n}\n\nfunc TestHandleHealthFailure(t *testing.T) {\n\thttpServer, server := newTestServer(t, func(c *Config) {\n\t\tc.HealthChecker = gosundheit.New()\n\n\t\tc.HealthChecker.RegisterCheck(\n\t\t\t&checks.CustomCheck{\n\t\t\t\tCheckName: \"fail\",\n\t\t\t\tCheckFunc: func(_ context.Context) (details interface{}, err error) {\n\t\t\t\t\treturn nil, errors.New(\"error\")\n\t\t\t\t},\n\t\t\t},\n\t\t\tgosundheit.InitiallyPassing(false),\n\t\t\tgosundheit.ExecutionPeriod(1*time.Second),\n\t\t)\n\t})\n\tdefer httpServer.Close()\n\n\trr := httptest.NewRecorder()\n\tserver.ServeHTTP(rr, httptest.NewRequest(\"GET\", \"/healthz\", nil))\n\tif rr.Code != http.StatusInternalServerError {\n\t\tt.Errorf(\"expected 500 got %d\", rr.Code)\n\t}\n}\n\ntype emptyStorage struct {\n\tstorage.Storage\n}\n\nfunc (*emptyStorage) GetAuthRequest(context.Context, string) (storage.AuthRequest, error) {\n\treturn storage.AuthRequest{}, storage.ErrNotFound\n}\n\nfunc TestHandleInvalidOAuth2Callbacks(t *testing.T) {\n\thttpServer, server := newTestServer(t, func(c *Config) {\n\t\tc.Storage = &emptyStorage{c.Storage}\n\t})\n\tdefer httpServer.Close()\n\n\ttests := []struct {\n\t\tTargetURI    string\n\t\tExpectedCode int\n\t}{\n\t\t{\"/callback\", http.StatusBadRequest},\n\t\t{\"/callback?code=&state=\", http.StatusBadRequest},\n\t\t{\"/callback?code=AAAAAAA&state=BBBBBBB\", http.StatusBadRequest},\n\t}\n\n\trr := httptest.NewRecorder()\n\n\tfor i, r := range tests {\n\t\tserver.ServeHTTP(rr, httptest.NewRequest(\"GET\", r.TargetURI, nil))\n\t\tif rr.Code != r.ExpectedCode {\n\t\t\tt.Fatalf(\"test %d expected %d, got %d\", i, r.ExpectedCode, rr.Code)\n\t\t}\n\t}\n}\n\nfunc TestHandleInvalidSAMLCallbacks(t *testing.T) {\n\thttpServer, server := newTestServer(t, func(c *Config) {\n\t\tc.Storage = &emptyStorage{c.Storage}\n\t})\n\tdefer httpServer.Close()\n\n\ttype requestForm struct {\n\t\tRelayState string\n\t}\n\ttests := []struct {\n\t\tRequestForm  requestForm\n\t\tExpectedCode int\n\t}{\n\t\t{requestForm{}, http.StatusBadRequest},\n\t\t{requestForm{RelayState: \"AAAAAAA\"}, http.StatusBadRequest},\n\t}\n\n\trr := httptest.NewRecorder()\n\n\tfor i, r := range tests {\n\t\tjsonValue, err := json.Marshal(r.RequestForm)\n\t\tif err != nil {\n\t\t\tt.Fatal(err.Error())\n\t\t}\n\t\tserver.ServeHTTP(rr, httptest.NewRequest(\"POST\", \"/callback\", bytes.NewBuffer(jsonValue)))\n\t\tif rr.Code != r.ExpectedCode {\n\t\t\tt.Fatalf(\"test %d expected %d, got %d\", i, r.ExpectedCode, rr.Code)\n\t\t}\n\t}\n}\n\n// TestHandleAuthCode checks that it is forbidden to use same code twice\nfunc TestHandleAuthCode(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\thandleCode func(*testing.T, context.Context, *oauth2.Config, string)\n\t}{\n\t\t{\n\t\t\tname: \"Code Reuse should return invalid_grant\",\n\t\t\thandleCode: func(t *testing.T, ctx context.Context, oauth2Config *oauth2.Config, code string) {\n\t\t\t\t_, err := oauth2Config.Exchange(ctx, code)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t_, err = oauth2Config.Exchange(ctx, code)\n\t\t\t\trequire.Error(t, err)\n\n\t\t\t\toauth2Err, ok := err.(*oauth2.RetrieveError)\n\t\t\t\trequire.True(t, ok)\n\n\t\t\t\tvar errResponse struct{ Error string }\n\t\t\t\terr = json.Unmarshal(oauth2Err.Body, &errResponse)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t// invalid_grant must be returned for invalid values\n\t\t\t\t// https://tools.ietf.org/html/rfc6749#section-5.2\n\t\t\t\trequire.Equal(t, errInvalidGrant, errResponse.Error)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"No Code should return invalid_request\",\n\t\t\thandleCode: func(t *testing.T, ctx context.Context, oauth2Config *oauth2.Config, _ string) {\n\t\t\t\t_, err := oauth2Config.Exchange(ctx, \"\")\n\t\t\t\trequire.Error(t, err)\n\n\t\t\t\toauth2Err, ok := err.(*oauth2.RetrieveError)\n\t\t\t\trequire.True(t, ok)\n\n\t\t\t\tvar errResponse struct{ Error string }\n\t\t\t\terr = json.Unmarshal(oauth2Err.Body, &errResponse)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\trequire.Equal(t, errInvalidRequest, errResponse.Error)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) { c.Issuer += \"/non-root-path\" })\n\t\t\tdefer httpServer.Close()\n\n\t\t\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tvar oauth2Client oauth2Client\n\t\t\toauth2Client.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path != \"/callback\" {\n\t\t\t\t\thttp.Redirect(w, r, oauth2Client.config.AuthCodeURL(\"\"), http.StatusSeeOther)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tq := r.URL.Query()\n\t\t\t\trequire.Equal(t, q.Get(\"error\"), \"\", q.Get(\"error_description\"))\n\n\t\t\t\tcode := q.Get(\"code\")\n\t\t\t\ttc.handleCode(t, ctx, oauth2Client.config, code)\n\n\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t}))\n\t\t\tdefer oauth2Client.server.Close()\n\n\t\t\tredirectURL := oauth2Client.server.URL + \"/callback\"\n\t\t\tclient := storage.Client{\n\t\t\t\tID:           \"testclient\",\n\t\t\t\tSecret:       \"testclientsecret\",\n\t\t\t\tRedirectURIs: []string{redirectURL},\n\t\t\t}\n\t\t\terr = s.storage.CreateClient(ctx, client)\n\t\t\trequire.NoError(t, err)\n\n\t\t\toauth2Client.config = &oauth2.Config{\n\t\t\t\tClientID:     client.ID,\n\t\t\t\tClientSecret: client.Secret,\n\t\t\t\tEndpoint:     p.Endpoint(),\n\t\t\t\tScopes:       []string{oidc.ScopeOpenID, \"email\", \"offline_access\"},\n\t\t\t\tRedirectURL:  redirectURL,\n\t\t\t}\n\n\t\t\tresp, err := http.Get(oauth2Client.server.URL + \"/login\")\n\t\t\trequire.NoError(t, err)\n\n\t\t\tresp.Body.Close()\n\t\t})\n\t}\n}\n\nfunc mockConnectorDataTestStorage(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tc := storage.Client{\n\t\tID:           \"test\",\n\t\tSecret:       \"barfoo\",\n\t\tRedirectURIs: []string{\"foo://bar.com/\", \"https://auth.example.com\"},\n\t\tName:         \"dex client\",\n\t\tLogoURL:      \"https://goo.gl/JIyzIC\",\n\t}\n\n\terr := s.CreateClient(ctx, c)\n\trequire.NoError(t, err)\n\n\tc1 := storage.Connector{\n\t\tID:   \"test\",\n\t\tType: \"mockPassword\",\n\t\tName: \"mockPassword\",\n\t\tConfig: []byte(`{\n\"username\": \"test\",\n\"password\": \"test\"\n}`),\n\t}\n\n\terr = s.CreateConnector(ctx, c1)\n\trequire.NoError(t, err)\n\n\tc2 := storage.Connector{\n\t\tID:   \"http://any.valid.url/\",\n\t\tType: \"mock\",\n\t\tName: \"mockURLID\",\n\t}\n\n\terr = s.CreateConnector(ctx, c2)\n\trequire.NoError(t, err)\n}\n\nfunc TestHandlePassword(t *testing.T) {\n\tctx := t.Context()\n\n\ttests := []struct {\n\t\tname                  string\n\t\tscopes                string\n\t\tofflineSessionCreated bool\n\t}{\n\t\t{\n\t\t\tname:                  \"Password login, request refresh token\",\n\t\t\tscopes:                \"openid offline_access email\",\n\t\t\tofflineSessionCreated: true,\n\t\t},\n\t\t{\n\t\t\tname:                  \"Password login\",\n\t\t\tscopes:                \"openid email\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t// Setup a dex server.\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.PasswordConnector = \"test\"\n\t\t\t\tc.Now = time.Now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tmockConnectorDataTestStorage(t, s.storage)\n\n\t\t\tmakeReq := func(username, password string) *httptest.ResponseRecorder {\n\t\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tu.Path = path.Join(u.Path, \"/token\")\n\t\t\t\tv := url.Values{}\n\t\t\t\tv.Add(\"scope\", tc.scopes)\n\t\t\t\tv.Add(\"grant_type\", \"password\")\n\t\t\t\tv.Add(\"username\", username)\n\t\t\t\tv.Add(\"password\", password)\n\n\t\t\t\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(v.Encode()))\n\t\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded; param=value\")\n\t\t\t\treq.SetBasicAuth(\"test\", \"barfoo\")\n\n\t\t\t\trr := httptest.NewRecorder()\n\t\t\t\ts.ServeHTTP(rr, req)\n\n\t\t\t\treturn rr\n\t\t\t}\n\n\t\t\t// Check unauthorized error\n\t\t\t{\n\t\t\t\trr := makeReq(\"test\", \"invalid\")\n\t\t\t\trequire.Equal(t, 401, rr.Code)\n\t\t\t}\n\n\t\t\t// Check that we received expected refresh token\n\t\t\t{\n\t\t\t\trr := makeReq(\"test\", \"test\")\n\t\t\t\trequire.Equal(t, 200, rr.Code)\n\n\t\t\t\tvar ref struct {\n\t\t\t\t\tToken string `json:\"refresh_token\"`\n\t\t\t\t}\n\t\t\t\terr := json.Unmarshal(rr.Body.Bytes(), &ref)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tnewSess, err := s.storage.GetOfflineSessions(ctx, \"0-385-28089-0\", \"test\")\n\t\t\t\tif tc.offlineSessionCreated {\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\trequire.Equal(t, `{\"test\": \"true\"}`, string(newSess.ConnectorData))\n\t\t\t\t} else {\n\t\t\t\t\trequire.Error(t, storage.ErrNotFound, err)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandlePassword_LocalPasswordDBClaims(t *testing.T) {\n\tctx := t.Context()\n\n\t// Setup a dex server.\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.PasswordConnector = \"local\"\n\t})\n\tdefer httpServer.Close()\n\n\t// Client credentials for password grant.\n\tclient := storage.Client{\n\t\tID:           \"test\",\n\t\tSecret:       \"barfoo\",\n\t\tRedirectURIs: []string{\"foo://bar.com/\", \"https://auth.example.com\"},\n\t}\n\trequire.NoError(t, s.storage.CreateClient(ctx, client))\n\n\t// Enable local connector.\n\tlocalConn := storage.Connector{\n\t\tID:              \"local\",\n\t\tType:            LocalConnector,\n\t\tName:            \"Email\",\n\t\tResourceVersion: \"1\",\n\t}\n\trequire.NoError(t, s.storage.CreateConnector(ctx, localConn))\n\t_, err := s.OpenConnector(localConn)\n\trequire.NoError(t, err)\n\n\t// Create a user in the password DB with groups and preferred_username.\n\tpw := \"secret\"\n\thash, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)\n\trequire.NoError(t, err)\n\trequire.NoError(t, s.storage.CreatePassword(ctx, storage.Password{\n\t\tEmail:             \"user@example.com\",\n\t\tUsername:          \"user-login\",\n\t\tName:              \"User Full Name\",\n\t\tEmailVerified:     boolPtr(false),\n\t\tPreferredUsername: \"user-public\",\n\t\tUserID:            \"user-id\",\n\t\tGroups:            []string{\"team-a\", \"team-a/admins\"},\n\t\tHash:              hash,\n\t}))\n\n\tu, err := url.Parse(s.issuerURL.String())\n\trequire.NoError(t, err)\n\tu.Path = path.Join(u.Path, \"/token\")\n\n\tv := url.Values{}\n\tv.Add(\"scope\", \"openid profile email groups\")\n\tv.Add(\"grant_type\", \"password\")\n\tv.Add(\"username\", \"user@example.com\")\n\tv.Add(\"password\", pw)\n\n\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(v.Encode()))\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.SetBasicAuth(\"test\", \"barfoo\")\n\n\trr := httptest.NewRecorder()\n\ts.ServeHTTP(rr, req)\n\trequire.Equal(t, http.StatusOK, rr.Code)\n\n\tvar tokenResponse struct {\n\t\tIDToken string `json:\"id_token\"`\n\t}\n\trequire.NoError(t, json.Unmarshal(rr.Body.Bytes(), &tokenResponse))\n\trequire.NotEmpty(t, tokenResponse.IDToken)\n\n\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\trequire.NoError(t, err)\n\tidToken, err := p.Verifier(&oidc.Config{SkipClientIDCheck: true}).Verify(ctx, tokenResponse.IDToken)\n\trequire.NoError(t, err)\n\n\tvar claims struct {\n\t\tName              string   `json:\"name\"`\n\t\tEmailVerified     bool     `json:\"email_verified\"`\n\t\tPreferredUsername string   `json:\"preferred_username\"`\n\t\tGroups            []string `json:\"groups\"`\n\t}\n\trequire.NoError(t, idToken.Claims(&claims))\n\trequire.Equal(t, \"User Full Name\", claims.Name)\n\trequire.False(t, claims.EmailVerified)\n\trequire.Equal(t, \"user-public\", claims.PreferredUsername)\n\trequire.Equal(t, []string{\"team-a\", \"team-a/admins\"}, claims.Groups)\n}\n\nfunc setSessionsEnabled(t *testing.T, enabled bool) {\n\tt.Helper()\n\tif enabled {\n\t\tt.Setenv(\"DEX_SESSIONS_ENABLED\", \"true\")\n\t} else {\n\t\tt.Setenv(\"DEX_SESSIONS_ENABLED\", \"false\")\n\t}\n}\n\nfunc TestFinalizeLoginCreatesUserIdentity(t *testing.T) {\n\tctx := t.Context()\n\tsetSessionsEnabled(t, true)\n\n\tconnID := \"mockPw\"\n\tauthReqID := \"test-create-ui\"\n\texpiry := time.Now().Add(100 * time.Second)\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.SkipApprovalScreen = true\n\t\tc.Now = time.Now\n\t})\n\tdefer httpServer.Close()\n\n\tsc := storage.Connector{\n\t\tID:              connID,\n\t\tType:            \"mockPassword\",\n\t\tName:            \"MockPassword\",\n\t\tResourceVersion: \"1\",\n\t\tConfig:          []byte(`{\"username\": \"foo\", \"password\": \"password\"}`),\n\t}\n\trequire.NoError(t, s.storage.CreateConnector(ctx, sc))\n\t_, err := s.OpenConnector(sc)\n\trequire.NoError(t, err)\n\n\tauthReq := storage.AuthRequest{\n\t\tID:            authReqID,\n\t\tConnectorID:   connID,\n\t\tRedirectURI:   \"cb\",\n\t\tExpiry:        expiry,\n\t\tResponseTypes: []string{responseTypeCode},\n\t}\n\trequire.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))\n\n\trr := httptest.NewRecorder()\n\treqPath := fmt.Sprintf(\"/auth/%s/login?state=%s&back=&login=foo&password=password\", connID, authReqID)\n\ts.handlePasswordLogin(rr, httptest.NewRequest(\"POST\", reqPath, nil))\n\n\trequire.Equal(t, 303, rr.Code)\n\n\tui, err := s.storage.GetUserIdentity(ctx, \"0-385-28089-0\", connID)\n\trequire.NoError(t, err, \"UserIdentity should exist after login\")\n\trequire.Equal(t, \"0-385-28089-0\", ui.UserID)\n\trequire.Equal(t, connID, ui.ConnectorID)\n\trequire.Equal(t, \"kilgore@kilgore.trout\", ui.Claims.Email)\n\trequire.NotZero(t, ui.CreatedAt, \"CreatedAt should be set\")\n\trequire.NotZero(t, ui.LastLogin, \"LastLogin should be set\")\n}\n\nfunc TestFinalizeLoginUpdatesUserIdentity(t *testing.T) {\n\tctx := t.Context()\n\tsetSessionsEnabled(t, true)\n\n\tconnID := \"mockPw\"\n\tauthReqID := \"test-update-ui\"\n\texpiry := time.Now().Add(100 * time.Second)\n\toldTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.SkipApprovalScreen = true\n\t\tc.Now = time.Now\n\t})\n\tdefer httpServer.Close()\n\n\tsc := storage.Connector{\n\t\tID:              connID,\n\t\tType:            \"mockPassword\",\n\t\tName:            \"MockPassword\",\n\t\tResourceVersion: \"1\",\n\t\tConfig:          []byte(`{\"username\": \"foo\", \"password\": \"password\"}`),\n\t}\n\trequire.NoError(t, s.storage.CreateConnector(ctx, sc))\n\t_, err := s.OpenConnector(sc)\n\trequire.NoError(t, err)\n\n\t// Pre-create UserIdentity with old data\n\trequire.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{\n\t\tUserID:      \"0-385-28089-0\",\n\t\tConnectorID: connID,\n\t\tClaims: storage.Claims{\n\t\t\tUserID:   \"0-385-28089-0\",\n\t\t\tUsername: \"Old Name\",\n\t\t\tEmail:    \"old@example.com\",\n\t\t},\n\t\tConsents:  map[string][]string{\"existing-client\": {\"openid\"}},\n\t\tCreatedAt: oldTime,\n\t\tLastLogin: oldTime,\n\t}))\n\n\tauthReq := storage.AuthRequest{\n\t\tID:            authReqID,\n\t\tConnectorID:   connID,\n\t\tRedirectURI:   \"cb\",\n\t\tExpiry:        expiry,\n\t\tResponseTypes: []string{responseTypeCode},\n\t}\n\trequire.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))\n\n\trr := httptest.NewRecorder()\n\treqPath := fmt.Sprintf(\"/auth/%s/login?state=%s&back=&login=foo&password=password\", connID, authReqID)\n\ts.handlePasswordLogin(rr, httptest.NewRequest(\"POST\", reqPath, nil))\n\n\trequire.Equal(t, 303, rr.Code)\n\n\tui, err := s.storage.GetUserIdentity(ctx, \"0-385-28089-0\", connID)\n\trequire.NoError(t, err, \"UserIdentity should exist after login\")\n\trequire.Equal(t, \"Kilgore Trout\", ui.Claims.Username, \"claims should be refreshed from the connector\")\n\trequire.Equal(t, \"kilgore@kilgore.trout\", ui.Claims.Email, \"claims should be refreshed from the connector\")\n\trequire.True(t, ui.LastLogin.After(oldTime), \"LastLogin should be updated\")\n\trequire.Equal(t, oldTime, ui.CreatedAt, \"CreatedAt should not change on update\")\n\trequire.Equal(t, []string{\"openid\"}, ui.Consents[\"existing-client\"], \"existing consents should be preserved\")\n}\n\nfunc TestFinalizeLoginSkipsUserIdentityWhenDisabled(t *testing.T) {\n\tctx := t.Context()\n\tsetSessionsEnabled(t, false)\n\n\tconnID := \"mockPw\"\n\tauthReqID := \"test-no-ui\"\n\texpiry := time.Now().Add(100 * time.Second)\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.SkipApprovalScreen = true\n\t\tc.Now = time.Now\n\t})\n\tdefer httpServer.Close()\n\n\tsc := storage.Connector{\n\t\tID:              connID,\n\t\tType:            \"mockPassword\",\n\t\tName:            \"MockPassword\",\n\t\tResourceVersion: \"1\",\n\t\tConfig:          []byte(`{\"username\": \"foo\", \"password\": \"password\"}`),\n\t}\n\trequire.NoError(t, s.storage.CreateConnector(ctx, sc))\n\t_, err := s.OpenConnector(sc)\n\trequire.NoError(t, err)\n\n\tauthReq := storage.AuthRequest{\n\t\tID:            authReqID,\n\t\tConnectorID:   connID,\n\t\tRedirectURI:   \"cb\",\n\t\tExpiry:        expiry,\n\t\tResponseTypes: []string{responseTypeCode},\n\t}\n\trequire.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))\n\n\trr := httptest.NewRecorder()\n\treqPath := fmt.Sprintf(\"/auth/%s/login?state=%s&back=&login=foo&password=password\", connID, authReqID)\n\ts.handlePasswordLogin(rr, httptest.NewRequest(\"POST\", reqPath, nil))\n\n\trequire.Equal(t, 303, rr.Code)\n\n\t_, err = s.storage.GetUserIdentity(ctx, \"0-385-28089-0\", connID)\n\trequire.ErrorIs(t, err, storage.ErrNotFound, \"UserIdentity should not be created when sessions disabled\")\n}\n\nfunc TestSkipApprovalWithExistingConsent(t *testing.T) {\n\tctx := t.Context()\n\tsetSessionsEnabled(t, true)\n\n\tconnID := \"mock\"\n\tauthReqID := \"test-consent-skip\"\n\texpiry := time.Now().Add(100 * time.Second)\n\n\ttests := []struct {\n\t\tname        string\n\t\tconsents    map[string][]string\n\t\tscopes      []string\n\t\tclientID    string\n\t\tforcePrompt bool\n\t\twantPath    string\n\t}{\n\t\t{\n\t\t\tname:     \"Existing consent covers requested scopes\",\n\t\t\tconsents: map[string][]string{\"test\": {\"email\", \"profile\"}},\n\t\t\tscopes:   []string{\"openid\", \"email\", \"profile\"},\n\t\t\tclientID: \"test\",\n\t\t\twantPath: \"/callback/cb\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Existing consent missing a scope\",\n\t\t\tconsents: map[string][]string{\"test\": {\"email\"}},\n\t\t\tscopes:   []string{\"openid\", \"email\", \"profile\"},\n\t\t\tclientID: \"test\",\n\t\t\twantPath: \"/approval\",\n\t\t},\n\t\t{\n\t\t\tname:        \"Force approval overrides consent\",\n\t\t\tconsents:    map[string][]string{\"test\": {\"email\", \"profile\"}},\n\t\t\tscopes:      []string{\"openid\", \"email\", \"profile\"},\n\t\t\tclientID:    \"test\",\n\t\t\tforcePrompt: true,\n\t\t\twantPath:    \"/approval\",\n\t\t},\n\t\t{\n\t\t\tname:     \"No consent for this client\",\n\t\t\tconsents: map[string][]string{\"other-client\": {\"email\"}},\n\t\t\tscopes:   []string{\"openid\", \"email\"},\n\t\t\tclientID: \"test\",\n\t\t\twantPath: \"/approval\",\n\t\t},\n\t\t{\n\t\t\tname:     \"Only openid scope - skip with empty consent\",\n\t\t\tconsents: map[string][]string{\"test\": {}},\n\t\t\tscopes:   []string{\"openid\"},\n\t\t\tclientID: \"test\",\n\t\t\twantPath: \"/callback/cb\",\n\t\t},\n\t\t{\n\t\t\tname:     \"offline_access requires consent\",\n\t\t\tconsents: map[string][]string{\"test\": {}},\n\t\t\tscopes:   []string{\"openid\", \"offline_access\"},\n\t\t\tclientID: \"test\",\n\t\t\twantPath: \"/approval\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.SkipApprovalScreen = false\n\t\t\t\tc.Now = time.Now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\t// Pre-create UserIdentity with consents\n\t\t\trequire.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{\n\t\t\t\tUserID:      \"0-385-28089-0\",\n\t\t\t\tConnectorID: connID,\n\t\t\t\tClaims: storage.Claims{\n\t\t\t\t\tUserID:        \"0-385-28089-0\",\n\t\t\t\t\tUsername:      \"Kilgore Trout\",\n\t\t\t\t\tEmail:         \"kilgore@kilgore.trout\",\n\t\t\t\t\tEmailVerified: true,\n\t\t\t\t},\n\t\t\t\tConsents:  tc.consents,\n\t\t\t\tCreatedAt: time.Now(),\n\t\t\t\tLastLogin: time.Now(),\n\t\t\t}))\n\n\t\t\tauthReq := storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tClientID:            tc.clientID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       []string{responseTypeCode},\n\t\t\t\tScopes:              tc.scopes,\n\t\t\t\tForceApprovalPrompt: tc.forcePrompt,\n\t\t\t}\n\t\t\trequire.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\treqPath := fmt.Sprintf(\"/callback/%s?state=%s\", connID, authReqID)\n\t\t\ts.handleConnectorCallback(rr, httptest.NewRequest(\"GET\", reqPath, nil))\n\n\t\t\trequire.Equal(t, 303, rr.Code)\n\t\t\tcb, err := url.Parse(rr.Result().Header.Get(\"Location\"))\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.wantPath, cb.Path)\n\t\t})\n\t}\n}\n\nfunc TestConsentPersistedOnApproval(t *testing.T) {\n\tctx := t.Context()\n\tsetSessionsEnabled(t, true)\n\n\thttpServer, s := newTestServer(t, nil)\n\tdefer httpServer.Close()\n\n\tuserID := \"test-user\"\n\tconnectorID := \"mock\"\n\tclientID := \"test\"\n\n\t// Pre-create UserIdentity (would have been created during login)\n\trequire.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{\n\t\tUserID:      userID,\n\t\tConnectorID: connectorID,\n\t\tClaims:      storage.Claims{UserID: userID},\n\t\tConsents:    make(map[string][]string),\n\t\tCreatedAt:   time.Now(),\n\t\tLastLogin:   time.Now(),\n\t}))\n\n\tauthReq := storage.AuthRequest{\n\t\tID:            \"approval-consent-test\",\n\t\tClientID:      clientID,\n\t\tConnectorID:   connectorID,\n\t\tResponseTypes: []string{responseTypeCode},\n\t\tRedirectURI:   \"https://client.example/callback\",\n\t\tExpiry:        time.Now().Add(time.Minute),\n\t\tLoggedIn:      true,\n\t\tClaims:        storage.Claims{UserID: userID},\n\t\tScopes:        []string{\"openid\", \"email\", \"profile\"},\n\t\tHMACKey:       []byte(\"consent-test-key\"),\n\t}\n\trequire.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))\n\n\th := hmac.New(sha256.New, authReq.HMACKey)\n\th.Write([]byte(authReq.ID))\n\tmac := base64.RawURLEncoding.EncodeToString(h.Sum(nil))\n\n\tform := url.Values{\n\t\t\"approval\": {\"approve\"},\n\t\t\"req\":      {authReq.ID},\n\t\t\"hmac\":     {mac},\n\t}\n\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(http.MethodPost, \"/approval\", strings.NewReader(form.Encode()))\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\ts.ServeHTTP(rr, req)\n\n\trequire.Equal(t, http.StatusSeeOther, rr.Code, \"approval should redirect\")\n\n\tui, err := s.storage.GetUserIdentity(ctx, userID, connectorID)\n\trequire.NoError(t, err, \"UserIdentity should exist\")\n\trequire.Equal(t, []string{\"openid\", \"email\", \"profile\"}, ui.Consents[clientID], \"approved scopes should be persisted\")\n}\n\nfunc TestScopesCoveredByConsent(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tapproved  []string\n\t\trequested []string\n\t\twant      bool\n\t}{\n\t\t{\n\t\t\tname:      \"All scopes covered\",\n\t\t\tapproved:  []string{\"email\", \"profile\"},\n\t\t\trequested: []string{\"openid\", \"email\", \"profile\"},\n\t\t\twant:      true,\n\t\t},\n\t\t{\n\t\t\tname:      \"Missing scope\",\n\t\t\tapproved:  []string{\"email\"},\n\t\t\trequested: []string{\"openid\", \"email\", \"groups\"},\n\t\t\twant:      false,\n\t\t},\n\t\t{\n\t\t\tname:      \"Only openid scope skipped\",\n\t\t\tapproved:  []string{},\n\t\t\trequested: []string{\"openid\"},\n\t\t\twant:      true,\n\t\t},\n\t\t{\n\t\t\tname:      \"offline_access requires consent\",\n\t\t\tapproved:  []string{},\n\t\t\trequested: []string{\"openid\", \"offline_access\"},\n\t\t\twant:      false,\n\t\t},\n\t\t{\n\t\t\tname:      \"offline_access covered by consent\",\n\t\t\tapproved:  []string{\"offline_access\"},\n\t\t\trequested: []string{\"openid\", \"offline_access\"},\n\t\t\twant:      true,\n\t\t},\n\t\t{\n\t\t\tname:      \"Nil approved\",\n\t\t\tapproved:  nil,\n\t\t\trequested: []string{\"email\"},\n\t\t\twant:      false,\n\t\t},\n\t\t{\n\t\t\tname:      \"Empty requested\",\n\t\t\tapproved:  []string{\"email\"},\n\t\t\trequested: []string{},\n\t\t\twant:      true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := scopesCoveredByConsent(tc.approved, tc.requested)\n\t\t\trequire.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n\nfunc TestHandlePasswordLoginWithSkipApproval(t *testing.T) {\n\tctx := t.Context()\n\n\tconnID := \"mockPw\"\n\tauthReqID := \"test\"\n\texpiry := time.Now().Add(100 * time.Second)\n\tresTypes := []string{responseTypeCode}\n\n\ttests := []struct {\n\t\tname                  string\n\t\tskipApproval          bool\n\t\tauthReq               storage.AuthRequest\n\t\texpectedRes           string\n\t\tofflineSessionCreated bool\n\t}{\n\t\t{\n\t\t\tname:         \"Force approval\",\n\t\t\tskipApproval: false,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: true,\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Skip approval by server config\",\n\t\t\tskipApproval: true,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: true,\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"No skip\",\n\t\t\tskipApproval: false,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: false,\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Skip approval\",\n\t\t\tskipApproval: true,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: false,\n\t\t\t},\n\t\t\texpectedRes:           \"/auth/mockPw/cb\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Force approval, request refresh token\",\n\t\t\tskipApproval: false,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: true,\n\t\t\t\tScopes:              []string{\"offline_access\"},\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: true,\n\t\t},\n\t\t{\n\t\t\tname:         \"Skip approval, request refresh token\",\n\t\t\tskipApproval: true,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: false,\n\t\t\t\tScopes:              []string{\"offline_access\"},\n\t\t\t},\n\t\t\texpectedRes:           \"/auth/mockPw/cb\",\n\t\t\tofflineSessionCreated: true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.SkipApprovalScreen = tc.skipApproval\n\t\t\t\tc.Now = time.Now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tsc := storage.Connector{\n\t\t\t\tID:              connID,\n\t\t\t\tType:            \"mockPassword\",\n\t\t\t\tName:            \"MockPassword\",\n\t\t\t\tResourceVersion: \"1\",\n\t\t\t\tConfig:          []byte(\"{\\\"username\\\": \\\"foo\\\", \\\"password\\\": \\\"password\\\"}\"),\n\t\t\t}\n\t\t\tif err := s.storage.CreateConnector(ctx, sc); err != nil {\n\t\t\t\tt.Fatalf(\"create connector: %v\", err)\n\t\t\t}\n\t\t\tif _, err := s.OpenConnector(sc); err != nil {\n\t\t\t\tt.Fatalf(\"open connector: %v\", err)\n\t\t\t}\n\t\t\tif err := s.storage.CreateAuthRequest(ctx, tc.authReq); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create AuthRequest: %v\", err)\n\t\t\t}\n\n\t\t\trr := httptest.NewRecorder()\n\n\t\t\tpath := fmt.Sprintf(\"/auth/%s/login?state=%s&back=&login=foo&password=password\", connID, authReqID)\n\t\t\ts.handlePasswordLogin(rr, httptest.NewRequest(\"POST\", path, nil))\n\n\t\t\trequire.Equal(t, 303, rr.Code)\n\n\t\t\tresp := rr.Result()\n\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tcb, _ := url.Parse(resp.Header.Get(\"Location\"))\n\t\t\trequire.Equal(t, tc.expectedRes, cb.Path)\n\n\t\t\tofflineSession, err := s.storage.GetOfflineSessions(ctx, \"0-385-28089-0\", connID)\n\t\t\tif tc.offlineSessionCreated {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotEmpty(t, offlineSession)\n\t\t\t} else {\n\t\t\t\trequire.Error(t, storage.ErrNotFound, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandleClientCredentials(t *testing.T) {\n\ttests := []struct {\n\t\tname          string\n\t\tclientID      string\n\t\tclientSecret  string\n\t\tscopes        string\n\t\twantCode      int\n\t\twantAccessTok bool\n\t\twantIDToken   bool\n\t\twantUsername  string\n\t}{\n\t\t{\n\t\t\tname:          \"Basic grant, no scopes\",\n\t\t\tclientID:      \"test\",\n\t\t\tclientSecret:  \"barfoo\",\n\t\t\tscopes:        \"\",\n\t\t\twantCode:      200,\n\t\t\twantAccessTok: true,\n\t\t\twantIDToken:   false,\n\t\t},\n\t\t{\n\t\t\tname:          \"With openid scope\",\n\t\t\tclientID:      \"test\",\n\t\t\tclientSecret:  \"barfoo\",\n\t\t\tscopes:        \"openid\",\n\t\t\twantCode:      200,\n\t\t\twantAccessTok: true,\n\t\t\twantIDToken:   true,\n\t\t},\n\t\t{\n\t\t\tname:          \"With openid and profile scope includes username\",\n\t\t\tclientID:      \"test\",\n\t\t\tclientSecret:  \"barfoo\",\n\t\t\tscopes:        \"openid profile\",\n\t\t\twantCode:      200,\n\t\t\twantAccessTok: true,\n\t\t\twantIDToken:   true,\n\t\t\twantUsername:  \"Test Client\",\n\t\t},\n\t\t{\n\t\t\tname:          \"With openid email profile groups\",\n\t\t\tclientID:      \"test\",\n\t\t\tclientSecret:  \"barfoo\",\n\t\t\tscopes:        \"openid email profile groups\",\n\t\t\twantCode:      200,\n\t\t\twantAccessTok: true,\n\t\t\twantIDToken:   true,\n\t\t\twantUsername:  \"Test Client\",\n\t\t},\n\t\t{\n\t\t\tname:         \"Invalid client secret\",\n\t\t\tclientID:     \"test\",\n\t\t\tclientSecret: \"wrong\",\n\t\t\tscopes:       \"\",\n\t\t\twantCode:     401,\n\t\t},\n\t\t{\n\t\t\tname:         \"Unknown client\",\n\t\t\tclientID:     \"nonexistent\",\n\t\t\tclientSecret: \"secret\",\n\t\t\tscopes:       \"\",\n\t\t\twantCode:     401,\n\t\t},\n\t\t{\n\t\t\tname:         \"offline_access scope rejected\",\n\t\t\tclientID:     \"test\",\n\t\t\tclientSecret: \"barfoo\",\n\t\t\tscopes:       \"openid offline_access\",\n\t\t\twantCode:     400,\n\t\t},\n\t\t{\n\t\t\tname:         \"Unrecognized scope\",\n\t\t\tclientID:     \"test\",\n\t\t\tclientSecret: \"barfoo\",\n\t\t\tscopes:       \"openid bogus\",\n\t\t\twantCode:     400,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Now = time.Now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\t// Create a confidential client for testing.\n\t\t\terr := s.storage.CreateClient(ctx, storage.Client{\n\t\t\t\tID:           \"test\",\n\t\t\t\tSecret:       \"barfoo\",\n\t\t\t\tRedirectURIs: []string{\"https://example.com/callback\"},\n\t\t\t\tName:         \"Test Client\",\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\trequire.NoError(t, err)\n\t\t\tu.Path = path.Join(u.Path, \"/token\")\n\n\t\t\tv := url.Values{}\n\t\t\tv.Add(\"grant_type\", \"client_credentials\")\n\t\t\tif tc.scopes != \"\" {\n\t\t\t\tv.Add(\"scope\", tc.scopes)\n\t\t\t}\n\n\t\t\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(v.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\treq.SetBasicAuth(tc.clientID, tc.clientSecret)\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\n\t\t\trequire.Equal(t, tc.wantCode, rr.Code)\n\n\t\t\tif tc.wantCode == 200 {\n\t\t\t\tvar resp struct {\n\t\t\t\t\tAccessToken  string `json:\"access_token\"`\n\t\t\t\t\tTokenType    string `json:\"token_type\"`\n\t\t\t\t\tExpiresIn    int    `json:\"expires_in\"`\n\t\t\t\t\tIDToken      string `json:\"id_token\"`\n\t\t\t\t\tRefreshToken string `json:\"refresh_token\"`\n\t\t\t\t}\n\t\t\t\terr := json.Unmarshal(rr.Body.Bytes(), &resp)\n\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\tif tc.wantAccessTok {\n\t\t\t\t\trequire.NotEmpty(t, resp.AccessToken)\n\t\t\t\t\trequire.Equal(t, \"bearer\", resp.TokenType)\n\t\t\t\t\trequire.Greater(t, resp.ExpiresIn, 0)\n\t\t\t\t}\n\t\t\t\tif tc.wantIDToken {\n\t\t\t\t\trequire.NotEmpty(t, resp.IDToken)\n\n\t\t\t\t\t// Verify the ID token claims.\n\t\t\t\t\tprovider, err := oidc.NewProvider(ctx, httpServer.URL)\n\t\t\t\t\trequire.NoError(t, err)\n\t\t\t\t\tverifier := provider.Verifier(&oidc.Config{ClientID: tc.clientID})\n\t\t\t\t\tidToken, err := verifier.Verify(ctx, resp.IDToken)\n\t\t\t\t\trequire.NoError(t, err)\n\n\t\t\t\t\t// Decode the subject to verify the connector ID.\n\t\t\t\t\tvar sub internal.IDTokenSubject\n\t\t\t\t\trequire.NoError(t, internal.Unmarshal(idToken.Subject, &sub))\n\t\t\t\t\trequire.Equal(t, \"\", sub.ConnId)\n\t\t\t\t\trequire.Equal(t, tc.clientID, sub.UserId)\n\n\t\t\t\t\tvar claims struct {\n\t\t\t\t\t\tName              string `json:\"name\"`\n\t\t\t\t\t\tPreferredUsername string `json:\"preferred_username\"`\n\t\t\t\t\t}\n\t\t\t\t\trequire.NoError(t, idToken.Claims(&claims))\n\n\t\t\t\t\tif tc.wantUsername != \"\" {\n\t\t\t\t\t\trequire.Equal(t, tc.wantUsername, claims.Name)\n\t\t\t\t\t\trequire.Equal(t, tc.wantUsername, claims.PreferredUsername)\n\t\t\t\t\t} else {\n\t\t\t\t\t\trequire.Empty(t, claims.Name)\n\t\t\t\t\t\trequire.Empty(t, claims.PreferredUsername)\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\trequire.Empty(t, resp.IDToken)\n\t\t\t\t}\n\t\t\t\t// client_credentials must never return a refresh token.\n\t\t\t\trequire.Empty(t, resp.RefreshToken)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandleConnectorCallbackWithSkipApproval(t *testing.T) {\n\tctx := t.Context()\n\n\tconnID := \"mock\"\n\tauthReqID := \"test\"\n\texpiry := time.Now().Add(100 * time.Second)\n\tresTypes := []string{responseTypeCode}\n\n\ttests := []struct {\n\t\tname                  string\n\t\tskipApproval          bool\n\t\tauthReq               storage.AuthRequest\n\t\texpectedRes           string\n\t\tofflineSessionCreated bool\n\t}{\n\t\t{\n\t\t\tname:         \"Force approval\",\n\t\t\tskipApproval: false,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: true,\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Skip approval by server config\",\n\t\t\tskipApproval: true,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: true,\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Skip approval by auth request\",\n\t\t\tskipApproval: false,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: false,\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Skip approval\",\n\t\t\tskipApproval: true,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: false,\n\t\t\t},\n\t\t\texpectedRes:           \"/callback/cb\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t\t{\n\t\t\tname:         \"Force approval, request refresh token\",\n\t\t\tskipApproval: false,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: true,\n\t\t\t\tScopes:              []string{\"offline_access\"},\n\t\t\t},\n\t\t\texpectedRes:           \"/approval\",\n\t\t\tofflineSessionCreated: true,\n\t\t},\n\t\t{\n\t\t\tname:         \"Skip approval, request refresh token\",\n\t\t\tskipApproval: true,\n\t\t\tauthReq: storage.AuthRequest{\n\t\t\t\tID:                  authReqID,\n\t\t\t\tConnectorID:         connID,\n\t\t\t\tRedirectURI:         \"cb\",\n\t\t\t\tExpiry:              expiry,\n\t\t\t\tResponseTypes:       resTypes,\n\t\t\t\tForceApprovalPrompt: false,\n\t\t\t\tScopes:              []string{\"offline_access\"},\n\t\t\t},\n\t\t\texpectedRes:           \"/callback/cb\",\n\t\t\tofflineSessionCreated: false,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.SkipApprovalScreen = tc.skipApproval\n\t\t\t\tc.Now = time.Now\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tif err := s.storage.CreateAuthRequest(ctx, tc.authReq); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create AuthRequest: %v\", err)\n\t\t\t}\n\t\t\trr := httptest.NewRecorder()\n\n\t\t\tpath := fmt.Sprintf(\"/callback/%s?state=%s\", connID, authReqID)\n\t\t\ts.handleConnectorCallback(rr, httptest.NewRequest(\"GET\", path, nil))\n\n\t\t\trequire.Equal(t, 303, rr.Code)\n\n\t\t\tresp := rr.Result()\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tcb, _ := url.Parse(resp.Header.Get(\"Location\"))\n\t\t\trequire.Equal(t, tc.expectedRes, cb.Path)\n\n\t\t\tofflineSession, err := s.storage.GetOfflineSessions(ctx, \"0-385-28089-0\", connID)\n\t\t\tif tc.offlineSessionCreated {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.NotEmpty(t, offlineSession)\n\t\t\t} else {\n\t\t\t\trequire.Error(t, storage.ErrNotFound, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandleTokenExchange(t *testing.T) {\n\ttests := []struct {\n\t\tname               string\n\t\tscope              string\n\t\trequestedTokenType string\n\t\tsubjectTokenType   string\n\t\tsubjectToken       string\n\n\t\texpectedCode      int\n\t\texpectedTokenType string\n\t}{\n\t\t{\n\t\t\t\"id-for-acccess\",\n\t\t\t\"openid\",\n\t\t\ttokenTypeAccess,\n\t\t\ttokenTypeID,\n\t\t\t\"foobar\",\n\t\t\thttp.StatusOK,\n\t\t\ttokenTypeAccess,\n\t\t},\n\t\t{\n\t\t\t\"id-for-id\",\n\t\t\t\"openid\",\n\t\t\ttokenTypeID,\n\t\t\ttokenTypeID,\n\t\t\t\"foobar\",\n\t\t\thttp.StatusOK,\n\t\t\ttokenTypeID,\n\t\t},\n\t\t{\n\t\t\t\"id-for-default\",\n\t\t\t\"openid\",\n\t\t\t\"\",\n\t\t\ttokenTypeID,\n\t\t\t\"foobar\",\n\t\t\thttp.StatusOK,\n\t\t\ttokenTypeAccess,\n\t\t},\n\t\t{\n\t\t\t\"access-for-access\",\n\t\t\t\"openid\",\n\t\t\ttokenTypeAccess,\n\t\t\ttokenTypeAccess,\n\t\t\t\"foobar\",\n\t\t\thttp.StatusOK,\n\t\t\ttokenTypeAccess,\n\t\t},\n\t\t{\n\t\t\t\"missing-subject_token_type\",\n\t\t\t\"openid\",\n\t\t\ttokenTypeAccess,\n\t\t\t\"\",\n\t\t\t\"foobar\",\n\t\t\thttp.StatusBadRequest,\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\t\"missing-subject_token\",\n\t\t\t\"openid\",\n\t\t\ttokenTypeAccess,\n\t\t\ttokenTypeAccess,\n\t\t\t\"\",\n\t\t\thttp.StatusBadRequest,\n\t\t\t\"\",\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Storage.CreateClient(ctx, storage.Client{\n\t\t\t\t\tID:     \"client_1\",\n\t\t\t\t\tSecret: \"secret_1\",\n\t\t\t\t})\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\t\t\tvals := make(url.Values)\n\t\t\tvals.Set(\"grant_type\", grantTypeTokenExchange)\n\t\t\tsetNonEmpty(vals, \"connector_id\", \"mock\")\n\t\t\tsetNonEmpty(vals, \"scope\", tc.scope)\n\t\t\tsetNonEmpty(vals, \"requested_token_type\", tc.requestedTokenType)\n\t\t\tsetNonEmpty(vals, \"subject_token_type\", tc.subjectTokenType)\n\t\t\tsetNonEmpty(vals, \"subject_token\", tc.subjectToken)\n\t\t\tsetNonEmpty(vals, \"client_id\", \"client_1\")\n\t\t\tsetNonEmpty(vals, \"client_secret\", \"secret_1\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\treq := httptest.NewRequest(http.MethodPost, httpServer.URL+\"/token\", strings.NewReader(vals.Encode()))\n\t\t\treq.Header.Set(\"content-type\", \"application/x-www-form-urlencoded\")\n\n\t\t\ts.handleToken(rr, req)\n\n\t\t\trequire.Equal(t, tc.expectedCode, rr.Code, rr.Body.String())\n\t\t\trequire.Equal(t, \"application/json\", rr.Result().Header.Get(\"content-type\"))\n\t\t\tif tc.expectedCode == http.StatusOK {\n\t\t\t\tvar res accessTokenResponse\n\t\t\t\terr := json.NewDecoder(rr.Result().Body).Decode(&res)\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, tc.expectedTokenType, res.IssuedTokenType)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandleTokenExchangeConnectorGrantTypeRestriction(t *testing.T) {\n\tctx := t.Context()\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Storage.CreateClient(ctx, storage.Client{\n\t\t\tID:     \"client_1\",\n\t\t\tSecret: \"secret_1\",\n\t\t})\n\t})\n\tdefer httpServer.Close()\n\n\t// Restrict mock connector to authorization_code only\n\terr := s.storage.UpdateConnector(ctx, \"mock\", func(c storage.Connector) (storage.Connector, error) {\n\t\tc.GrantTypes = []string{grantTypeAuthorizationCode}\n\t\treturn c, nil\n\t})\n\trequire.NoError(t, err)\n\t// Clear cached connector to pick up new grant types\n\ts.mu.Lock()\n\tdelete(s.connectors, \"mock\")\n\ts.mu.Unlock()\n\n\tvals := make(url.Values)\n\tvals.Set(\"grant_type\", grantTypeTokenExchange)\n\tvals.Set(\"connector_id\", \"mock\")\n\tvals.Set(\"scope\", \"openid\")\n\tvals.Set(\"requested_token_type\", tokenTypeAccess)\n\tvals.Set(\"subject_token_type\", tokenTypeID)\n\tvals.Set(\"subject_token\", \"foobar\")\n\tvals.Set(\"client_id\", \"client_1\")\n\tvals.Set(\"client_secret\", \"secret_1\")\n\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(http.MethodPost, httpServer.URL+\"/token\", strings.NewReader(vals.Encode()))\n\treq.Header.Set(\"content-type\", \"application/x-www-form-urlencoded\")\n\n\ts.handleToken(rr, req)\n\n\trequire.Equal(t, http.StatusBadRequest, rr.Code, rr.Body.String())\n}\n\nfunc TestHandleAuthorizationConnectorGrantTypeFiltering(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\t// grantTypes per connector ID; nil means unrestricted\n\t\tconnectorGrantTypes map[string][]string\n\t\tresponseType        string\n\t\twantCode            int\n\t\t// wantRedirectContains is checked when wantCode == 302\n\t\twantRedirectContains string\n\t\t// wantBodyContains is checked when wantCode != 302\n\t\twantBodyContains string\n\t}{\n\t\t{\n\t\t\tname: \"one connector filtered, redirect to remaining\",\n\t\t\tconnectorGrantTypes: map[string][]string{\n\t\t\t\t\"mock\":  {grantTypeDeviceCode},\n\t\t\t\t\"mock2\": nil,\n\t\t\t},\n\t\t\tresponseType:         \"code\",\n\t\t\twantCode:             http.StatusFound,\n\t\t\twantRedirectContains: \"/auth/mock2\",\n\t\t},\n\t\t{\n\t\t\tname: \"all connectors filtered\",\n\t\t\tconnectorGrantTypes: map[string][]string{\n\t\t\t\t\"mock\":  {grantTypeDeviceCode},\n\t\t\t\t\"mock2\": {grantTypeDeviceCode},\n\t\t\t},\n\t\t\tresponseType:     \"code\",\n\t\t\twantCode:         http.StatusBadRequest,\n\t\t\twantBodyContains: \"No connectors available\",\n\t\t},\n\t\t{\n\t\t\tname: \"no restrictions, both available\",\n\t\t\tconnectorGrantTypes: map[string][]string{\n\t\t\t\t\"mock\":  nil,\n\t\t\t\t\"mock2\": nil,\n\t\t\t},\n\t\t\tresponseType: \"code\",\n\t\t\twantCode:     http.StatusOK,\n\t\t},\n\t\t{\n\t\t\tname: \"implicit flow filters auth_code-only connector\",\n\t\t\tconnectorGrantTypes: map[string][]string{\n\t\t\t\t\"mock\":  {grantTypeAuthorizationCode},\n\t\t\t\t\"mock2\": nil,\n\t\t\t},\n\t\t\tresponseType:         \"token\",\n\t\t\twantCode:             http.StatusFound,\n\t\t\twantRedirectContains: \"/auth/mock2\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\t\t\thttpServer, s := newTestServerMultipleConnectors(t, func(c *Config) {\n\t\t\t\tc.Storage.CreateClient(ctx, storage.Client{\n\t\t\t\t\tID:           \"test\",\n\t\t\t\t\tRedirectURIs: []string{\"http://example.com/callback\"},\n\t\t\t\t})\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tfor id, gts := range tc.connectorGrantTypes {\n\t\t\t\terr := s.storage.UpdateConnector(ctx, id, func(c storage.Connector) (storage.Connector, error) {\n\t\t\t\t\tc.GrantTypes = gts\n\t\t\t\t\treturn c, nil\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\ts.mu.Lock()\n\t\t\t\tdelete(s.connectors, id)\n\t\t\t\ts.mu.Unlock()\n\t\t\t}\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\treqURL := fmt.Sprintf(\"%s/auth?response_type=%s&client_id=test&redirect_uri=http://example.com/callback&scope=openid\", httpServer.URL, tc.responseType)\n\t\t\treq := httptest.NewRequest(http.MethodGet, reqURL, nil)\n\t\t\ts.handleAuthorization(rr, req)\n\n\t\t\trequire.Equal(t, tc.wantCode, rr.Code)\n\t\t\tif tc.wantRedirectContains != \"\" {\n\t\t\t\trequire.Contains(t, rr.Header().Get(\"Location\"), tc.wantRedirectContains)\n\t\t\t}\n\t\t\tif tc.wantBodyContains != \"\" {\n\t\t\t\trequire.Contains(t, rr.Body.String(), tc.wantBodyContains)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestHandleConnectorLoginGrantTypeRejection(t *testing.T) {\n\tctx := t.Context()\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Storage.CreateClient(ctx, storage.Client{\n\t\t\tID:           \"test-client\",\n\t\t\tSecret:       \"secret\",\n\t\t\tRedirectURIs: []string{\"http://example.com/callback\"},\n\t\t})\n\t})\n\tdefer httpServer.Close()\n\n\t// Restrict mock connector to device_code only\n\terr := s.storage.UpdateConnector(ctx, \"mock\", func(c storage.Connector) (storage.Connector, error) {\n\t\tc.GrantTypes = []string{grantTypeDeviceCode}\n\t\treturn c, nil\n\t})\n\trequire.NoError(t, err)\n\ts.mu.Lock()\n\tdelete(s.connectors, \"mock\")\n\ts.mu.Unlock()\n\n\t// Try to use mock connector for auth code flow via the full server router\n\trr := httptest.NewRecorder()\n\treqURL := httpServer.URL + \"/auth/mock?response_type=code&client_id=test-client&redirect_uri=http://example.com/callback&scope=openid\"\n\treq := httptest.NewRequest(http.MethodGet, reqURL, nil)\n\ts.ServeHTTP(rr, req)\n\n\trequire.Equal(t, http.StatusBadRequest, rr.Code)\n\trequire.Contains(t, rr.Body.String(), \"does not support this grant type\")\n}\n\nfunc setNonEmpty(vals url.Values, key, value string) {\n\tif value != \"\" {\n\t\tvals.Set(key, value)\n\t}\n}\n\n// registerTestConnector creates a connector in storage and registers it in the server's connectors map.\nfunc registerTestConnector(t *testing.T, s *Server, connID string, c connector.Connector) {\n\tt.Helper()\n\tctx := t.Context()\n\n\tstorageConn := storage.Connector{\n\t\tID:              connID,\n\t\tType:            \"saml\",\n\t\tName:            \"Test SAML\",\n\t\tResourceVersion: \"1\",\n\t}\n\tif err := s.storage.CreateConnector(ctx, storageConn); err != nil {\n\t\tt.Fatalf(\"failed to create connector in storage: %v\", err)\n\t}\n\n\ts.mu.Lock()\n\ts.connectors[connID] = Connector{\n\t\tResourceVersion: \"1\",\n\t\tConnector:       c,\n\t}\n\ts.mu.Unlock()\n}\n\nfunc TestConnectorDataPersistence(t *testing.T) {\n\t// Test that ConnectorData is correctly stored in refresh token\n\t// and can be used for subsequent refresh operations.\n\thttpServer, server := newTestServer(t, func(c *Config) {\n\t\tc.RefreshTokenPolicy = &RefreshTokenPolicy{rotateRefreshTokens: true}\n\t})\n\tdefer httpServer.Close()\n\n\tctx := t.Context()\n\tconnID := \"saml-conndata\"\n\n\t// Create a mock SAML connector that also implements RefreshConnector\n\tmockConn := &mockSAMLRefreshConnector{\n\t\trefreshIdentity: connector.Identity{\n\t\t\tUserID:        \"refreshed-user\",\n\t\t\tUsername:      \"refreshed-name\",\n\t\t\tEmail:         \"refreshed@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"refreshed-group\"},\n\t\t},\n\t}\n\tregisterTestConnector(t, server, connID, mockConn)\n\n\t// Create client\n\tclient := storage.Client{\n\t\tID:           \"conndata-client\",\n\t\tSecret:       \"conndata-secret\",\n\t\tRedirectURIs: []string{\"https://example.com/callback\"},\n\t\tName:         \"ConnData Test Client\",\n\t}\n\trequire.NoError(t, server.storage.CreateClient(ctx, client))\n\n\t// Create refresh token with ConnectorData (simulating what HandlePOST would store)\n\tconnectorData := []byte(`{\"userID\":\"user-123\",\"username\":\"testuser\",\"email\":\"test@example.com\",\"emailVerified\":true,\"groups\":[\"admin\",\"dev\"]}`)\n\trefreshToken := storage.RefreshToken{\n\t\tID:          \"conndata-refresh\",\n\t\tToken:       \"conndata-token\",\n\t\tCreatedAt:   time.Now(),\n\t\tLastUsed:    time.Now(),\n\t\tClientID:    client.ID,\n\t\tConnectorID: connID,\n\t\tScopes:      []string{\"openid\", \"email\", \"offline_access\"},\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"user-123\",\n\t\t\tUsername:      \"testuser\",\n\t\t\tEmail:         \"test@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"admin\", \"dev\"},\n\t\t},\n\t\tConnectorData: connectorData,\n\t\tNonce:         \"conndata-nonce\",\n\t}\n\trequire.NoError(t, server.storage.CreateRefresh(ctx, refreshToken))\n\n\tofflineSession := storage.OfflineSessions{\n\t\tUserID:        \"user-123\",\n\t\tConnID:        connID,\n\t\tRefresh:       map[string]*storage.RefreshTokenRef{client.ID: {ID: refreshToken.ID, ClientID: client.ID}},\n\t\tConnectorData: connectorData,\n\t}\n\trequire.NoError(t, server.storage.CreateOfflineSessions(ctx, offlineSession))\n\n\t// Verify ConnectorData is stored correctly\n\tstoredToken, err := server.storage.GetRefresh(ctx, refreshToken.ID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, connectorData, storedToken.ConnectorData,\n\t\t\"ConnectorData should be persisted in refresh token storage\")\n\n\t// Verify ConnectorData is stored in offline session\n\tstoredSession, err := server.storage.GetOfflineSessions(ctx, \"user-123\", connID)\n\trequire.NoError(t, err)\n\trequire.Equal(t, connectorData, storedSession.ConnectorData,\n\t\t\"ConnectorData should be persisted in offline session storage\")\n}\n\n// mockSAMLRefreshConnector implements SAMLConnector + RefreshConnector for testing.\ntype mockSAMLRefreshConnector struct {\n\trefreshIdentity connector.Identity\n}\n\nfunc (m *mockSAMLRefreshConnector) POSTData(s connector.Scopes, requestID string) (ssoURL, samlRequest string, err error) {\n\treturn \"\", \"\", nil\n}\n\nfunc (m *mockSAMLRefreshConnector) HandlePOST(s connector.Scopes, samlResponse, inResponseTo string) (connector.Identity, error) {\n\treturn connector.Identity{}, nil\n}\n\nfunc (m *mockSAMLRefreshConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {\n\treturn m.refreshIdentity, nil\n}\n\nfunc TestFilterConnectors(t *testing.T) {\n\tconnectors := []storage.Connector{\n\t\t{ID: \"github\", Type: \"github\", Name: \"GitHub\"},\n\t\t{ID: \"google\", Type: \"oidc\", Name: \"Google\"},\n\t\t{ID: \"ldap\", Type: \"ldap\", Name: \"LDAP\"},\n\t}\n\n\ttests := []struct {\n\t\tname              string\n\t\tallowedConnectors []string\n\t\twantIDs           []string\n\t}{\n\t\t{\n\t\t\tname:              \"No filter - all connectors returned\",\n\t\t\tallowedConnectors: nil,\n\t\t\twantIDs:           []string{\"github\", \"google\", \"ldap\"},\n\t\t},\n\t\t{\n\t\t\tname:              \"Empty filter - all connectors returned\",\n\t\t\tallowedConnectors: []string{},\n\t\t\twantIDs:           []string{\"github\", \"google\", \"ldap\"},\n\t\t},\n\t\t{\n\t\t\tname:              \"Filter to one connector\",\n\t\t\tallowedConnectors: []string{\"github\"},\n\t\t\twantIDs:           []string{\"github\"},\n\t\t},\n\t\t{\n\t\t\tname:              \"Filter to two connectors\",\n\t\t\tallowedConnectors: []string{\"github\", \"ldap\"},\n\t\t\twantIDs:           []string{\"github\", \"ldap\"},\n\t\t},\n\t\t{\n\t\t\tname:              \"Filter with non-existent connector ID\",\n\t\t\tallowedConnectors: []string{\"nonexistent\"},\n\t\t\twantIDs:           []string{},\n\t\t},\n\t\t{\n\t\t\tname:              \"Filter with mix of valid and invalid IDs\",\n\t\t\tallowedConnectors: []string{\"google\", \"nonexistent\"},\n\t\t\twantIDs:           []string{\"google\"},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tresult := filterConnectors(connectors, tc.allowedConnectors)\n\t\t\tgotIDs := make([]string, len(result))\n\t\t\tfor i, c := range result {\n\t\t\t\tgotIDs[i] = c.ID\n\t\t\t}\n\t\t\trequire.Equal(t, tc.wantIDs, gotIDs)\n\t\t})\n\t}\n}\n\nfunc TestIsConnectorAllowed(t *testing.T) {\n\ttests := []struct {\n\t\tname              string\n\t\tallowedConnectors []string\n\t\tconnectorID       string\n\t\twant              bool\n\t}{\n\t\t{\n\t\t\tname:              \"No restrictions - all allowed\",\n\t\t\tallowedConnectors: nil,\n\t\t\tconnectorID:       \"any\",\n\t\t\twant:              true,\n\t\t},\n\t\t{\n\t\t\tname:              \"Empty list - all allowed\",\n\t\t\tallowedConnectors: []string{},\n\t\t\tconnectorID:       \"any\",\n\t\t\twant:              true,\n\t\t},\n\t\t{\n\t\t\tname:              \"Connector in allowed list\",\n\t\t\tallowedConnectors: []string{\"github\", \"google\"},\n\t\t\tconnectorID:       \"github\",\n\t\t\twant:              true,\n\t\t},\n\t\t{\n\t\t\tname:              \"Connector not in allowed list\",\n\t\t\tallowedConnectors: []string{\"github\", \"google\"},\n\t\t\tconnectorID:       \"ldap\",\n\t\t\twant:              false,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot := isConnectorAllowed(tc.allowedConnectors, tc.connectorID)\n\t\t\trequire.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n\nfunc TestHandleAuthorizationWithAllowedConnectors(t *testing.T) {\n\tctx := t.Context()\n\n\thttpServer, s := newTestServerMultipleConnectors(t, nil)\n\tdefer httpServer.Close()\n\n\t// Create a client that only allows \"mock\" connector (not \"mock2\")\n\tclient := storage.Client{\n\t\tID:                \"filtered-client\",\n\t\tSecret:            \"secret\",\n\t\tRedirectURIs:      []string{\"https://example.com/callback\"},\n\t\tName:              \"Filtered Client\",\n\t\tAllowedConnectors: []string{\"mock\"},\n\t}\n\trequire.NoError(t, s.storage.CreateClient(ctx, client))\n\n\t// Request the auth page with this client - should only show \"mock\" connector\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", fmt.Sprintf(\"/auth?client_id=%s&redirect_uri=%s&response_type=code&scope=openid\",\n\t\tclient.ID, url.QueryEscape(\"https://example.com/callback\")), nil)\n\ts.ServeHTTP(rr, req)\n\n\t// With only one allowed connector and alwaysShowLogin=false (default),\n\t// the server should redirect directly to the connector\n\trequire.Equal(t, http.StatusFound, rr.Code)\n\tlocation := rr.Header().Get(\"Location\")\n\trequire.Contains(t, location, \"/auth/mock\")\n\trequire.NotContains(t, location, \"mock2\")\n}\n\nfunc TestHandleAuthorizationWithNoMatchingConnectors(t *testing.T) {\n\tctx := t.Context()\n\n\thttpServer, s := newTestServerMultipleConnectors(t, nil)\n\tdefer httpServer.Close()\n\n\t// Create a client that only allows a non-existent connector\n\tclient := storage.Client{\n\t\tID:                \"no-connectors-client\",\n\t\tSecret:            \"secret\",\n\t\tRedirectURIs:      []string{\"https://example.com/callback\"},\n\t\tName:              \"No Connectors Client\",\n\t\tAllowedConnectors: []string{\"nonexistent\"},\n\t}\n\trequire.NoError(t, s.storage.CreateClient(ctx, client))\n\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", fmt.Sprintf(\"/auth?client_id=%s&redirect_uri=%s&response_type=code&scope=openid\",\n\t\tclient.ID, url.QueryEscape(\"https://example.com/callback\")), nil)\n\ts.ServeHTTP(rr, req)\n\n\t// Should return an error, not an empty login page\n\trequire.Equal(t, http.StatusBadRequest, rr.Code)\n}\n\nfunc TestHandleAuthorizationWithoutAllowedConnectors(t *testing.T) {\n\tctx := t.Context()\n\n\thttpServer, s := newTestServerMultipleConnectors(t, nil)\n\tdefer httpServer.Close()\n\n\t// Create a client with no connector restrictions\n\tclient := storage.Client{\n\t\tID:           \"unfiltered-client\",\n\t\tSecret:       \"secret\",\n\t\tRedirectURIs: []string{\"https://example.com/callback\"},\n\t\tName:         \"Unfiltered Client\",\n\t}\n\trequire.NoError(t, s.storage.CreateClient(ctx, client))\n\n\t// Request the auth page - should show all connectors (rendered as HTML)\n\trr := httptest.NewRecorder()\n\treq := httptest.NewRequest(\"GET\", fmt.Sprintf(\"/auth?client_id=%s&redirect_uri=%s&response_type=code&scope=openid\",\n\t\tclient.ID, url.QueryEscape(\"https://example.com/callback\")), nil)\n\ts.ServeHTTP(rr, req)\n\n\t// With multiple connectors and no filter, the login page should be rendered (200 OK)\n\trequire.Equal(t, http.StatusOK, rr.Code)\n}\n"
  },
  {
    "path": "server/internal/codec.go",
    "content": "package internal\n\nimport (\n\t\"encoding/base64\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\n// Marshal converts a protobuf message to a URL legal string.\nfunc Marshal(message proto.Message) (string, error) {\n\tdata, err := proto.Marshal(message)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn base64.RawURLEncoding.EncodeToString(data), nil\n}\n\n// Unmarshal decodes a protobuf message.\nfunc Unmarshal(s string, message proto.Message) error {\n\tdata, err := base64.RawURLEncoding.DecodeString(s)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn proto.Unmarshal(data, message)\n}\n"
  },
  {
    "path": "server/internal/types.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// versions:\n// \tprotoc-gen-go v1.36.5\n// \tprotoc        v5.29.3\n// source: server/internal/types.proto\n\n// Package internal holds protobuf types used by the server.\n\npackage internal\n\nimport (\n\tprotoreflect \"google.golang.org/protobuf/reflect/protoreflect\"\n\tprotoimpl \"google.golang.org/protobuf/runtime/protoimpl\"\n\treflect \"reflect\"\n\tsync \"sync\"\n\tunsafe \"unsafe\"\n)\n\nconst (\n\t// Verify that this generated code is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)\n\t// Verify that runtime/protoimpl is sufficiently up-to-date.\n\t_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)\n)\n\n// RefreshToken is a message that holds refresh token data used by dex.\ntype RefreshToken struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tRefreshId     string                 `protobuf:\"bytes,1,opt,name=refresh_id,json=refreshId,proto3\" json:\"refresh_id,omitempty\"`\n\tToken         string                 `protobuf:\"bytes,2,opt,name=token,proto3\" json:\"token,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *RefreshToken) Reset() {\n\t*x = RefreshToken{}\n\tmi := &file_server_internal_types_proto_msgTypes[0]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *RefreshToken) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*RefreshToken) ProtoMessage() {}\n\nfunc (x *RefreshToken) ProtoReflect() protoreflect.Message {\n\tmi := &file_server_internal_types_proto_msgTypes[0]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use RefreshToken.ProtoReflect.Descriptor instead.\nfunc (*RefreshToken) Descriptor() ([]byte, []int) {\n\treturn file_server_internal_types_proto_rawDescGZIP(), []int{0}\n}\n\nfunc (x *RefreshToken) GetRefreshId() string {\n\tif x != nil {\n\t\treturn x.RefreshId\n\t}\n\treturn \"\"\n}\n\nfunc (x *RefreshToken) GetToken() string {\n\tif x != nil {\n\t\treturn x.Token\n\t}\n\treturn \"\"\n}\n\n// IDTokenSubject represents both the userID and connID which is returned\n// as the \"sub\" claim in the ID Token.\ntype IDTokenSubject struct {\n\tstate         protoimpl.MessageState `protogen:\"open.v1\"`\n\tUserId        string                 `protobuf:\"bytes,1,opt,name=user_id,json=userId,proto3\" json:\"user_id,omitempty\"`\n\tConnId        string                 `protobuf:\"bytes,2,opt,name=conn_id,json=connId,proto3\" json:\"conn_id,omitempty\"`\n\tunknownFields protoimpl.UnknownFields\n\tsizeCache     protoimpl.SizeCache\n}\n\nfunc (x *IDTokenSubject) Reset() {\n\t*x = IDTokenSubject{}\n\tmi := &file_server_internal_types_proto_msgTypes[1]\n\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\tms.StoreMessageInfo(mi)\n}\n\nfunc (x *IDTokenSubject) String() string {\n\treturn protoimpl.X.MessageStringOf(x)\n}\n\nfunc (*IDTokenSubject) ProtoMessage() {}\n\nfunc (x *IDTokenSubject) ProtoReflect() protoreflect.Message {\n\tmi := &file_server_internal_types_proto_msgTypes[1]\n\tif x != nil {\n\t\tms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))\n\t\tif ms.LoadMessageInfo() == nil {\n\t\t\tms.StoreMessageInfo(mi)\n\t\t}\n\t\treturn ms\n\t}\n\treturn mi.MessageOf(x)\n}\n\n// Deprecated: Use IDTokenSubject.ProtoReflect.Descriptor instead.\nfunc (*IDTokenSubject) Descriptor() ([]byte, []int) {\n\treturn file_server_internal_types_proto_rawDescGZIP(), []int{1}\n}\n\nfunc (x *IDTokenSubject) GetUserId() string {\n\tif x != nil {\n\t\treturn x.UserId\n\t}\n\treturn \"\"\n}\n\nfunc (x *IDTokenSubject) GetConnId() string {\n\tif x != nil {\n\t\treturn x.ConnId\n\t}\n\treturn \"\"\n}\n\nvar File_server_internal_types_proto protoreflect.FileDescriptor\n\nvar file_server_internal_types_proto_rawDesc = string([]byte{\n\t0x0a, 0x1b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,\n\t0x6c, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x69,\n\t0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x43, 0x0a, 0x0c, 0x52, 0x65, 0x66, 0x72, 0x65,\n\t0x73, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x66, 0x72, 0x65,\n\t0x73, 0x68, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x72, 0x65, 0x66,\n\t0x72, 0x65, 0x73, 0x68, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,\n\t0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x42, 0x0a, 0x0e,\n\t0x49, 0x44, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x53, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x17,\n\t0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,\n\t0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x6e, 0x5f,\n\t0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x6e, 0x49, 0x64,\n\t0x42, 0x27, 0x5a, 0x25, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64,\n\t0x65, 0x78, 0x69, 0x64, 0x70, 0x2f, 0x64, 0x65, 0x78, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72,\n\t0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,\n\t0x33,\n})\n\nvar (\n\tfile_server_internal_types_proto_rawDescOnce sync.Once\n\tfile_server_internal_types_proto_rawDescData []byte\n)\n\nfunc file_server_internal_types_proto_rawDescGZIP() []byte {\n\tfile_server_internal_types_proto_rawDescOnce.Do(func() {\n\t\tfile_server_internal_types_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_server_internal_types_proto_rawDesc), len(file_server_internal_types_proto_rawDesc)))\n\t})\n\treturn file_server_internal_types_proto_rawDescData\n}\n\nvar file_server_internal_types_proto_msgTypes = make([]protoimpl.MessageInfo, 2)\nvar file_server_internal_types_proto_goTypes = []any{\n\t(*RefreshToken)(nil),   // 0: internal.RefreshToken\n\t(*IDTokenSubject)(nil), // 1: internal.IDTokenSubject\n}\nvar file_server_internal_types_proto_depIdxs = []int32{\n\t0, // [0:0] is the sub-list for method output_type\n\t0, // [0:0] is the sub-list for method input_type\n\t0, // [0:0] is the sub-list for extension type_name\n\t0, // [0:0] is the sub-list for extension extendee\n\t0, // [0:0] is the sub-list for field type_name\n}\n\nfunc init() { file_server_internal_types_proto_init() }\nfunc file_server_internal_types_proto_init() {\n\tif File_server_internal_types_proto != nil {\n\t\treturn\n\t}\n\ttype x struct{}\n\tout := protoimpl.TypeBuilder{\n\t\tFile: protoimpl.DescBuilder{\n\t\t\tGoPackagePath: reflect.TypeOf(x{}).PkgPath(),\n\t\t\tRawDescriptor: unsafe.Slice(unsafe.StringData(file_server_internal_types_proto_rawDesc), len(file_server_internal_types_proto_rawDesc)),\n\t\t\tNumEnums:      0,\n\t\t\tNumMessages:   2,\n\t\t\tNumExtensions: 0,\n\t\t\tNumServices:   0,\n\t\t},\n\t\tGoTypes:           file_server_internal_types_proto_goTypes,\n\t\tDependencyIndexes: file_server_internal_types_proto_depIdxs,\n\t\tMessageInfos:      file_server_internal_types_proto_msgTypes,\n\t}.Build()\n\tFile_server_internal_types_proto = out.File\n\tfile_server_internal_types_proto_goTypes = nil\n\tfile_server_internal_types_proto_depIdxs = nil\n}\n"
  },
  {
    "path": "server/internal/types.proto",
    "content": "syntax = \"proto3\";\n\n// Package internal holds protobuf types used by the server.\npackage internal;\n\noption go_package = \"github.com/dexidp/dex/server/internal\";\n\n// RefreshToken is a message that holds refresh token data used by dex.\nmessage RefreshToken {\n  string refresh_id = 1;\n  string token = 2;\n}\n\n// IDTokenSubject represents both the userID and connID which is returned\n// as the \"sub\" claim in the ID Token.\nmessage IDTokenSubject {\n  string user_id = 1;\n  string conn_id = 2;\n}\n"
  },
  {
    "path": "server/introspectionhandler.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\n\t\"github.com/dexidp/dex/server/internal\"\n)\n\n// Introspection contains an access token's session data as specified by\n// [IETF RFC 7662](https://tools.ietf.org/html/rfc7662)\ntype Introspection struct {\n\t// Boolean indicator of whether or not the presented token\n\t// is currently active.  The specifics of a token's \"active\" state\n\t// will vary depending on the implementation of the authorization\n\t// server and the information it keeps about its tokens, but a \"true\"\n\t// value return for the \"active\" property will generally indicate\n\t// that a given token has been issued by this authorization server,\n\t// has not been revoked by the resource owner, and is within its\n\t// given time window of validity (e.g., after its issuance time and\n\t// before its expiration time).\n\tActive bool `json:\"active\"`\n\n\t// JSON string containing a space-separated list of\n\t// scopes associated with this token.\n\tScope string `json:\"scope,omitempty\"`\n\n\t// Client identifier for the OAuth 2.0 client that\n\t// requested this token.\n\tClientID string `json:\"client_id\"`\n\n\t// Subject of the token, as defined in JWT [RFC7519].\n\t// Usually a machine-readable identifier of the resource owner who\n\t// authorized this token.\n\tSubject string `json:\"sub\"`\n\n\t// Integer timestamp, measured in the number of seconds\n\t// since January 1 1970 UTC, indicating when this token will expire.\n\tExpiry int64 `json:\"exp\"`\n\n\t// Integer timestamp, measured in the number of seconds\n\t// since January 1 1970 UTC, indicating when this token was\n\t// originally issued.\n\tIssuedAt int64 `json:\"iat\"`\n\n\t// Integer timestamp, measured in the number of seconds\n\t// since January 1 1970 UTC, indicating when this token is not to be\n\t// used before.\n\tNotBefore int64 `json:\"nbf\"`\n\n\t// Human-readable identifier for the resource owner who\n\t// authorized this token.\n\tUsername string `json:\"username,omitempty\"`\n\n\t// Service-specific string identifier or list of string\n\t// identifiers representing the intended audience for this token, as\n\t// defined in JWT\n\tAudience audience `json:\"aud\"`\n\n\t// String representing the issuer of this token, as\n\t// defined in JWT\n\tIssuer string `json:\"iss\"`\n\n\t// String identifier for the token, as defined in JWT [RFC7519].\n\tJwtTokenID string `json:\"jti,omitempty\"`\n\n\t// TokenType is the introspected token's type, typically `bearer`.\n\tTokenType string `json:\"token_type\"`\n\n\t// TokenUse is the introspected token's use, for example `access_token` or `refresh_token`.\n\tTokenUse string `json:\"token_use\"`\n\n\t// Extra is arbitrary data set from the token claims.\n\tExtra IntrospectionExtra `json:\"ext,omitempty\"`\n}\n\ntype IntrospectionExtra struct {\n\tAuthorizingParty string `json:\"azp,omitempty\"`\n\n\tEmail         string `json:\"email,omitempty\"`\n\tEmailVerified *bool  `json:\"email_verified,omitempty\"`\n\n\tGroups []string `json:\"groups,omitempty\"`\n\n\tName              string `json:\"name,omitempty\"`\n\tPreferredUsername string `json:\"preferred_username,omitempty\"`\n\n\tFederatedIDClaims *federatedIDClaims `json:\"federated_claims,omitempty\"`\n}\n\ntype TokenTypeEnum int\n\nconst (\n\tAccessToken TokenTypeEnum = iota\n\tRefreshToken\n)\n\nfunc (t TokenTypeEnum) String() string {\n\tswitch t {\n\tcase AccessToken:\n\t\treturn \"access_token\"\n\tcase RefreshToken:\n\t\treturn \"refresh_token\"\n\tdefault:\n\t\treturn fmt.Sprintf(\"TokenTypeEnum(%d)\", t)\n\t}\n}\n\ntype introspectionError struct {\n\ttyp  string\n\tcode int\n\tdesc string\n}\n\nfunc (e *introspectionError) Error() string {\n\treturn fmt.Sprintf(\"introspection error: status %d, %q %s\", e.code, e.typ, e.desc)\n}\n\nfunc (e *introspectionError) Is(tgt error) bool {\n\ttarget, ok := tgt.(*introspectionError)\n\tif !ok {\n\t\treturn false\n\t}\n\n\treturn e.typ == target.typ &&\n\t\te.code == target.code &&\n\t\te.desc == target.desc\n}\n\nfunc newIntrospectInactiveTokenError() *introspectionError {\n\treturn &introspectionError{typ: errInactiveToken, desc: \"\", code: http.StatusUnauthorized}\n}\n\nfunc newIntrospectInternalServerError() *introspectionError {\n\treturn &introspectionError{typ: errServerError, desc: \"\", code: http.StatusInternalServerError}\n}\n\nfunc newIntrospectBadRequestError(desc string) *introspectionError {\n\treturn &introspectionError{typ: errInvalidRequest, desc: desc, code: http.StatusBadRequest}\n}\n\nfunc (s *Server) guessTokenType(ctx context.Context, token string) (TokenTypeEnum, error) {\n\t// We skip every checks, we only want to know if it's a valid JWT\n\tverifierConfig := oidc.Config{\n\t\tSkipClientIDCheck: true,\n\t\tSkipExpiryCheck:   true,\n\t\tSkipIssuerCheck:   true,\n\n\t\t// We skip signature checks to avoid database calls;\n\t\tInsecureSkipSignatureCheck: true,\n\t}\n\n\tverifier := oidc.NewVerifier(s.issuerURL.String(), nil, &verifierConfig)\n\tif _, err := verifier.Verify(ctx, token); err != nil {\n\t\t// If it's not an access token, let's assume it's a refresh token;\n\t\treturn RefreshToken, nil\n\t}\n\n\t// If it's a valid JWT, it's an access token.\n\treturn AccessToken, nil\n}\n\nfunc (s *Server) getTokenFromRequest(r *http.Request) (string, TokenTypeEnum, error) {\n\tif r.Method != \"POST\" {\n\t\treturn \"\", 0, newIntrospectBadRequestError(fmt.Sprintf(\"HTTP method is \\\"%s\\\", expected \\\"POST\\\".\", r.Method))\n\t} else if err := r.ParseForm(); err != nil {\n\t\treturn \"\", 0, newIntrospectBadRequestError(\"Unable to parse HTTP body, make sure to send a properly formatted form request body.\")\n\t} else if len(r.PostForm) == 0 {\n\t\treturn \"\", 0, newIntrospectBadRequestError(\"The POST body can not be empty.\")\n\t} else if !r.PostForm.Has(\"token\") {\n\t\treturn \"\", 0, newIntrospectBadRequestError(\"The POST body doesn't contain 'token' parameter.\")\n\t}\n\n\ttoken := r.PostForm.Get(\"token\")\n\ttokenType, err := s.guessTokenType(r.Context(), token)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to guess token type\", \"err\", err)\n\t\treturn \"\", 0, newIntrospectInternalServerError()\n\t}\n\n\trequestTokenType := r.PostForm.Get(\"token_type_hint\")\n\tif requestTokenType != \"\" {\n\t\tif tokenType.String() != requestTokenType {\n\t\t\ts.logger.Warn(\"token type hint doesn't match token type\", \"request_token_type\", requestTokenType, \"token_type\", tokenType)\n\t\t}\n\t}\n\n\treturn token, tokenType, nil\n}\n\nfunc (s *Server) introspectRefreshToken(ctx context.Context, token string) (*Introspection, error) {\n\trToken := new(internal.RefreshToken)\n\tif err := internal.Unmarshal(token, rToken); err != nil {\n\t\t// For backward compatibility, assume the refresh_token is a raw refresh token ID\n\t\t// if it fails to decode.\n\t\t//\n\t\t// Because refresh_token values that aren't unmarshable were generated by servers\n\t\t// that don't have a Token value, we'll still reject any attempts to claim a\n\t\t// refresh_token twice.\n\t\trToken = &internal.RefreshToken{RefreshId: token, Token: \"\"}\n\t}\n\n\trCtx, err := s.getRefreshTokenFromStorage(ctx, nil, rToken)\n\tif err != nil {\n\t\tif errors.Is(err, invalidErr) || errors.Is(err, expiredErr) {\n\t\t\treturn nil, newIntrospectInactiveTokenError()\n\t\t}\n\n\t\ts.logger.ErrorContext(ctx, \"failed to get refresh token\", \"err\", err)\n\t\treturn nil, newIntrospectInternalServerError()\n\t}\n\n\tsubjectString, sErr := genSubject(rCtx.storageToken.Claims.UserID, rCtx.storageToken.ConnectorID)\n\tif sErr != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to marshal offline session ID\", \"err\", err)\n\t\treturn nil, newIntrospectInternalServerError()\n\t}\n\n\treturn &Introspection{\n\t\tActive:    true,\n\t\tClientID:  rCtx.storageToken.ClientID,\n\t\tIssuedAt:  rCtx.storageToken.CreatedAt.Unix(),\n\t\tNotBefore: rCtx.storageToken.CreatedAt.Unix(),\n\t\tExpiry:    rCtx.storageToken.CreatedAt.Add(s.refreshTokenPolicy.absoluteLifetime).Unix(),\n\t\tSubject:   subjectString,\n\t\tUsername:  rCtx.storageToken.Claims.PreferredUsername,\n\t\tAudience:  getAudience(rCtx.storageToken.ClientID, rCtx.scopes),\n\t\tIssuer:    s.issuerURL.String(),\n\n\t\tExtra: IntrospectionExtra{\n\t\t\tEmail:             rCtx.storageToken.Claims.Email,\n\t\t\tEmailVerified:     &rCtx.storageToken.Claims.EmailVerified,\n\t\t\tGroups:            rCtx.storageToken.Claims.Groups,\n\t\t\tName:              rCtx.storageToken.Claims.Username,\n\t\t\tPreferredUsername: rCtx.storageToken.Claims.PreferredUsername,\n\t\t},\n\t\tTokenType: \"Bearer\",\n\t\tTokenUse:  \"refresh_token\",\n\t}, nil\n}\n\nfunc (s *Server) introspectAccessToken(ctx context.Context, token string) (*Introspection, error) {\n\tverifier := oidc.NewVerifier(s.issuerURL.String(), &signerKeySet{s.signer}, &oidc.Config{SkipClientIDCheck: true})\n\tidToken, err := verifier.Verify(ctx, token)\n\tif err != nil {\n\t\treturn nil, newIntrospectInactiveTokenError()\n\t}\n\n\tvar claims IntrospectionExtra\n\tif err := idToken.Claims(&claims); err != nil {\n\t\ts.logger.ErrorContext(ctx, \"error while fetching token claims\", \"err\", err.Error())\n\t\treturn nil, newIntrospectInternalServerError()\n\t}\n\n\tclientID, err := getClientID(idToken.Audience, claims.AuthorizingParty)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"error while fetching client_id from token:\", \"err\", err.Error())\n\t\treturn nil, newIntrospectInternalServerError()\n\t}\n\n\tclient, err := s.storage.GetClient(ctx, clientID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"error while fetching client from storage\", \"err\", err.Error())\n\t\treturn nil, newIntrospectInternalServerError()\n\t}\n\n\treturn &Introspection{\n\t\tActive:    true,\n\t\tClientID:  client.ID,\n\t\tIssuedAt:  idToken.IssuedAt.Unix(),\n\t\tNotBefore: idToken.IssuedAt.Unix(),\n\t\tExpiry:    idToken.Expiry.Unix(),\n\t\tSubject:   idToken.Subject,\n\t\tUsername:  claims.PreferredUsername,\n\t\tAudience:  idToken.Audience,\n\t\tIssuer:    s.issuerURL.String(),\n\n\t\tExtra:     claims,\n\t\tTokenType: \"Bearer\",\n\t\tTokenUse:  \"access_token\",\n\t}, nil\n}\n\nfunc (s *Server) handleIntrospect(w http.ResponseWriter, r *http.Request) {\n\tctx := r.Context()\n\n\tvar introspect *Introspection\n\ttoken, tokenType, err := s.getTokenFromRequest(r)\n\tif err == nil {\n\t\tswitch tokenType {\n\t\tcase AccessToken:\n\t\t\tintrospect, err = s.introspectAccessToken(ctx, token)\n\t\tcase RefreshToken:\n\t\t\tintrospect, err = s.introspectRefreshToken(ctx, token)\n\t\tdefault:\n\t\t\t// Token type is neither handled token types.\n\t\t\ts.logger.ErrorContext(r.Context(), \"unknown token type\", \"token_type\", tokenType)\n\t\t\tintrospectInactiveErr(w)\n\t\t\treturn\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tif intErr, ok := err.(*introspectionError); ok {\n\t\t\ts.introspectErrHelper(w, intErr.typ, intErr.desc, intErr.code)\n\t\t} else {\n\t\t\ts.logger.ErrorContext(r.Context(), \"an unknown error occurred\", \"err\", err.Error())\n\t\t\ts.introspectErrHelper(w, errServerError, \"An unknown error occurred\", http.StatusInternalServerError)\n\t\t}\n\n\t\treturn\n\t}\n\n\trawJSON, jsonErr := json.Marshal(introspect)\n\tif jsonErr != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to marshal introspection response\", \"err\", jsonErr)\n\t\ts.introspectErrHelper(w, errServerError, \"\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Write(rawJSON)\n}\n\nfunc (s *Server) introspectErrHelper(w http.ResponseWriter, typ string, description string, statusCode int) {\n\tif typ == errInactiveToken {\n\t\tintrospectInactiveErr(w)\n\t\treturn\n\t}\n\n\tif err := tokenErr(w, typ, description, statusCode); err != nil {\n\t\t// TODO(nabokihms): error with context\n\t\ts.logger.Error(\"introspect error response\", \"err\", err)\n\t}\n}\n\nfunc introspectInactiveErr(w http.ResponseWriter) {\n\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\tw.Header().Set(\"Pragma\", \"no-cache\")\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.WriteHeader(200)\n\tjson.NewEncoder(w).Encode(struct {\n\t\tActive bool `json:\"active\"`\n\t}{Active: false})\n}\n"
  },
  {
    "path": "server/introspectionhandler_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"path\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\nfunc toJSON(a interface{}) string {\n\tb, err := json.Marshal(a)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\treturn string(b)\n}\n\nfunc mockTestStorage(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tc := storage.Client{\n\t\tID:           \"test\",\n\t\tSecret:       \"barfoo\",\n\t\tRedirectURIs: []string{\"foo://bar.com/\", \"https://auth.example.com\"},\n\t\tName:         \"dex client\",\n\t\tLogoURL:      \"https://goo.gl/JIyzIC\",\n\t}\n\n\terr := s.CreateClient(ctx, c)\n\trequire.NoError(t, err)\n\n\tc1 := storage.Connector{\n\t\tID:   \"test\",\n\t\tType: \"mockPassword\",\n\t\tName: \"mockPassword\",\n\t\tConfig: []byte(`{\n\"username\": \"test\",\n\"password\": \"test\"\n}`),\n\t}\n\n\terr = s.CreateConnector(ctx, c1)\n\trequire.NoError(t, err)\n\n\terr = s.CreateRefresh(ctx, storage.RefreshToken{\n\t\tID:            \"test\",\n\t\tToken:         \"bar\",\n\t\tObsoleteToken: \"\",\n\t\tNonce:         \"foo\",\n\t\tClientID:      \"test\",\n\t\tConnectorID:   \"test\",\n\t\tScopes:        []string{\"openid\", \"email\", \"profile\"},\n\t\tCreatedAt:     time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:      time.Now().UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t})\n\trequire.NoError(t, err)\n\n\terr = s.CreateRefresh(ctx, storage.RefreshToken{\n\t\tID:            \"expired\",\n\t\tToken:         \"bar\",\n\t\tObsoleteToken: \"\",\n\t\tNonce:         \"foo\",\n\t\tClientID:      \"test\",\n\t\tConnectorID:   \"test\",\n\t\tScopes:        []string{\"openid\", \"email\", \"profile\"},\n\t\tCreatedAt:     time.Now().AddDate(-1, 0, 0).UTC().Round(time.Millisecond),\n\t\tLastUsed:      time.Now().AddDate(-1, 0, 0).UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t})\n\trequire.NoError(t, err)\n\n\terr = s.CreateOfflineSessions(ctx, storage.OfflineSessions{\n\t\tUserID: \"1\",\n\t\tConnID: \"test\",\n\t\tRefresh: map[string]*storage.RefreshTokenRef{\n\t\t\t\"test\":    {ID: \"test\", ClientID: \"test\"},\n\t\t\t\"expired\": {ID: \"expired\", ClientID: \"test\"},\n\t\t},\n\t\tConnectorData: nil,\n\t})\n\trequire.NoError(t, err)\n}\n\nfunc getIntrospectionValue(issuerURL url.URL, issuedAt time.Time, expiry time.Time, tokenUse string) *Introspection {\n\ttrueValue := true\n\treturn &Introspection{\n\t\tActive:    true,\n\t\tClientID:  \"test\",\n\t\tSubject:   \"CgExEgR0ZXN0\",\n\t\tExpiry:    expiry.Unix(),\n\t\tIssuedAt:  issuedAt.Unix(),\n\t\tNotBefore: issuedAt.Unix(),\n\t\tAudience: []string{\n\t\t\t\"test\",\n\t\t},\n\t\tIssuer:    issuerURL.String(),\n\t\tTokenType: \"Bearer\",\n\t\tTokenUse:  tokenUse,\n\t\tExtra: IntrospectionExtra{\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: &trueValue,\n\t\t\tGroups: []string{\n\t\t\t\t\"a\",\n\t\t\t\t\"b\",\n\t\t\t},\n\t\t\tName: \"jane\",\n\t\t},\n\t}\n}\n\nfunc TestGetTokenFromRequestSuccess(t *testing.T) {\n\tt0 := time.Now()\n\tctx := t.Context()\n\n\tnow := func() time.Time { return t0 }\n\t// Setup a dex server.\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t\tc.Now = now\n\t})\n\tdefer httpServer.Close()\n\n\tmockTestStorage(t, s.storage)\n\n\t// Generate a valid RS256-signed access token\n\taccessToken, _, err := s.newIDToken(ctx, \"test\", storage.Claims{\n\t\tUserID:   \"1\",\n\t\tUsername: \"jane\",\n\t}, []string{\"openid\"}, \"nonce\", \"\", \"\", \"test\", time.Time{})\n\trequire.NoError(t, err)\n\n\ttests := []struct {\n\t\ttestName          string\n\t\texpectedToken     string\n\t\texpectedTokenType TokenTypeEnum\n\t}{\n\t\t// Access Token\n\t\t{\n\t\t\ttestName:          \"Access Token\",\n\t\t\texpectedToken:     accessToken,\n\t\t\texpectedTokenType: AccessToken,\n\t\t},\n\t\t// Refresh Token\n\t\t{\n\t\t\ttestName:          \"Refresh token\",\n\t\t\texpectedToken:     \"CgR0ZXN0EgNiYXI\",\n\t\t\texpectedTokenType: RefreshToken,\n\t\t},\n\t\t// Unknown token\n\t\t{\n\t\t\ttestName:          \"Unknown token\",\n\t\t\texpectedToken:     \"AaAaAaA\",\n\t\t\texpectedTokenType: RefreshToken,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tdata := url.Values{}\n\t\t\tdata.Set(\"token\", tc.expectedToken)\n\t\t\treq := httptest.NewRequest(http.MethodPost, \"https://test.tech/token/introspect\", bytes.NewBufferString(data.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\t\t\ttoken, tokenType, err := s.getTokenFromRequest(req)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Error returned: %s\", err.Error())\n\t\t\t}\n\n\t\t\tif token != tc.expectedToken {\n\t\t\t\tt.Fatalf(\"Wrong token returned.  Expected %v got %v\", tc.expectedToken, token)\n\t\t\t}\n\n\t\t\tif tokenType != tc.expectedTokenType {\n\t\t\t\tt.Fatalf(\"Wrong token type returned.  Expected %v got %v\", tc.expectedTokenType, tokenType)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestGetTokenFromRequestFailure(t *testing.T) {\n\tt0 := time.Now()\n\n\tnow := func() time.Time { return t0 }\n\n\t// Setup a dex server.\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t\tc.Now = now\n\t})\n\tdefer httpServer.Close()\n\n\t_, _, err := s.getTokenFromRequest(httptest.NewRequest(http.MethodGet, \"https://test.tech/token/introspect\", nil))\n\trequire.ErrorIs(t, err, &introspectionError{\n\t\ttyp:  errInvalidRequest,\n\t\tdesc: \"HTTP method is \\\"GET\\\", expected \\\"POST\\\".\",\n\t\tcode: http.StatusBadRequest,\n\t})\n\n\t_, _, err = s.getTokenFromRequest(httptest.NewRequest(http.MethodPost, \"https://test.tech/token/introspect\", nil))\n\trequire.ErrorIs(t, err, &introspectionError{\n\t\ttyp:  errInvalidRequest,\n\t\tdesc: \"The POST body can not be empty.\",\n\t\tcode: http.StatusBadRequest,\n\t})\n\n\treq := httptest.NewRequest(http.MethodPost, \"https://test.tech/token/introspect\", strings.NewReader(\"token_type_hint=access_token\"))\n\treq.Header.Add(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t_, _, err = s.getTokenFromRequest(req)\n\trequire.ErrorIs(t, err, &introspectionError{\n\t\ttyp:  errInvalidRequest,\n\t\tdesc: \"The POST body doesn't contain 'token' parameter.\",\n\t\tcode: http.StatusBadRequest,\n\t})\n}\n\nfunc TestHandleIntrospect(t *testing.T) {\n\tt0 := time.Now()\n\n\tctx := t.Context()\n\n\t// Setup a dex server.\n\tnow := func() time.Time { return t0 }\n\n\tlogger := newLogger(t)\n\n\trefreshTokenPolicy, err := NewRefreshTokenPolicy(logger, false, \"\", \"24h\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to prepare rotation policy: %v\", err)\n\t}\n\trefreshTokenPolicy.now = now\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t\tc.RefreshTokenPolicy = refreshTokenPolicy\n\t\tc.Now = now\n\t})\n\tdefer httpServer.Close()\n\n\tmockTestStorage(t, s.storage)\n\n\tactiveAccessToken, expiry, err := s.newIDToken(ctx, \"test\", storage.Claims{\n\t\tUserID:        \"1\",\n\t\tUsername:      \"jane\",\n\t\tEmail:         \"jane.doe@example.com\",\n\t\tEmailVerified: true,\n\t\tGroups:        []string{\"a\", \"b\"},\n\t}, []string{\"openid\", \"email\", \"profile\", \"groups\"}, \"foo\", \"\", \"\", \"test\", time.Time{})\n\trequire.NoError(t, err)\n\n\tactiveRefreshToken, err := internal.Marshal(&internal.RefreshToken{RefreshId: \"test\", Token: \"bar\"})\n\trequire.NoError(t, err)\n\texpiredRefreshToken, err := internal.Marshal(&internal.RefreshToken{RefreshId: \"expired\", Token: \"bar\"})\n\trequire.NoError(t, err)\n\n\tinactiveResponse := \"{\\\"active\\\":false}\\n\"\n\tbadRequestResponse := `{\"error\":\"invalid_request\",\"error_description\":\"The POST body can not be empty.\"}`\n\n\ttests := []struct {\n\t\ttestName           string\n\t\ttoken              string\n\t\ttokenType          string\n\t\tresponse           string\n\t\tresponseStatusCode int\n\t}{\n\t\t// No token\n\t\t{\n\t\t\ttestName:           \"No token\",\n\t\t\tresponse:           badRequestResponse,\n\t\t\tresponseStatusCode: 400,\n\t\t},\n\t\t// Access token tests\n\t\t{\n\t\t\ttestName:           \"Access Token: active\",\n\t\t\ttoken:              activeAccessToken,\n\t\t\tresponse:           toJSON(getIntrospectionValue(s.issuerURL, t0, expiry, \"access_token\")),\n\t\t\tresponseStatusCode: 200,\n\t\t},\n\t\t{\n\t\t\ttestName:           \"Access Token: wrong\",\n\t\t\ttoken:              \"fake-token\",\n\t\t\tresponse:           inactiveResponse,\n\t\t\tresponseStatusCode: 200,\n\t\t},\n\t\t// Refresh token tests\n\t\t{\n\t\t\ttestName:           \"Refresh Token: active\",\n\t\t\ttoken:              activeRefreshToken,\n\t\t\tresponse:           toJSON(getIntrospectionValue(s.issuerURL, t0, t0.Add(s.refreshTokenPolicy.absoluteLifetime), \"refresh_token\")),\n\t\t\tresponseStatusCode: 200,\n\t\t},\n\t\t{\n\t\t\ttestName:           \"Refresh Token: expired\",\n\t\t\ttoken:              expiredRefreshToken,\n\t\t\tresponse:           inactiveResponse,\n\t\t\tresponseStatusCode: 200,\n\t\t},\n\t\t{\n\t\t\ttestName:           \"Refresh Token: active => false (wrong)\",\n\t\t\ttoken:              \"fake-token\",\n\t\t\tresponse:           inactiveResponse,\n\t\t\tresponseStatusCode: 200,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tdata := url.Values{}\n\t\t\tif tc.token != \"\" {\n\t\t\t\tdata.Set(\"token\", tc.token)\n\t\t\t}\n\t\t\tif tc.tokenType != \"\" {\n\t\t\t\tdata.Set(\"token_type_hint\", tc.tokenType)\n\t\t\t}\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"Could not parse issuer URL %v\", err)\n\t\t\t}\n\t\t\tu.Path = path.Join(u.Path, \"token\", \"introspect\")\n\n\t\t\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(data.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\n\t\t\tif rr.Code != tc.responseStatusCode {\n\t\t\t\tt.Errorf(\"%s: Unexpected Response Type.  Expected %v got %v\", tc.testName, tc.responseStatusCode, rr.Code)\n\t\t\t}\n\n\t\t\tresult, _ := io.ReadAll(rr.Body)\n\t\t\tif string(result) != tc.response {\n\t\t\t\tt.Errorf(\"%s: Unexpected Response.  Expected %q got %q\", tc.testName, tc.response, result)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestIntrospectErrHelper(t *testing.T) {\n\tt0 := time.Now()\n\n\tnow := func() time.Time { return t0 }\n\n\t// Setup a dex server.\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t\tc.Now = now\n\t})\n\tdefer httpServer.Close()\n\n\ttests := []struct {\n\t\ttestName      string\n\t\terr           *introspectionError\n\t\tresStatusCode int\n\t\tresBody       string\n\t}{\n\t\t{\n\t\t\ttestName:      \"Inactive Token\",\n\t\t\terr:           newIntrospectInactiveTokenError(),\n\t\t\tresStatusCode: http.StatusOK,\n\t\t\tresBody:       \"{\\\"active\\\":false}\\n\",\n\t\t},\n\t\t{\n\t\t\ttestName:      \"Bad Request\",\n\t\t\terr:           newIntrospectBadRequestError(\"This is a bad request\"),\n\t\t\tresStatusCode: http.StatusBadRequest,\n\t\t\tresBody:       `{\"error\":\"invalid_request\",\"error_description\":\"This is a bad request\"}`,\n\t\t},\n\t\t{\n\t\t\ttestName:      \"Internal Server Error\",\n\t\t\terr:           newIntrospectInternalServerError(),\n\t\t\tresStatusCode: http.StatusInternalServerError,\n\t\t\tresBody:       `{\"error\":\"server_error\"}`,\n\t\t},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.testName, func(t *testing.T) {\n\t\t\tw1 := httptest.NewRecorder()\n\n\t\t\ts.introspectErrHelper(w1, tc.err.typ, tc.err.desc, tc.err.code)\n\n\t\t\tres := w1.Result()\n\t\t\trequire.Equal(t, tc.resStatusCode, res.StatusCode)\n\t\t\trequire.Equal(t, \"application/json\", res.Header.Get(\"Content-Type\"))\n\n\t\t\tdata, err := io.ReadAll(res.Body)\n\t\t\tdefer res.Body.Close()\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Equal(t, tc.resBody, string(data))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/mfa.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"image/png\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\n\t\"github.com/pquerna/otp\"\n\t\"github.com/pquerna/otp/totp\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// MFAProvider is a pluggable multi-factor authentication method.\ntype MFAProvider interface {\n\t// Type returns the authenticator type identifier (e.g., \"TOTP\").\n\tType() string\n\t// EnabledForConnectorType returns true if this provider applies to the given connector type.\n\t// If no connector types are configured, the provider applies to all.\n\tEnabledForConnectorType(connectorType string) bool\n}\n\n// TOTPProvider implements TOTP-based multi-factor authentication.\ntype TOTPProvider struct {\n\tissuer         string\n\tconnectorTypes map[string]struct{}\n}\n\n// NewTOTPProvider creates a new TOTP MFA provider.\nfunc NewTOTPProvider(issuer string, connectorTypes []string) *TOTPProvider {\n\tm := make(map[string]struct{}, len(connectorTypes))\n\tfor _, t := range connectorTypes {\n\t\tm[t] = struct{}{}\n\t}\n\treturn &TOTPProvider{issuer: issuer, connectorTypes: m}\n}\n\nfunc (p *TOTPProvider) EnabledForConnectorType(connectorType string) bool {\n\tif len(p.connectorTypes) == 0 {\n\t\treturn true\n\t}\n\t_, ok := p.connectorTypes[connectorType]\n\treturn ok\n}\n\nfunc (p *TOTPProvider) Type() string { return \"TOTP\" }\n\nfunc (p *TOTPProvider) generate(connID, email string) (*otp.Key, error) {\n\treturn totp.Generate(totp.GenerateOpts{\n\t\tIssuer:      p.issuer,\n\t\tAccountName: fmt.Sprintf(\"(%s) %s\", connID, email),\n\t})\n}\n\nfunc (s *Server) handleMFAVerify(w http.ResponseWriter, r *http.Request) {\n\tmacEncoded := r.FormValue(\"hmac\")\n\tif macEncoded == \"\" {\n\t\ts.renderError(r, w, http.StatusUnauthorized, \"Unauthorized request.\")\n\t\treturn\n\t}\n\tmac, err := base64.RawURLEncoding.DecodeString(macEncoded)\n\tif err != nil {\n\t\ts.renderError(r, w, http.StatusUnauthorized, \"Unauthorized request.\")\n\t\treturn\n\t}\n\n\tctx := r.Context()\n\n\tauthReq, err := s.storage.GetAuthRequest(ctx, r.FormValue(\"req\"))\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to get auth request\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Database error.\")\n\t\treturn\n\t}\n\tif !authReq.LoggedIn {\n\t\ts.logger.ErrorContext(ctx, \"auth request does not have an identity for MFA verification\")\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login process not yet finalized.\")\n\t\treturn\n\t}\n\n\tauthenticatorID := r.FormValue(\"authenticator\")\n\n\t// Verify HMAC — includes authenticatorID to prevent skipping steps in the MFA chain.\n\th := hmac.New(sha256.New, authReq.HMACKey)\n\th.Write([]byte(authReq.ID + \"|\" + authenticatorID))\n\tif !hmac.Equal(mac, h.Sum(nil)) {\n\t\ts.renderError(r, w, http.StatusUnauthorized, \"Unauthorized request.\")\n\t\treturn\n\t}\n\tprovider, ok := s.mfaProviders[authenticatorID]\n\tif !ok {\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Unknown authenticator.\")\n\t\treturn\n\t}\n\n\ttotpProvider, ok := provider.(*TOTPProvider)\n\tif !ok {\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Unsupported authenticator type.\")\n\t\treturn\n\t}\n\n\tidentity, err := s.storage.GetUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to get user identity\", \"err\", err)\n\t\ts.renderError(r, w, http.StatusInternalServerError, \"Database error.\")\n\t\treturn\n\t}\n\n\t// Build approval URL with an HMAC that covers only the request ID\n\t// (MFA HMAC includes authenticatorID and is not valid for approval).\n\tapprovalH := hmac.New(sha256.New, authReq.HMACKey)\n\tapprovalH.Write([]byte(authReq.ID))\n\treturnURL := path.Join(s.issuerURL.Path, \"/approval\") + \"?req=\" + authReq.ID +\n\t\t\"&hmac=\" + base64.RawURLEncoding.EncodeToString(approvalH.Sum(nil))\n\n\tif authReq.MFAValidated {\n\t\thttp.Redirect(w, r, returnURL, http.StatusSeeOther)\n\t\treturn\n\t}\n\n\tsecret := identity.MFASecrets[authenticatorID]\n\n\tswitch r.Method {\n\tcase http.MethodGet:\n\t\tif secret == nil {\n\t\t\t// First-time enrollment: generate a new TOTP key.\n\t\t\t// TODO(nabokihms): clean up stale unconfirmed secrets. If a user starts\n\t\t\t// enrollment multiple times without completing it, old secrets accumulate.\n\t\t\tgenerated, err := totpProvider.generate(authReq.ConnectorID, authReq.Claims.Email)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to generate TOTP key\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tsecret = &storage.MFASecret{\n\t\t\t\tAuthenticatorID: authenticatorID,\n\t\t\t\tType:            \"TOTP\",\n\t\t\t\tSecret:          generated.String(),\n\t\t\t\tConfirmed:       false,\n\t\t\t\tCreatedAt:       s.now(),\n\t\t\t}\n\n\t\t\tif err := s.storage.UpdateUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) {\n\t\t\t\tif old.MFASecrets == nil {\n\t\t\t\t\told.MFASecrets = make(map[string]*storage.MFASecret)\n\t\t\t\t}\n\t\t\t\told.MFASecrets[authenticatorID] = secret\n\t\t\t\treturn old, nil\n\t\t\t}); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to store MFA secret\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\ts.renderTOTPPage(secret, false, totpProvider.issuer, authReq.ConnectorID, w, r)\n\n\tcase http.MethodPost:\n\t\t// TODO(nabokihms): this endpoint should be protected with a rate limit (like the auth endpoint).\n\t\t// TOTP has a limited keyspace (6 digits) with a 30-second validity window,\n\t\t// making it particularly vulnerable to brute-force without rate limiting.\n\t\t//\n\t\t// For now the best way is to use external rate limiting solutions.\n\t\tif secret == nil || secret.Secret == \"\" {\n\t\t\ts.renderError(r, w, http.StatusBadRequest, \"MFA not enrolled.\")\n\t\t\treturn\n\t\t}\n\n\t\tcode := r.FormValue(\"totp\")\n\t\tgenerated, err := otp.NewKeyFromURL(secret.Secret)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to load TOTP key\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\treturn\n\t\t}\n\n\t\tif !totp.Validate(code, generated.Secret()) {\n\t\t\ts.renderTOTPPage(secret, true, totpProvider.issuer, authReq.ConnectorID, w, r)\n\t\t\treturn\n\t\t}\n\n\t\t// Mark MFA secret as confirmed.\n\t\tif !secret.Confirmed {\n\t\t\tif err := s.storage.UpdateUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) {\n\t\t\t\tif s := old.MFASecrets[authenticatorID]; s != nil {\n\t\t\t\t\ts.Confirmed = true\n\t\t\t\t}\n\t\t\t\treturn old, nil\n\t\t\t}); err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to confirm MFA secret\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// Check if there are more authenticators in the MFA chain.\n\t\tmfaChain, err := s.mfaChainForClient(ctx, authReq.ClientID, authReq.ConnectorID)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get MFA chain\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\treturn\n\t\t}\n\n\t\t// Find the next authenticator in the chain after the current one.\n\t\tvar nextAuthenticator string\n\t\tfor i, id := range mfaChain {\n\t\t\tif id == authenticatorID && i+1 < len(mfaChain) {\n\t\t\t\tnextAuthenticator = mfaChain[i+1]\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tif nextAuthenticator != \"\" {\n\t\t\t// Redirect to the next authenticator in the chain.\n\t\t\th := hmac.New(sha256.New, authReq.HMACKey)\n\t\t\th.Write([]byte(authReq.ID + \"|\" + nextAuthenticator))\n\t\t\tv := url.Values{}\n\t\t\tv.Set(\"req\", authReq.ID)\n\t\t\tv.Set(\"hmac\", base64.RawURLEncoding.EncodeToString(h.Sum(nil)))\n\t\t\tv.Set(\"authenticator\", nextAuthenticator)\n\t\t\tnextURL := path.Join(s.issuerURL.Path, \"/mfa/verify\") + \"?\" + v.Encode()\n\t\t\thttp.Redirect(w, r, nextURL, http.StatusSeeOther)\n\t\t\treturn\n\t\t}\n\n\t\t// All authenticators in the chain completed — mark as validated.\n\t\tif err := s.storage.UpdateAuthRequest(ctx, authReq.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) {\n\t\t\told.MFAValidated = true\n\t\t\treturn old, nil\n\t\t}); err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to update auth request\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\treturn\n\t\t}\n\n\t\ts.sendCodeOrRedirectToApproval(w, r, authReq, returnURL)\n\n\tdefault:\n\t\ts.renderError(r, w, http.StatusBadRequest, \"Unsupported request method.\")\n\t}\n}\n\nfunc (s *Server) renderTOTPPage(secret *storage.MFASecret, lastFail bool, issuer, connectorID string, w http.ResponseWriter, r *http.Request) {\n\t// Prevent browser from caching the TOTP page (contains QR code with secret).\n\tw.Header().Set(\"Cache-Control\", \"no-store\")\n\tvar qrCode string\n\tif !secret.Confirmed {\n\t\tvar err error\n\t\tqrCode, err = generateTOTPQRCode(secret.Secret)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to generate QR code\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Internal server error.\")\n\t\t\treturn\n\t\t}\n\t}\n\tif err := s.templates.totpVerify(r, w, r.URL.String(), issuer, connectorID, qrCode, lastFail); err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"server template error\", \"err\", err)\n\t}\n}\n\n// sendCodeOrRedirectToApproval checks skipApproval and stored consent,\n// sending a code response directly if possible, or redirecting to the approval page.\nfunc (s *Server) sendCodeOrRedirectToApproval(w http.ResponseWriter, r *http.Request, authReq storage.AuthRequest, approvalURL string) {\n\tctx := r.Context()\n\n\tif !authReq.ForceApprovalPrompt {\n\t\tif s.skipApproval {\n\t\t\tauthReq, err := s.storage.GetAuthRequest(ctx, authReq.ID)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to get auth request\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login error.\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.sendCodeResponse(w, r, authReq)\n\t\t\treturn\n\t\t}\n\n\t\tui, err := s.storage.GetUserIdentity(ctx, authReq.Claims.UserID, authReq.ConnectorID)\n\t\tif err == nil && scopesCoveredByConsent(ui.Consents[authReq.ClientID], authReq.Scopes) {\n\t\t\tauthReq, err := s.storage.GetAuthRequest(ctx, authReq.ID)\n\t\t\tif err != nil {\n\t\t\t\ts.logger.ErrorContext(ctx, \"failed to get auth request\", \"err\", err)\n\t\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Login error.\")\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.sendCodeResponse(w, r, authReq)\n\t\t\treturn\n\t\t}\n\t}\n\n\thttp.Redirect(w, r, approvalURL, http.StatusSeeOther)\n}\n\nfunc generateTOTPQRCode(keyURL string) (string, error) {\n\tgenerated, err := otp.NewKeyFromURL(keyURL)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to load TOTP key: %w\", err)\n\t}\n\n\tqrCodeImage, err := generated.Image(300, 300)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to generate TOTP QR code: %w\", err)\n\t}\n\n\tvar buf bytes.Buffer\n\tif err := png.Encode(&buf, qrCodeImage); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to encode TOTP QR code: %w\", err)\n\t}\n\n\treturn base64.StdEncoding.EncodeToString(buf.Bytes()), nil\n}\n\n// mfaChainForClient returns the MFA chain for a client filtered by connector type,\n// falling back to the server's defaultMFAChain if the client has none.\n// Returns nil if no MFA is configured/applicable.\nfunc (s *Server) mfaChainForClient(ctx context.Context, clientID, connectorID string) ([]string, error) {\n\tif len(s.mfaProviders) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tclient, err := s.storage.GetClient(ctx, clientID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// nil means \"not set\" — fall back to default.\n\t// Explicit empty slice ([]string{}) means \"no MFA\" — don't fall back.\n\tsource := client.MFAChain\n\tif source == nil {\n\t\tsource = s.defaultMFAChain\n\t}\n\n\t// Resolve connector type from connector ID.\n\tconnectorType, err := s.getConnectorType(ctx, connectorID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar chain []string\n\tfor _, authID := range source {\n\t\tprovider, ok := s.mfaProviders[authID]\n\t\tif ok && provider.EnabledForConnectorType(connectorType) {\n\t\t\tchain = append(chain, authID)\n\t\t}\n\t}\n\treturn chain, nil\n}\n\n// getConnectorType returns the type of the connector with the given ID.\nfunc (s *Server) getConnectorType(ctx context.Context, connectorID string) (string, error) {\n\tconn, err := s.getConnector(ctx, connectorID)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"get connector %q: %w\", connectorID, err)\n\t}\n\treturn conn.Type, nil\n}\n"
  },
  {
    "path": "server/oauth2.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"crypto\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"hash\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"github.com/go-jose/go-jose/v4\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/server/signer\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// TODO(ericchiang): clean this file up and figure out more idiomatic error handling.\n\n// See: https://tools.ietf.org/html/rfc6749#section-4.1.2.1\n\n// displayedAuthErr is an error that should be displayed to the user as a web page\ntype displayedAuthErr struct {\n\tStatus      int\n\tDescription string\n}\n\nfunc (err *displayedAuthErr) Error() string {\n\treturn err.Description\n}\n\nfunc newDisplayedErr(status int, format string, a ...interface{}) *displayedAuthErr {\n\treturn &displayedAuthErr{status, fmt.Sprintf(format, a...)}\n}\n\n// redirectWithError redirects back to the client with an OAuth2 error response.\n// Used for prompt=none when login or consent is required.\nfunc (s *Server) redirectWithError(w http.ResponseWriter, r *http.Request, authReq *storage.AuthRequest, errType, description string) {\n\terr := &redirectedAuthErr{\n\t\tState:       authReq.State,\n\t\tRedirectURI: authReq.RedirectURI,\n\t\tType:        errType,\n\t\tDescription: description,\n\t}\n\terr.Handler().ServeHTTP(w, r)\n}\n\n// redirectedAuthErr is an error that should be reported back to the client by 302 redirect\ntype redirectedAuthErr struct {\n\tState       string\n\tRedirectURI string\n\tType        string\n\tDescription string\n}\n\nfunc (err *redirectedAuthErr) Error() string {\n\treturn err.Description\n}\n\nfunc (err *redirectedAuthErr) Handler() http.Handler {\n\thf := func(w http.ResponseWriter, r *http.Request) {\n\t\tv := url.Values{}\n\t\tv.Add(\"state\", err.State)\n\t\tv.Add(\"error\", err.Type)\n\t\tif err.Description != \"\" {\n\t\t\tv.Add(\"error_description\", err.Description)\n\t\t}\n\n\t\t// Parse the redirect URI to ensure it's valid before redirecting\n\t\tu, parseErr := url.Parse(err.RedirectURI)\n\t\tif parseErr != nil {\n\t\t\t// If URI parsing fails, respond with an error instead of redirecting\n\t\t\thttp.Error(w, \"Invalid redirect URI\", http.StatusBadRequest)\n\t\t\treturn\n\t\t}\n\n\t\t// Add error parameters to the URL\n\t\tquery := u.Query()\n\t\tfor key, values := range v {\n\t\t\tfor _, value := range values {\n\t\t\t\tquery.Add(key, value)\n\t\t\t}\n\t\t}\n\t\tu.RawQuery = query.Encode()\n\n\t\thttp.Redirect(w, r, u.String(), http.StatusSeeOther)\n\t}\n\treturn http.HandlerFunc(hf)\n}\n\nfunc tokenErr(w http.ResponseWriter, typ, description string, statusCode int) error {\n\tdata := struct {\n\t\tError       string `json:\"error\"`\n\t\tDescription string `json:\"error_description,omitempty\"`\n\t}{typ, description}\n\tbody, err := json.Marshal(data)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to marshal token error response: %v\", err)\n\t}\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tw.Header().Set(\"Content-Length\", strconv.Itoa(len(body)))\n\tw.WriteHeader(statusCode)\n\tw.Write(body)\n\treturn nil\n}\n\nconst (\n\terrInvalidRequest          = \"invalid_request\"\n\terrUnauthorizedClient      = \"unauthorized_client\"\n\terrAccessDenied            = \"access_denied\"\n\terrUnsupportedResponseType = \"unsupported_response_type\"\n\terrRequestNotSupported     = \"request_not_supported\"\n\terrInvalidScope            = \"invalid_scope\"\n\terrServerError             = \"server_error\"\n\terrTemporarilyUnavailable  = \"temporarily_unavailable\"\n\terrUnsupportedGrantType    = \"unsupported_grant_type\"\n\terrInvalidGrant            = \"invalid_grant\"\n\terrInvalidClient           = \"invalid_client\"\n\terrInactiveToken           = \"inactive_token\"\n\terrLoginRequired           = \"login_required\"\n\terrInteractionRequired     = \"interaction_required\"\n\terrConsentRequired         = \"consent_required\"\n)\n\nconst (\n\tscopeOfflineAccess     = \"offline_access\" // Request a refresh token.\n\tscopeOpenID            = \"openid\"\n\tscopeGroups            = \"groups\"\n\tscopeEmail             = \"email\"\n\tscopeProfile           = \"profile\"\n\tscopeFederatedID       = \"federated:id\"\n\tscopeCrossClientPrefix = \"audience:server:client_id:\"\n)\n\nconst (\n\tdeviceCallbackURI = \"/device/callback\"\n)\n\nconst (\n\tredirectURIOOB = \"urn:ietf:wg:oauth:2.0:oob\"\n)\n\nconst (\n\tgrantTypeAuthorizationCode = \"authorization_code\"\n\tgrantTypeRefreshToken      = \"refresh_token\"\n\tgrantTypeImplicit          = \"implicit\"\n\tgrantTypePassword          = \"password\"\n\tgrantTypeDeviceCode        = \"urn:ietf:params:oauth:grant-type:device_code\"\n\tgrantTypeTokenExchange     = \"urn:ietf:params:oauth:grant-type:token-exchange\"\n\tgrantTypeClientCredentials = \"client_credentials\"\n)\n\n// ConnectorGrantTypes is the set of grant types that can be restricted per connector.\nvar ConnectorGrantTypes = map[string]bool{\n\tgrantTypeAuthorizationCode: true,\n\tgrantTypeRefreshToken:      true,\n\tgrantTypeImplicit:          true,\n\tgrantTypePassword:          true,\n\tgrantTypeDeviceCode:        true,\n\tgrantTypeTokenExchange:     true,\n}\n\nconst (\n\t// https://www.rfc-editor.org/rfc/rfc8693.html#section-3\n\ttokenTypeAccess  = \"urn:ietf:params:oauth:token-type:access_token\"\n\ttokenTypeRefresh = \"urn:ietf:params:oauth:token-type:refresh_token\"\n\ttokenTypeID      = \"urn:ietf:params:oauth:token-type:id_token\"\n\ttokenTypeSAML1   = \"urn:ietf:params:oauth:token-type:saml1\"\n\ttokenTypeSAML2   = \"urn:ietf:params:oauth:token-type:saml2\"\n\ttokenTypeJWT     = \"urn:ietf:params:oauth:token-type:jwt\"\n)\n\nconst (\n\tresponseTypeCode             = \"code\"                // \"Regular\" flow\n\tresponseTypeToken            = \"token\"               // Implicit flow for frontend apps.\n\tresponseTypeIDToken          = \"id_token\"            // ID Token in url fragment\n\tresponseTypeCodeToken        = \"code token\"          // \"Regular\" flow + Implicit flow\n\tresponseTypeCodeIDToken      = \"code id_token\"       // \"Regular\" flow + ID Token\n\tresponseTypeIDTokenToken     = \"id_token token\"      // ID Token + Implicit flow\n\tresponseTypeCodeIDTokenToken = \"code id_token token\" // \"Regular\" flow + ID Token + Implicit flow\n)\n\nconst (\n\tdeviceTokenPending  = \"authorization_pending\"\n\tdeviceTokenComplete = \"complete\"\n\tdeviceTokenSlowDown = \"slow_down\"\n\tdeviceTokenExpired  = \"expired_token\"\n)\n\nfunc parseScopes(scopes []string) connector.Scopes {\n\tvar s connector.Scopes\n\tfor _, scope := range scopes {\n\t\tswitch scope {\n\t\tcase scopeOfflineAccess:\n\t\t\ts.OfflineAccess = true\n\t\tcase scopeGroups:\n\t\t\ts.Groups = true\n\t\t}\n\t}\n\treturn s\n}\n\n// The hash algorithm for the at_hash is determined by the signing\n// algorithm used for the id_token. From the spec:\n//\n//\t...the hash algorithm used is the hash algorithm used in the alg Header\n//\tParameter of the ID Token's JOSE Header. For instance, if the alg is RS256,\n//\thash the access_token value with SHA-256\n//\n// https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken\nvar hashForSigAlg = map[jose.SignatureAlgorithm]func() hash.Hash{\n\tjose.RS256: sha256.New,\n\tjose.RS384: sha512.New384,\n\tjose.RS512: sha512.New,\n\tjose.ES256: sha256.New,\n\tjose.ES384: sha512.New384,\n\tjose.ES512: sha512.New,\n}\n\n// Compute an at_hash from a raw access token and a signature algorithm\n//\n// See: https://openid.net/specs/openid-connect-core-1_0.html#ImplicitIDToken\nfunc accessTokenHash(alg jose.SignatureAlgorithm, accessToken string) (string, error) {\n\tnewHash, ok := hashForSigAlg[alg]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"unsupported signature algorithm: %s\", alg)\n\t}\n\n\thashFunc := newHash()\n\tif _, err := io.WriteString(hashFunc, accessToken); err != nil {\n\t\treturn \"\", fmt.Errorf(\"computing hash: %v\", err)\n\t}\n\tsum := hashFunc.Sum(nil)\n\treturn base64.RawURLEncoding.EncodeToString(sum[:len(sum)/2]), nil\n}\n\ntype audience []string\n\nfunc (a audience) contains(aud string) bool {\n\tfor _, e := range a {\n\t\tif aud == e {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc (a audience) MarshalJSON() ([]byte, error) {\n\tif len(a) == 1 {\n\t\treturn json.Marshal(a[0])\n\t}\n\treturn json.Marshal([]string(a))\n}\n\ntype idTokenClaims struct {\n\tIssuer           string   `json:\"iss\"`\n\tSubject          string   `json:\"sub\"`\n\tAudience         audience `json:\"aud\"`\n\tExpiry           int64    `json:\"exp\"`\n\tIssuedAt         int64    `json:\"iat\"`\n\tAuthorizingParty string   `json:\"azp,omitempty\"`\n\tNonce            string   `json:\"nonce,omitempty\"`\n\tAuthTime         int64    `json:\"auth_time,omitempty\"`\n\n\tAccessTokenHash string `json:\"at_hash,omitempty\"`\n\tCodeHash        string `json:\"c_hash,omitempty\"`\n\n\tEmail         string `json:\"email,omitempty\"`\n\tEmailVerified *bool  `json:\"email_verified,omitempty\"`\n\n\tGroups []string `json:\"groups,omitempty\"`\n\n\tName              string `json:\"name,omitempty\"`\n\tPreferredUsername string `json:\"preferred_username,omitempty\"`\n\n\tFederatedIDClaims *federatedIDClaims `json:\"federated_claims,omitempty\"`\n}\n\ntype federatedIDClaims struct {\n\tConnectorID string `json:\"connector_id,omitempty\"`\n\tUserID      string `json:\"user_id,omitempty\"`\n}\n\nfunc (s *Server) newAccessToken(ctx context.Context, clientID string, claims storage.Claims, scopes []string, nonce, connID string, authTime time.Time) (accessToken string, expiry time.Time, err error) {\n\treturn s.newIDToken(ctx, clientID, claims, scopes, nonce, storage.NewID(), \"\", connID, authTime)\n}\n\nfunc getClientID(aud audience, azp string) (string, error) {\n\tswitch len(aud) {\n\tcase 0:\n\t\treturn \"\", fmt.Errorf(\"no audience is set, could not find ClientID\")\n\tcase 1:\n\t\treturn aud[0], nil\n\tdefault:\n\t\treturn azp, nil\n\t}\n}\n\nfunc getAudience(clientID string, scopes []string) audience {\n\tvar aud audience\n\n\tfor _, scope := range scopes {\n\t\tif peerID, ok := parseCrossClientScope(scope); ok {\n\t\t\taud = append(aud, peerID)\n\t\t}\n\t}\n\n\tif len(aud) == 0 {\n\t\t// Client didn't ask for cross client audience. Set the current\n\t\t// client as the audience.\n\t\taud = audience{clientID}\n\t\t// Client asked for cross client audience:\n\t\t// if the current client was not requested explicitly\n\t} else if !aud.contains(clientID) {\n\t\t// by default it becomes one of entries in Audience\n\t\taud = append(aud, clientID)\n\t}\n\n\treturn aud\n}\n\nfunc genSubject(userID string, connID string) (string, error) {\n\tsub := &internal.IDTokenSubject{\n\t\tUserId: userID,\n\t\tConnId: connID,\n\t}\n\n\treturn internal.Marshal(sub)\n}\n\nfunc (s *Server) newIDToken(ctx context.Context, clientID string, claims storage.Claims, scopes []string, nonce, accessToken, code, connID string, authTime time.Time) (idToken string, expiry time.Time, err error) {\n\tissuedAt := s.now()\n\texpiry = issuedAt.Add(s.idTokensValidFor)\n\n\tsubjectString, err := genSubject(claims.UserID, connID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to marshal offline session ID\", \"err\", err)\n\t\treturn \"\", expiry, fmt.Errorf(\"failed to marshal offline session ID: %v\", err)\n\t}\n\n\ttok := idTokenClaims{\n\t\tIssuer:   s.issuerURL.String(),\n\t\tSubject:  subjectString,\n\t\tNonce:    nonce,\n\t\tExpiry:   expiry.Unix(),\n\t\tIssuedAt: issuedAt.Unix(),\n\t}\n\n\t// Include auth_time when sessions are enabled and the value is available.\n\tif !authTime.IsZero() {\n\t\ttok.AuthTime = authTime.Unix()\n\t}\n\n\t// Determine signing algorithm from signer\n\tsigningAlg, err := s.signer.Algorithm(ctx)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to get signing algorithm\", \"err\", err)\n\t\treturn \"\", expiry, fmt.Errorf(\"failed to get signing algorithm: %v\", err)\n\t}\n\n\tif accessToken != \"\" {\n\t\tatHash, err := accessTokenHash(signingAlg, accessToken)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"error computing at_hash\", \"err\", err)\n\t\t\treturn \"\", expiry, fmt.Errorf(\"error computing at_hash: %v\", err)\n\t\t}\n\t\ttok.AccessTokenHash = atHash\n\t}\n\n\tif code != \"\" {\n\t\tcHash, err := accessTokenHash(signingAlg, code)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"error computing c_hash\", \"err\", err)\n\t\t\treturn \"\", expiry, fmt.Errorf(\"error computing c_hash: #{err}\")\n\t\t}\n\t\ttok.CodeHash = cHash\n\t}\n\n\tfor _, scope := range scopes {\n\t\tswitch {\n\t\tcase scope == scopeEmail:\n\t\t\ttok.Email = claims.Email\n\t\t\ttok.EmailVerified = &claims.EmailVerified\n\t\tcase scope == scopeGroups:\n\t\t\ttok.Groups = claims.Groups\n\t\tcase scope == scopeProfile:\n\t\t\ttok.Name = claims.Username\n\t\t\ttok.PreferredUsername = claims.PreferredUsername\n\t\tcase scope == scopeFederatedID:\n\t\t\ttok.FederatedIDClaims = &federatedIDClaims{\n\t\t\t\tConnectorID: connID,\n\t\t\t\tUserID:      claims.UserID,\n\t\t\t}\n\t\tdefault:\n\t\t\tpeerID, ok := parseCrossClientScope(scope)\n\t\t\tif !ok {\n\t\t\t\t// Ignore unknown scopes. These are already validated during the\n\t\t\t\t// initial auth request.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tisTrusted, err := s.validateCrossClientTrust(ctx, clientID, peerID)\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", expiry, err\n\t\t\t}\n\t\t\tif !isTrusted {\n\t\t\t\t// TODO(ericchiang): propagate this error to the client.\n\t\t\t\treturn \"\", expiry, fmt.Errorf(\"peer (%s) does not trust client\", peerID)\n\t\t\t}\n\t\t}\n\t}\n\n\ttok.Audience = getAudience(clientID, scopes)\n\tif len(tok.Audience) > 1 {\n\t\t// The current client becomes the authorizing party.\n\t\ttok.AuthorizingParty = clientID\n\t}\n\n\tpayload, err := json.Marshal(tok)\n\tif err != nil {\n\t\treturn \"\", expiry, fmt.Errorf(\"could not serialize claims: %v\", err)\n\t}\n\n\tif idToken, err = s.signer.Sign(ctx, payload); err != nil {\n\t\treturn \"\", expiry, fmt.Errorf(\"failed to sign payload: %v\", err)\n\t}\n\treturn idToken, expiry, nil\n}\n\n// validateIDTokenHint verifies the signature and issuer of an id_token_hint.\n// Expired tokens are accepted per OIDC Core 1.0 §3.1.2.1.\n// Returns the raw subject claim from the token.\nfunc (s *Server) validateIDTokenHint(ctx context.Context, hint string) (string, error) {\n\tverifier := oidc.NewVerifier(s.issuerURL.String(), &signerKeySet{s.signer}, &oidc.Config{\n\t\tSkipExpiryCheck: true,\n\t\t// SkipClientIDCheck is set because the hint may originate from any client that\n\t\t// Dex issued a token to — the caller does not know the expected audience in advance.\n\t\t// The signature verification via signerKeySet already guarantees the token was\n\t\t// issued by this server, which is sufficient for a hint.\n\t\t// Dex does the client id check later in the scope of the session validation.\n\t\tSkipClientIDCheck: true,\n\t})\n\tidToken, err := verifier.Verify(ctx, hint)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn idToken.Subject, nil\n}\n\n// sessionMatchesHint checks whether the session's user identity matches the\n// subject from an id_token_hint by encoding the session's (userID, connectorID)\n// via genSubject and doing a string comparison.\nfunc sessionMatchesHint(session *storage.AuthSession, hintSubject string) bool {\n\tif session == nil {\n\t\treturn false\n\t}\n\tencoded, err := genSubject(session.UserID, session.ConnectorID)\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn encoded == hintSubject\n}\n\n// parseAuthorizationRequest parses the initial request from the OAuth2 client.\n// Returns the auth request, the raw subject from id_token_hint (empty if not provided), and any error.\nfunc (s *Server) parseAuthorizationRequest(r *http.Request) (*storage.AuthRequest, string, error) {\n\tctx := r.Context()\n\tif err := r.ParseForm(); err != nil {\n\t\treturn nil, \"\", newDisplayedErr(http.StatusBadRequest, \"Failed to parse request.\")\n\t}\n\tq := r.Form\n\tredirectURI, err := url.QueryUnescape(q.Get(\"redirect_uri\"))\n\tif err != nil {\n\t\treturn nil, \"\", newDisplayedErr(http.StatusBadRequest, \"No redirect_uri provided.\")\n\t}\n\n\tclientID := q.Get(\"client_id\")\n\tstate := q.Get(\"state\")\n\tnonce := q.Get(\"nonce\")\n\tconnectorID := q.Get(\"connector_id\")\n\t// Some clients, like the old go-oidc, provide extra whitespace. Tolerate this.\n\tscopes := strings.Fields(q.Get(\"scope\"))\n\tresponseTypes := strings.Fields(q.Get(\"response_type\"))\n\n\tcodeChallenge := q.Get(\"code_challenge\")\n\tcodeChallengeMethod := q.Get(\"code_challenge_method\")\n\n\tif codeChallengeMethod == \"\" {\n\t\tcodeChallengeMethod = codeChallengeMethodPlain\n\t}\n\n\tclient, err := s.storage.GetClient(ctx, clientID)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(r.Context(), \"invalid client_id provided\", \"client_id\", clientID)\n\t\t\treturn nil, \"\", newDisplayedErr(http.StatusNotFound, \"Invalid client_id.\")\n\t\t}\n\t\ts.logger.ErrorContext(r.Context(), \"failed to get client\", \"err\", err)\n\t\treturn nil, \"\", newDisplayedErr(http.StatusInternalServerError, \"Database error.\")\n\t}\n\n\tif !validateRedirectURI(client, redirectURI) {\n\t\ts.logger.ErrorContext(r.Context(), \"unregistered redirect_uri\", \"redirect_uri\", redirectURI, \"client_id\", clientID)\n\t\treturn nil, \"\", newDisplayedErr(http.StatusBadRequest, \"Unregistered redirect_uri.\")\n\t}\n\tif redirectURI == deviceCallbackURI && client.Public {\n\t\tredirectURI = s.absPath(deviceCallbackURI)\n\t}\n\n\t// From here on out, we want to redirect back to the client with an error.\n\tnewRedirectedErr := func(typ, format string, a ...interface{}) *redirectedAuthErr {\n\t\treturn &redirectedAuthErr{state, redirectURI, typ, fmt.Sprintf(format, a...)}\n\t}\n\n\tif connectorID != \"\" {\n\t\tconnectors, err := s.storage.ListConnectors(ctx)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to list connectors\", \"err\", err)\n\t\t\treturn nil, \"\", newRedirectedErr(errServerError, \"Unable to retrieve connectors\")\n\t\t}\n\t\tif !validateConnectorID(connectors, connectorID) {\n\t\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Invalid ConnectorID\")\n\t\t}\n\t\tif !isConnectorAllowed(client.AllowedConnectors, connectorID) {\n\t\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Connector not allowed for this client\")\n\t\t}\n\t}\n\n\t// dex doesn't support request parameter and must return request_not_supported error\n\t// https://openid.net/specs/openid-connect-core-1_0.html#6.1\n\tif q.Get(\"request\") != \"\" {\n\t\treturn nil, \"\", newRedirectedErr(errRequestNotSupported, \"Server does not support request parameter.\")\n\t}\n\n\tif codeChallenge != \"\" && !slices.Contains(s.pkce.CodeChallengeMethodsSupported, codeChallengeMethod) {\n\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Unsupported PKCE challenge method (%q).\", codeChallengeMethod)\n\t}\n\n\t// Enforce PKCE if configured.\n\t// https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-12#section-4.1.1\n\tif s.pkce.Enforce && codeChallenge == \"\" {\n\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"PKCE is required. The code_challenge parameter must be provided.\")\n\t}\n\n\tvar (\n\t\tunrecognized  []string\n\t\tinvalidScopes []string\n\t)\n\thasOpenIDScope := false\n\tfor _, scope := range scopes {\n\t\tswitch scope {\n\t\tcase scopeOpenID:\n\t\t\thasOpenIDScope = true\n\t\tcase scopeOfflineAccess, scopeEmail, scopeProfile, scopeGroups, scopeFederatedID:\n\t\tdefault:\n\t\t\tpeerID, ok := parseCrossClientScope(scope)\n\t\t\tif !ok {\n\t\t\t\tunrecognized = append(unrecognized, scope)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tisTrusted, err := s.validateCrossClientTrust(r.Context(), clientID, peerID)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, \"\", newRedirectedErr(errServerError, \"Internal server error.\")\n\t\t\t}\n\t\t\tif !isTrusted {\n\t\t\t\tinvalidScopes = append(invalidScopes, scope)\n\t\t\t}\n\t\t}\n\t}\n\tif !hasOpenIDScope {\n\t\treturn nil, \"\", newRedirectedErr(errInvalidScope, `Missing required scope(s) [\"openid\"].`)\n\t}\n\tif len(unrecognized) > 0 {\n\t\treturn nil, \"\", newRedirectedErr(errInvalidScope, \"Unrecognized scope(s) %q\", unrecognized)\n\t}\n\tif len(invalidScopes) > 0 {\n\t\treturn nil, \"\", newRedirectedErr(errInvalidScope, \"Client can't request scope(s) %q\", invalidScopes)\n\t}\n\n\tvar rt struct {\n\t\tcode    bool\n\t\tidToken bool\n\t\ttoken   bool\n\t}\n\n\tfor _, responseType := range responseTypes {\n\t\tswitch responseType {\n\t\tcase responseTypeCode:\n\t\t\trt.code = true\n\t\tcase responseTypeIDToken:\n\t\t\trt.idToken = true\n\t\tcase responseTypeToken:\n\t\t\trt.token = true\n\t\tdefault:\n\t\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Invalid response type %q\", responseType)\n\t\t}\n\n\t\tif !s.supportedResponseTypes[responseType] {\n\t\t\treturn nil, \"\", newRedirectedErr(errUnsupportedResponseType, \"Unsupported response type %q\", responseType)\n\t\t}\n\t}\n\n\tif len(responseTypes) == 0 {\n\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"No response_type provided\")\n\t}\n\n\tif rt.token && !rt.code && !rt.idToken {\n\t\t// \"token\" can't be provided by its own.\n\t\t//\n\t\t// https://openid.net/specs/openid-connect-core-1_0.html#Authentication\n\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Response type 'token' must be provided with type 'id_token' and/or 'code'\")\n\t}\n\tif !rt.code {\n\t\t// Either \"id_token token\" or \"id_token\" has been provided which implies the\n\t\t// implicit flow. Implicit flow requires a nonce value.\n\t\t//\n\t\t// https://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthRequest\n\t\tif nonce == \"\" {\n\t\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Response type 'token' requires a 'nonce' value.\")\n\t\t}\n\t}\n\tif rt.token {\n\t\tif redirectURI == redirectURIOOB {\n\t\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Cannot use response type 'token' with redirect_uri '%s'.\", redirectURIOOB)\n\t\t}\n\t}\n\n\tprompt, err := ParsePrompt(q.Get(\"prompt\"))\n\tif err != nil {\n\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Invalid prompt parameter: %v\", err)\n\t}\n\n\t// Parse max_age: -1 means not specified.\n\tmaxAge := -1\n\tif maxAgeStr := q.Get(\"max_age\"); maxAgeStr != \"\" {\n\t\tv, err := strconv.Atoi(maxAgeStr)\n\t\tif err != nil || v < 0 {\n\t\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Invalid max_age value %q\", maxAgeStr)\n\t\t}\n\t\tmaxAge = v\n\t}\n\n\t// OIDC prompt=consent implies force approval.\n\tforceApproval := q.Get(\"approval_prompt\") == \"force\" || prompt.Consent()\n\n\t// Validate id_token_hint if provided (OIDC Core 1.0 §3.1.2.1).\n\tvar idTokenHintSubject string\n\tif hint := q.Get(\"id_token_hint\"); hint != \"\" {\n\t\tsub, err := s.validateIDTokenHint(ctx, hint)\n\t\tif err != nil {\n\t\t\treturn nil, \"\", newRedirectedErr(errInvalidRequest, \"Invalid id_token_hint.\")\n\t\t}\n\t\tidTokenHintSubject = sub\n\t}\n\n\treturn &storage.AuthRequest{\n\t\tID:                  storage.NewID(),\n\t\tClientID:            client.ID,\n\t\tState:               state,\n\t\tNonce:               nonce,\n\t\tForceApprovalPrompt: forceApproval,\n\t\tPrompt:              prompt.String(),\n\t\tMaxAge:              maxAge,\n\t\tScopes:              scopes,\n\t\tRedirectURI:         redirectURI,\n\t\tResponseTypes:       responseTypes,\n\t\tConnectorID:         connectorID,\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       codeChallenge,\n\t\t\tCodeChallengeMethod: codeChallengeMethod,\n\t\t},\n\t\tHMACKey: storage.NewHMACKey(crypto.SHA256),\n\t}, idTokenHintSubject, nil\n}\n\nfunc parseCrossClientScope(scope string) (peerID string, ok bool) {\n\tif ok = strings.HasPrefix(scope, scopeCrossClientPrefix); ok {\n\t\tpeerID = scope[len(scopeCrossClientPrefix):]\n\t}\n\treturn\n}\n\nfunc (s *Server) validateCrossClientTrust(ctx context.Context, clientID, peerID string) (trusted bool, err error) {\n\tif peerID == clientID {\n\t\treturn true, nil\n\t}\n\tpeer, err := s.storage.GetClient(ctx, peerID)\n\tif err != nil {\n\t\tif err != storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get client\", \"err\", err)\n\t\t\treturn false, err\n\t\t}\n\t\treturn false, nil\n\t}\n\tfor _, id := range peer.TrustedPeers {\n\t\tif id == clientID {\n\t\t\treturn true, nil\n\t\t}\n\t}\n\treturn false, nil\n}\n\nfunc validateRedirectURI(client storage.Client, redirectURI string) bool {\n\t// Allow named RedirectURIs for both public and non-public clients.\n\t// This is required make PKCE-enabled web apps work, when configured as public clients.\n\tfor _, uri := range client.RedirectURIs {\n\t\tif redirectURI == uri {\n\t\t\treturn true\n\t\t}\n\t}\n\t// For non-public clients or when RedirectURIs is set, we allow only explicitly named RedirectURIs.\n\t// Otherwise, we check below for special URIs used for desktop or mobile apps.\n\tif !client.Public || len(client.RedirectURIs) > 0 {\n\t\treturn false\n\t}\n\n\tif redirectURI == redirectURIOOB || redirectURI == deviceCallbackURI {\n\t\treturn true\n\t}\n\n\t// verify that the host is of form \"http://localhost:(port)(path)\", \"http://localhost(path)\" or numeric form like\n\t// \"http://127.0.0.1:(port)(path)\"\n\tu, err := url.Parse(redirectURI)\n\tif err != nil {\n\t\treturn false\n\t}\n\tif u.Scheme != \"http\" {\n\t\treturn false\n\t}\n\treturn isHostLocal(u.Host)\n}\n\nfunc isHostLocal(host string) bool {\n\tif host == \"localhost\" || net.ParseIP(host).IsLoopback() {\n\t\treturn true\n\t}\n\n\thost, _, err := net.SplitHostPort(host)\n\tif err != nil {\n\t\treturn false\n\t}\n\n\treturn host == \"localhost\" || net.ParseIP(host).IsLoopback()\n}\n\nfunc validateConnectorID(connectors []storage.Connector, connectorID string) bool {\n\tfor _, c := range connectors {\n\t\tif c.ID == connectorID {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// signerKeySet implements the oidc.KeySet interface backed by the Dex signer\ntype signerKeySet struct {\n\tsigner signer.Signer\n}\n\nfunc (s *signerKeySet) VerifySignature(ctx context.Context, jwt string) (payload []byte, err error) {\n\tjws, err := jose.ParseSigned(jwt, []jose.SignatureAlgorithm{jose.RS256, jose.RS384, jose.RS512, jose.ES256, jose.ES384, jose.ES512})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkeyID := \"\"\n\tfor _, sig := range jws.Signatures {\n\t\tkeyID = sig.Header.KeyID\n\t\tbreak\n\t}\n\n\tkeys, err := s.signer.ValidationKeys(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, key := range keys {\n\t\tif keyID == \"\" || key.KeyID == keyID {\n\t\t\tif payload, err := jws.Verify(key); err == nil {\n\t\t\t\treturn payload, nil\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil, errors.New(\"failed to verify id token signature\")\n}\n"
  },
  {
    "path": "server/oauth2_test.go",
    "content": "package server\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/server/signer\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/memory\"\n)\n\nfunc TestGetClientID(t *testing.T) {\n\tcid, err := getClientID(audience{}, \"\")\n\trequire.Equal(t, \"\", cid)\n\trequire.Equal(t, \"no audience is set, could not find ClientID\", err.Error())\n\n\tcid, err = getClientID(audience{\"a\"}, \"\")\n\trequire.Equal(t, \"a\", cid)\n\trequire.NoError(t, err)\n\n\tcid, err = getClientID(audience{\"a\", \"b\"}, \"azp\")\n\trequire.Equal(t, \"azp\", cid)\n\trequire.NoError(t, err)\n}\n\nfunc TestGetAudience(t *testing.T) {\n\taud := getAudience(\"client-id\", []string{})\n\trequire.Equal(t, aud, audience{\"client-id\"})\n\n\taud = getAudience(\"client-id\", []string{\"ascope\"})\n\trequire.Equal(t, aud, audience{\"client-id\"})\n\n\taud = getAudience(\"client-id\", []string{\"ascope\", \"audience:server:client_id:aa\", \"audience:server:client_id:bb\"})\n\trequire.Equal(t, aud, audience{\"aa\", \"bb\", \"client-id\"})\n}\n\nfunc TestGetSubject(t *testing.T) {\n\tsub, err := genSubject(\"foo\", \"bar\")\n\trequire.Equal(t, \"CgNmb28SA2Jhcg\", sub)\n\trequire.NoError(t, err)\n}\n\nfunc TestParseAuthorizationRequest(t *testing.T) {\n\ttests := []struct {\n\t\tname                   string\n\t\tclients                []storage.Client\n\t\tsupportedResponseTypes []string\n\t\tpkce                   PKCEConfig\n\n\t\tusePOST bool\n\n\t\tqueryParams map[string]string\n\n\t\texpectedError error\n\t}{\n\t\t{\n\t\t\tname: \"normal request\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"foo\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/foo\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"foo\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/foo\",\n\t\t\t\t\"response_type\": \"code\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"POST request\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"foo\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/foo\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"foo\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/foo\",\n\t\t\t\t\"response_type\": \"code\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t\tusePOST: true,\n\t\t},\n\t\t{\n\t\t\tname: \"invalid client id\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"foo\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/foo\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/foo\",\n\t\t\t\t\"response_type\": \"code\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &displayedAuthErr{Status: http.StatusNotFound},\n\t\t},\n\t\t{\n\t\t\tname: \"invalid redirect uri\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/foo\",\n\t\t\t\t\"response_type\": \"code\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &displayedAuthErr{Status: http.StatusBadRequest},\n\t\t},\n\t\t{\n\t\t\tname: \"implicit flow\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\", \"id_token\", \"token\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/bar\",\n\t\t\t\t\"response_type\": \"code id_token\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"unsupported response type\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/bar\",\n\t\t\t\t\"response_type\": \"code id_token\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &redirectedAuthErr{Type: errUnsupportedResponseType},\n\t\t},\n\t\t{\n\t\t\tname: \"only token response type\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\", \"id_token\", \"token\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/bar\",\n\t\t\t\t\"response_type\": \"token\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &redirectedAuthErr{Type: errInvalidRequest},\n\t\t},\n\t\t{\n\t\t\tname: \"choose connector_id\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\", \"id_token\", \"token\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"connector_id\":  \"mock\",\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/bar\",\n\t\t\t\t\"response_type\": \"code id_token\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"choose second connector_id\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\", \"id_token\", \"token\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"connector_id\":  \"mock2\",\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/bar\",\n\t\t\t\t\"response_type\": \"code id_token\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"choose invalid connector_id\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\", \"id_token\", \"token\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"connector_id\":  \"bogus\",\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/bar\",\n\t\t\t\t\"response_type\": \"code id_token\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &redirectedAuthErr{Type: errInvalidRequest},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE code_challenge_method plain\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":             \"bar\",\n\t\t\t\t\"redirect_uri\":          \"https://example.com/bar\",\n\t\t\t\t\"response_type\":         \"code\",\n\t\t\t\t\"code_challenge\":        \"123\",\n\t\t\t\t\"code_challenge_method\": \"plain\",\n\t\t\t\t\"scope\":                 \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE code_challenge_method default plain\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":      \"bar\",\n\t\t\t\t\"redirect_uri\":   \"https://example.com/bar\",\n\t\t\t\t\"response_type\":  \"code\",\n\t\t\t\t\"code_challenge\": \"123\",\n\t\t\t\t\"scope\":          \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE code_challenge_method S256\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":             \"bar\",\n\t\t\t\t\"redirect_uri\":          \"https://example.com/bar\",\n\t\t\t\t\"response_type\":         \"code\",\n\t\t\t\t\"code_challenge\":        \"123\",\n\t\t\t\t\"code_challenge_method\": \"S256\",\n\t\t\t\t\"scope\":                 \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE invalid code_challenge_method\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":             \"bar\",\n\t\t\t\t\"redirect_uri\":          \"https://example.com/bar\",\n\t\t\t\t\"response_type\":         \"code\",\n\t\t\t\t\"code_challenge\":        \"123\",\n\t\t\t\t\"code_challenge_method\": \"invalid_method\",\n\t\t\t\t\"scope\":                 \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &redirectedAuthErr{Type: errInvalidRequest},\n\t\t},\n\t\t{\n\t\t\tname: \"No response type\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":             \"bar\",\n\t\t\t\t\"redirect_uri\":          \"https://example.com/bar\",\n\t\t\t\t\"code_challenge\":        \"123\",\n\t\t\t\t\"code_challenge_method\": \"plain\",\n\t\t\t\t\"scope\":                 \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &redirectedAuthErr{Type: errInvalidRequest},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE enforced, no code_challenge provided\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tpkce: PKCEConfig{\n\t\t\t\tEnforce:                       true,\n\t\t\t\tCodeChallengeMethodsSupported: []string{\"S256\", \"plain\"},\n\t\t\t},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":     \"bar\",\n\t\t\t\t\"redirect_uri\":  \"https://example.com/bar\",\n\t\t\t\t\"response_type\": \"code\",\n\t\t\t\t\"scope\":         \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &redirectedAuthErr{Type: errInvalidRequest},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE enforced, code_challenge provided\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tpkce: PKCEConfig{\n\t\t\t\tEnforce:                       true,\n\t\t\t\tCodeChallengeMethodsSupported: []string{\"S256\", \"plain\"},\n\t\t\t},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":             \"bar\",\n\t\t\t\t\"redirect_uri\":          \"https://example.com/bar\",\n\t\t\t\t\"response_type\":         \"code\",\n\t\t\t\t\"code_challenge\":        \"123\",\n\t\t\t\t\"code_challenge_method\": \"S256\",\n\t\t\t\t\"scope\":                 \"openid email profile\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE only S256 allowed, plain rejected\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tpkce: PKCEConfig{\n\t\t\t\tCodeChallengeMethodsSupported: []string{\"S256\"},\n\t\t\t},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":             \"bar\",\n\t\t\t\t\"redirect_uri\":          \"https://example.com/bar\",\n\t\t\t\t\"response_type\":         \"code\",\n\t\t\t\t\"code_challenge\":        \"123\",\n\t\t\t\t\"code_challenge_method\": \"plain\",\n\t\t\t\t\"scope\":                 \"openid email profile\",\n\t\t\t},\n\t\t\texpectedError: &redirectedAuthErr{Type: errInvalidRequest},\n\t\t},\n\t\t{\n\t\t\tname: \"PKCE only S256 allowed, S256 accepted\",\n\t\t\tclients: []storage.Client{\n\t\t\t\t{\n\t\t\t\t\tID:           \"bar\",\n\t\t\t\t\tRedirectURIs: []string{\"https://example.com/bar\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tsupportedResponseTypes: []string{\"code\"},\n\t\t\tpkce: PKCEConfig{\n\t\t\t\tCodeChallengeMethodsSupported: []string{\"S256\"},\n\t\t\t},\n\t\t\tqueryParams: map[string]string{\n\t\t\t\t\"client_id\":             \"bar\",\n\t\t\t\t\"redirect_uri\":          \"https://example.com/bar\",\n\t\t\t\t\"response_type\":         \"code\",\n\t\t\t\t\"code_challenge\":        \"123\",\n\t\t\t\t\"code_challenge_method\": \"S256\",\n\t\t\t\t\"scope\":                 \"openid email profile\",\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thttpServer, server := newTestServerMultipleConnectors(t, func(c *Config) {\n\t\t\t\tc.SupportedResponseTypes = tc.supportedResponseTypes\n\t\t\t\tc.Storage = storage.WithStaticClients(c.Storage, tc.clients)\n\t\t\t\tif len(tc.pkce.CodeChallengeMethodsSupported) > 0 || tc.pkce.Enforce {\n\t\t\t\t\tc.PKCE = tc.pkce\n\t\t\t\t}\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tparams := url.Values{}\n\t\t\tfor k, v := range tc.queryParams {\n\t\t\t\tparams.Set(k, v)\n\t\t\t}\n\t\t\tvar req *http.Request\n\t\t\tif tc.usePOST {\n\t\t\t\tbody := strings.NewReader(params.Encode())\n\t\t\t\treq = httptest.NewRequest(\"POST\", httpServer.URL+\"/auth\", body)\n\t\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\t\t\t} else {\n\t\t\t\treq = httptest.NewRequest(\"GET\", httpServer.URL+\"/auth?\"+params.Encode(), nil)\n\t\t\t}\n\n\t\t\t_, _, err := server.parseAuthorizationRequest(req)\n\t\t\tif tc.expectedError == nil {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: expected no error\", tc.name)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tswitch expectedErr := tc.expectedError.(type) {\n\t\t\t\tcase *redirectedAuthErr:\n\t\t\t\t\te, ok := err.(*redirectedAuthErr)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tt.Fatalf(\"%s: expected redirectedAuthErr error\", tc.name)\n\t\t\t\t\t}\n\t\t\t\t\tif e.Type != expectedErr.Type {\n\t\t\t\t\t\tt.Errorf(\"%s: expected error type %v, got %v\", tc.name, expectedErr.Type, e.Type)\n\t\t\t\t\t}\n\t\t\t\t\tif e.RedirectURI != tc.queryParams[\"redirect_uri\"] {\n\t\t\t\t\t\tt.Errorf(\"%s: expected error to be returned in redirect to %v\", tc.name, tc.queryParams[\"redirect_uri\"])\n\t\t\t\t\t}\n\t\t\t\tcase *displayedAuthErr:\n\t\t\t\t\te, ok := err.(*displayedAuthErr)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\tt.Fatalf(\"%s: expected displayedAuthErr error\", tc.name)\n\t\t\t\t\t}\n\t\t\t\t\tif e.Status != expectedErr.Status {\n\t\t\t\t\t\tt.Errorf(\"%s: expected http status %v, got %v\", tc.name, expectedErr.Status, e.Status)\n\t\t\t\t\t}\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"%s: unsupported error type\", tc.name)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nconst (\n\t// at_hash value and access_token returned by Google.\n\tgoogleAccessTokenHash = \"piwt8oCH-K2D9pXlaS1Y-w\"\n\tgoogleAccessToken     = \"ya29.CjHSA1l5WUn8xZ6HanHFzzdHdbXm-14rxnC7JHch9eFIsZkQEGoWzaYG4o7k5f6BnPLj\"\n\tgoogleSigningAlg      = jose.RS256\n)\n\nfunc TestAccessTokenHash(t *testing.T) {\n\tatHash, err := accessTokenHash(googleSigningAlg, googleAccessToken)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif atHash != googleAccessTokenHash {\n\t\tt.Errorf(\"expected %q got %q\", googleAccessTokenHash, atHash)\n\t}\n}\n\nfunc TestValidRedirectURI(t *testing.T) {\n\ttests := []struct {\n\t\tclient      storage.Client\n\t\tredirectURI string\n\t\twantValid   bool\n\t}{\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://foo.com/bar\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://foo.com/bar/baz\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t// These special desktop + device + localhost URIs are allowed by default.\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"urn:ietf:wg:oauth:2.0:oob\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"/device/callback\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost:8080/\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost:991/bar\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://127.0.0.1:8080/\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://127.0.0.1:991/bar\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://127.0.0.1\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t// Both Public + RedirectURIs configured: Could e.g. be a PKCE-enabled web app.\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://foo.com/bar\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://foo.com/bar/baz\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t// These special desktop + device + localhost URIs are not allowed implicitly when RedirectURIs is non-empty.\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"urn:ietf:wg:oauth:2.0:oob\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"/device/callback\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost:8080/\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost:991/bar\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t// These special desktop + device + localhost URIs can still be specified explicitly.\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\", \"urn:ietf:wg:oauth:2.0:oob\"},\n\t\t\t},\n\t\t\tredirectURI: \"urn:ietf:wg:oauth:2.0:oob\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\", \"/device/callback\"},\n\t\t\t},\n\t\t\tredirectURI: \"/device/callback\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\", \"http://localhost:8080/\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost:8080/\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\", \"http://localhost:991/bar\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost:991/bar\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic:       true,\n\t\t\t\tRedirectURIs: []string{\"http://foo.com/bar\", \"http://localhost\"},\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost\",\n\t\t\twantValid:   true,\n\t\t},\n\t\t// Non-localhost URIs are not allowed implicitly.\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://foo.com/bar\",\n\t\t\twantValid:   false,\n\t\t},\n\t\t{\n\t\t\tclient: storage.Client{\n\t\t\t\tPublic: true,\n\t\t\t},\n\t\t\tredirectURI: \"http://localhost.localhost:8080/\",\n\t\t\twantValid:   false,\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tgot := validateRedirectURI(test.client, test.redirectURI)\n\t\tif got != test.wantValid {\n\t\t\tt.Errorf(\"client=%#v, redirectURI=%q, wanted valid=%t, got=%t\",\n\t\t\t\ttest.client, test.redirectURI, test.wantValid, got)\n\t\t}\n\t}\n}\n\nfunc TestSignerKeySet(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\tif err := s.UpdateKeys(t.Context(), func(keys storage.Keys) (storage.Keys, error) {\n\t\tkeys.SigningKey = &jose.JSONWebKey{\n\t\t\tKey:       testKey,\n\t\t\tKeyID:     \"testkey\",\n\t\t\tAlgorithm: \"RS256\",\n\t\t\tUse:       \"sig\",\n\t\t}\n\t\tkeys.SigningKeyPub = &jose.JSONWebKey{\n\t\t\tKey:       testKey.Public(),\n\t\t\tKeyID:     \"testkey\",\n\t\t\tAlgorithm: \"RS256\",\n\t\t\tUse:       \"sig\",\n\t\t}\n\t\treturn keys, nil\n\t}); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ttests := []struct {\n\t\tname           string\n\t\ttokenGenerator func() (jwt string, err error)\n\t\twantErr        bool\n\t}{\n\t\t{\n\t\t\tname: \"valid token\",\n\t\t\ttokenGenerator: func() (string, error) {\n\t\t\t\tsigner, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: testKey}, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\n\t\t\t\tjws, err := signer.Sign([]byte(\"payload\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\n\t\t\t\treturn jws.CompactSerialize()\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"token signed by different key\",\n\t\t\ttokenGenerator: func() (string, error) {\n\t\t\t\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\n\t\t\t\tsigner, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: key}, nil)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\n\t\t\t\tjws, err := signer.Sign([]byte(\"payload\"))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn \"\", err\n\t\t\t\t}\n\n\t\t\t\treturn jws.CompactSerialize()\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tjwt, err := tc.tokenGenerator()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\t// Create a mock signer for testing\n\t\t\tsig, err := signer.NewMockSigner(testKey)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\tkeySet := &signerKeySet{\n\t\t\t\tsigner: sig,\n\t\t\t}\n\n\t\t\t_, err = keySet.VerifySignature(t.Context(), jwt)\n\t\t\tif (err != nil && !tc.wantErr) || (err == nil && tc.wantErr) {\n\t\t\t\tt.Fatalf(\"wantErr = %v, but got err = %v\", tc.wantErr, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRedirectedAuthErrHandler(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tredirectURI string\n\t\tstate       string\n\t\terrType     string\n\t\tdescription string\n\t\twantStatus  int\n\t\twantErr     bool\n\t}{\n\t\t{\n\t\t\tname:        \"valid redirect uri with error parameters\",\n\t\t\tredirectURI: \"https://example.com/callback\",\n\t\t\tstate:       \"state123\",\n\t\t\terrType:     errInvalidRequest,\n\t\t\tdescription: \"Invalid request parameter\",\n\t\t\twantStatus:  http.StatusSeeOther,\n\t\t\twantErr:     false,\n\t\t},\n\t\t{\n\t\t\tname:        \"valid redirect uri with query params\",\n\t\t\tredirectURI: \"https://example.com/callback?existing=param&another=value\",\n\t\t\tstate:       \"state456\",\n\t\t\terrType:     errAccessDenied,\n\t\t\tdescription: \"User denied access\",\n\t\t\twantStatus:  http.StatusSeeOther,\n\t\t\twantErr:     false,\n\t\t},\n\t\t{\n\t\t\tname:        \"valid redirect uri without description\",\n\t\t\tredirectURI: \"https://example.com/callback\",\n\t\t\tstate:       \"state789\",\n\t\t\terrType:     errServerError,\n\t\t\tdescription: \"\",\n\t\t\twantStatus:  http.StatusSeeOther,\n\t\t\twantErr:     false,\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid redirect uri\",\n\t\t\tredirectURI: \"not a valid url ://\",\n\t\t\tstate:       \"state\",\n\t\t\terrType:     errInvalidRequest,\n\t\t\tdescription: \"Test error\",\n\t\t\twantStatus:  http.StatusBadRequest,\n\t\t\twantErr:     true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\ttc := tc\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\terr := &redirectedAuthErr{\n\t\t\t\tState:       tc.state,\n\t\t\t\tRedirectURI: tc.redirectURI,\n\t\t\t\tType:        tc.errType,\n\t\t\t\tDescription: tc.description,\n\t\t\t}\n\n\t\t\thandler := err.Handler()\n\t\t\tw := httptest.NewRecorder()\n\t\t\tr := httptest.NewRequest(\"GET\", \"/\", nil)\n\n\t\t\thandler.ServeHTTP(w, r)\n\n\t\t\tif w.Code != tc.wantStatus {\n\t\t\t\tt.Errorf(\"expected status %d, got %d\", tc.wantStatus, w.Code)\n\t\t\t}\n\n\t\t\tif tc.wantStatus == http.StatusSeeOther {\n\t\t\t\t// Verify the redirect location is a valid URL\n\t\t\t\tlocation := w.Header().Get(\"Location\")\n\t\t\t\tif location == \"\" {\n\t\t\t\t\tt.Fatalf(\"expected Location header, got empty string\")\n\t\t\t\t}\n\n\t\t\t\t// Parse the redirect URL to verify it's valid\n\t\t\t\tredirectURL, parseErr := url.Parse(location)\n\t\t\t\tif parseErr != nil {\n\t\t\t\t\tt.Fatalf(\"invalid redirect URL: %v\", parseErr)\n\t\t\t\t}\n\n\t\t\t\t// Verify error parameters are present in the query string\n\t\t\t\tquery := redirectURL.Query()\n\t\t\t\tif query.Get(\"state\") != tc.state {\n\t\t\t\t\tt.Errorf(\"expected state %q, got %q\", tc.state, query.Get(\"state\"))\n\t\t\t\t}\n\t\t\t\tif query.Get(\"error\") != tc.errType {\n\t\t\t\t\tt.Errorf(\"expected error type %q, got %q\", tc.errType, query.Get(\"error\"))\n\t\t\t\t}\n\t\t\t\tif tc.description != \"\" && query.Get(\"error_description\") != tc.description {\n\t\t\t\t\tt.Errorf(\"expected error_description %q, got %q\", tc.description, query.Get(\"error_description\"))\n\t\t\t\t}\n\n\t\t\t\t// Verify that existing query parameters are preserved\n\t\t\t\tif tc.name == \"valid redirect uri with query params\" {\n\t\t\t\t\tif query.Get(\"existing\") != \"param\" {\n\t\t\t\t\t\tt.Errorf(\"expected existing parameter 'param', got %q\", query.Get(\"existing\"))\n\t\t\t\t\t}\n\t\t\t\t\tif query.Get(\"another\") != \"value\" {\n\t\t\t\t\t\tt.Errorf(\"expected another parameter 'value', got %q\", query.Get(\"another\"))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\n// signTestIDToken creates a signed JWT with the given claims using the test key.\nfunc signTestIDToken(t *testing.T, claims interface{}) string {\n\tt.Helper()\n\tpayload, err := json.Marshal(claims)\n\trequire.NoError(t, err)\n\n\tjoseSigner, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: testKey}, nil)\n\trequire.NoError(t, err)\n\n\tjws, err := joseSigner.Sign(payload)\n\trequire.NoError(t, err)\n\n\ttoken, err := jws.CompactSerialize()\n\trequire.NoError(t, err)\n\treturn token\n}\n\nfunc TestValidateIDTokenHint(t *testing.T) {\n\tsig, err := signer.NewMockSigner(testKey)\n\trequire.NoError(t, err)\n\n\tissuerURL, err := url.Parse(\"https://issuer.example.com\")\n\trequire.NoError(t, err)\n\n\ts := &Server{\n\t\tsigner:    sig,\n\t\tissuerURL: *issuerURL,\n\t\tlogger:    slog.Default(),\n\t}\n\n\tnow := time.Now()\n\n\tt.Run(\"valid hint (not expired)\", func(t *testing.T) {\n\t\ttoken := signTestIDToken(t, idTokenClaims{\n\t\t\tIssuer:  \"https://issuer.example.com\",\n\t\t\tSubject: \"CgNmb28SA2Jhcg\",\n\t\t\tExpiry:  now.Add(1 * time.Hour).Unix(),\n\t\t})\n\t\tsub, err := s.validateIDTokenHint(t.Context(), token)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"CgNmb28SA2Jhcg\", sub)\n\t})\n\n\tt.Run(\"valid hint (expired)\", func(t *testing.T) {\n\t\ttoken := signTestIDToken(t, idTokenClaims{\n\t\t\tIssuer:  \"https://issuer.example.com\",\n\t\t\tSubject: \"CgNmb28SA2Jhcg\",\n\t\t\tExpiry:  now.Add(-1 * time.Hour).Unix(),\n\t\t})\n\t\tsub, err := s.validateIDTokenHint(t.Context(), token)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"CgNmb28SA2Jhcg\", sub)\n\t})\n\n\tt.Run(\"invalid signature\", func(t *testing.T) {\n\t\totherKey, err := rsa.GenerateKey(rand.Reader, 2048)\n\t\trequire.NoError(t, err)\n\n\t\tpayload, err := json.Marshal(idTokenClaims{\n\t\t\tIssuer:  \"https://issuer.example.com\",\n\t\t\tSubject: \"CgNmb28SA2Jhcg\",\n\t\t\tExpiry:  now.Add(1 * time.Hour).Unix(),\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\tjoseSigner, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.RS256, Key: otherKey}, nil)\n\t\trequire.NoError(t, err)\n\t\tjws, err := joseSigner.Sign(payload)\n\t\trequire.NoError(t, err)\n\t\ttoken, err := jws.CompactSerialize()\n\t\trequire.NoError(t, err)\n\n\t\t_, err = s.validateIDTokenHint(t.Context(), token)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"wrong issuer\", func(t *testing.T) {\n\t\ttoken := signTestIDToken(t, idTokenClaims{\n\t\t\tIssuer:  \"https://wrong-issuer.example.com\",\n\t\t\tSubject: \"CgNmb28SA2Jhcg\",\n\t\t\tExpiry:  now.Add(1 * time.Hour).Unix(),\n\t\t})\n\t\t_, err := s.validateIDTokenHint(t.Context(), token)\n\t\tassert.Error(t, err)\n\t})\n\n\tt.Run(\"malformed token\", func(t *testing.T) {\n\t\t_, err := s.validateIDTokenHint(t.Context(), \"not-a-valid-jwt\")\n\t\tassert.Error(t, err)\n\t})\n}\n\nfunc TestSessionMatchesHint(t *testing.T) {\n\t// genSubject(\"foo\", \"bar\") == \"CgNmb28SA2Jhcg\" (from TestGetSubject)\n\tassert.True(t, sessionMatchesHint(&storage.AuthSession{UserID: \"foo\", ConnectorID: \"bar\"}, \"CgNmb28SA2Jhcg\"))\n\tassert.False(t, sessionMatchesHint(&storage.AuthSession{UserID: \"other\", ConnectorID: \"bar\"}, \"CgNmb28SA2Jhcg\"))\n\tassert.False(t, sessionMatchesHint(&storage.AuthSession{UserID: \"foo\", ConnectorID: \"other\"}, \"CgNmb28SA2Jhcg\"))\n\tassert.False(t, sessionMatchesHint(nil, \"CgNmb28SA2Jhcg\"))\n}\n\nfunc TestParseAuthorizationRequest_IDTokenHint(t *testing.T) {\n\tsig, err := signer.NewMockSigner(testKey)\n\trequire.NoError(t, err)\n\n\tnow := time.Now()\n\n\tt.Run(\"valid id_token_hint populates subject\", func(t *testing.T) {\n\t\thttpServer, server := newTestServerMultipleConnectors(t, func(c *Config) {\n\t\t\tc.SupportedResponseTypes = []string{\"code\"}\n\t\t\tc.Storage = storage.WithStaticClients(c.Storage, []storage.Client{\n\t\t\t\t{ID: \"foo\", RedirectURIs: []string{\"https://example.com/foo\"}},\n\t\t\t})\n\t\t\tc.Signer = sig\n\t\t})\n\t\tdefer httpServer.Close()\n\n\t\ttoken := signTestIDToken(t, idTokenClaims{\n\t\t\tIssuer:  httpServer.URL,\n\t\t\tSubject: \"CgNmb28SA2Jhcg\",\n\t\t\tExpiry:  now.Add(1 * time.Hour).Unix(),\n\t\t})\n\n\t\tparams := url.Values{\n\t\t\t\"client_id\":     {\"foo\"},\n\t\t\t\"redirect_uri\":  {\"https://example.com/foo\"},\n\t\t\t\"response_type\": {\"code\"},\n\t\t\t\"scope\":         {\"openid\"},\n\t\t\t\"id_token_hint\": {token},\n\t\t}\n\t\treq := httptest.NewRequest(\"GET\", httpServer.URL+\"/auth?\"+params.Encode(), nil)\n\n\t\t_, hintSubject, err := server.parseAuthorizationRequest(req)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"CgNmb28SA2Jhcg\", hintSubject)\n\t})\n\n\tt.Run(\"invalid id_token_hint returns error\", func(t *testing.T) {\n\t\thttpServer, server := newTestServerMultipleConnectors(t, func(c *Config) {\n\t\t\tc.SupportedResponseTypes = []string{\"code\"}\n\t\t\tc.Storage = storage.WithStaticClients(c.Storage, []storage.Client{\n\t\t\t\t{ID: \"foo\", RedirectURIs: []string{\"https://example.com/foo\"}},\n\t\t\t})\n\t\t\tc.Signer = sig\n\t\t})\n\t\tdefer httpServer.Close()\n\n\t\tparams := url.Values{\n\t\t\t\"client_id\":     {\"foo\"},\n\t\t\t\"redirect_uri\":  {\"https://example.com/foo\"},\n\t\t\t\"response_type\": {\"code\"},\n\t\t\t\"scope\":         {\"openid\"},\n\t\t\t\"id_token_hint\": {\"invalid-token\"},\n\t\t}\n\t\treq := httptest.NewRequest(\"GET\", httpServer.URL+\"/auth?\"+params.Encode(), nil)\n\n\t\t_, _, err := server.parseAuthorizationRequest(req)\n\t\trequire.Error(t, err)\n\t\tredirectErr, ok := err.(*redirectedAuthErr)\n\t\trequire.True(t, ok)\n\t\tassert.Equal(t, errInvalidRequest, redirectErr.Type)\n\t})\n\n\tt.Run(\"no id_token_hint leaves subject empty\", func(t *testing.T) {\n\t\thttpServer, server := newTestServerMultipleConnectors(t, func(c *Config) {\n\t\t\tc.SupportedResponseTypes = []string{\"code\"}\n\t\t\tc.Storage = storage.WithStaticClients(c.Storage, []storage.Client{\n\t\t\t\t{ID: \"foo\", RedirectURIs: []string{\"https://example.com/foo\"}},\n\t\t\t})\n\t\t})\n\t\tdefer httpServer.Close()\n\n\t\tparams := url.Values{\n\t\t\t\"client_id\":     {\"foo\"},\n\t\t\t\"redirect_uri\":  {\"https://example.com/foo\"},\n\t\t\t\"response_type\": {\"code\"},\n\t\t\t\"scope\":         {\"openid\"},\n\t\t}\n\t\treq := httptest.NewRequest(\"GET\", httpServer.URL+\"/auth?\"+params.Encode(), nil)\n\n\t\t_, hintSubject, err := server.parseAuthorizationRequest(req)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"\", hintSubject)\n\t})\n}\n"
  },
  {
    "path": "server/prompt.go",
    "content": "package server\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\n// Prompt represents the parsed OIDC \"prompt\" parameter (RFC 6749 / OpenID Connect Core 3.1.2.1).\n// The parameter is space-separated and may contain: \"none\", \"login\", \"consent\", \"select_account\".\n// \"none\" must not be combined with any other value.\ntype Prompt struct {\n\tnone    bool\n\tlogin   bool\n\tconsent bool\n}\n\n// ParsePrompt parses and validates the raw prompt query parameter.\n// Returns an error suitable for returning as an OAuth2 invalid_request if the value is invalid.\nfunc ParsePrompt(raw string) (Prompt, error) {\n\traw = strings.TrimSpace(raw)\n\tif raw == \"\" {\n\t\treturn Prompt{}, nil\n\t}\n\n\tvar p Prompt\n\tseen := make(map[string]bool)\n\n\tfor _, v := range strings.Fields(raw) {\n\t\tif seen[v] {\n\t\t\tcontinue\n\t\t}\n\t\tseen[v] = true\n\n\t\tswitch v {\n\t\tcase \"none\":\n\t\t\tp.none = true\n\t\tcase \"login\":\n\t\t\tp.login = true\n\t\tcase \"consent\":\n\t\t\tp.consent = true\n\t\tcase \"select_account\":\n\t\t\t// Dex does not support account selection; ignore per spec recommendation.\n\t\tdefault:\n\t\t\treturn Prompt{}, fmt.Errorf(\"invalid prompt value %q\", v)\n\t\t}\n\t}\n\n\tif p.none && (p.login || p.consent) {\n\t\treturn Prompt{}, fmt.Errorf(\"prompt=none must not be combined with other values\")\n\t}\n\n\treturn p, nil\n}\n\n// None returns true if the caller requested no interactive UI.\nfunc (p Prompt) None() bool { return p.none }\n\n// Login returns true if the caller requested forced re-authentication.\nfunc (p Prompt) Login() bool { return p.login }\n\n// Consent returns true if the caller requested forced consent screen.\nfunc (p Prompt) Consent() bool { return p.consent }\n\n// String returns the canonical space-separated representation stored in the database.\nfunc (p Prompt) String() string {\n\tvar parts []string\n\tif p.none {\n\t\treturn \"none\"\n\t}\n\tif p.login {\n\t\tparts = append(parts, \"login\")\n\t}\n\tif p.consent {\n\t\tparts = append(parts, \"consent\")\n\t}\n\treturn strings.Join(parts, \" \")\n}\n"
  },
  {
    "path": "server/prompt_test.go",
    "content": "package server\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParsePrompt(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\traw     string\n\t\twant    Prompt\n\t\twantErr bool\n\t}{\n\t\t{name: \"empty\", raw: \"\", want: Prompt{}},\n\t\t{name: \"none\", raw: \"none\", want: Prompt{none: true}},\n\t\t{name: \"login\", raw: \"login\", want: Prompt{login: true}},\n\t\t{name: \"consent\", raw: \"consent\", want: Prompt{consent: true}},\n\t\t{name: \"login consent\", raw: \"login consent\", want: Prompt{login: true, consent: true}},\n\t\t{name: \"consent login\", raw: \"consent login\", want: Prompt{login: true, consent: true}},\n\t\t{name: \"select_account ignored\", raw: \"select_account\", want: Prompt{}},\n\t\t{name: \"login select_account\", raw: \"login select_account\", want: Prompt{login: true}},\n\t\t{name: \"duplicate values\", raw: \"login login\", want: Prompt{login: true}},\n\t\t{name: \"whitespace padding\", raw: \"  login  \", want: Prompt{login: true}},\n\n\t\t// Errors.\n\t\t{name: \"none with login\", raw: \"none login\", wantErr: true},\n\t\t{name: \"none with consent\", raw: \"none consent\", wantErr: true},\n\t\t{name: \"unknown value\", raw: \"bogus\", wantErr: true},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tgot, err := ParsePrompt(tc.raw)\n\t\t\tif tc.wantErr {\n\t\t\t\trequire.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tc.want, got)\n\t\t})\n\t}\n}\n\nfunc TestPromptString(t *testing.T) {\n\ttests := []struct {\n\t\tprompt Prompt\n\t\twant   string\n\t}{\n\t\t{Prompt{}, \"\"},\n\t\t{Prompt{none: true}, \"none\"},\n\t\t{Prompt{login: true}, \"login\"},\n\t\t{Prompt{consent: true}, \"consent\"},\n\t\t{Prompt{login: true, consent: true}, \"login consent\"},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.want, func(t *testing.T) {\n\t\t\tassert.Equal(t, tc.want, tc.prompt.String())\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/refreshhandlers.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\ntype RefreshTokenPolicy struct {\n\trotateRefreshTokens bool // enable rotation\n\n\tabsoluteLifetime  time.Duration // interval from token creation to the end of its life\n\tvalidIfNotUsedFor time.Duration // interval from last token update to the end of its life\n\treuseInterval     time.Duration // interval within which old refresh token is allowed to be reused\n\n\tnow func() time.Time\n\n\tlogger *slog.Logger\n}\n\nfunc NewRefreshTokenPolicy(logger *slog.Logger, rotation bool, validIfNotUsedFor, absoluteLifetime, reuseInterval string) (*RefreshTokenPolicy, error) {\n\tr := RefreshTokenPolicy{now: time.Now, logger: logger}\n\tvar err error\n\n\tif validIfNotUsedFor != \"\" {\n\t\tr.validIfNotUsedFor, err = time.ParseDuration(validIfNotUsedFor)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid config value %q for refresh token valid if not used for: %v\", validIfNotUsedFor, err)\n\t\t}\n\t\tlogger.Info(\"config refresh tokens\", \"valid_if_not_used_for\", validIfNotUsedFor)\n\t}\n\n\tif absoluteLifetime != \"\" {\n\t\tr.absoluteLifetime, err = time.ParseDuration(absoluteLifetime)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid config value %q for refresh tokens absolute lifetime: %v\", absoluteLifetime, err)\n\t\t}\n\t\tlogger.Info(\"config refresh tokens\", \"absolute_lifetime\", absoluteLifetime)\n\t}\n\n\tif reuseInterval != \"\" {\n\t\tr.reuseInterval, err = time.ParseDuration(reuseInterval)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"invalid config value %q for refresh tokens reuse interval: %v\", reuseInterval, err)\n\t\t}\n\t\tlogger.Info(\"config refresh tokens\", \"reuse_interval\", reuseInterval)\n\t}\n\n\tr.rotateRefreshTokens = !rotation\n\tlogger.Info(\"config refresh tokens rotation\", \"enabled\", r.rotateRefreshTokens)\n\treturn &r, nil\n}\n\nfunc (r *RefreshTokenPolicy) RotationEnabled() bool {\n\treturn r.rotateRefreshTokens\n}\n\nfunc (r *RefreshTokenPolicy) CompletelyExpired(lastUsed time.Time) bool {\n\tif r.absoluteLifetime == 0 {\n\t\treturn false // expiration disabled\n\t}\n\treturn r.now().After(lastUsed.Add(r.absoluteLifetime))\n}\n\nfunc (r *RefreshTokenPolicy) ExpiredBecauseUnused(lastUsed time.Time) bool {\n\tif r.validIfNotUsedFor == 0 {\n\t\treturn false // expiration disabled\n\t}\n\treturn r.now().After(lastUsed.Add(r.validIfNotUsedFor))\n}\n\nfunc (r *RefreshTokenPolicy) AllowedToReuse(lastUsed time.Time) bool {\n\tif r.reuseInterval == 0 {\n\t\treturn false // expiration disabled\n\t}\n\treturn !r.now().After(lastUsed.Add(r.reuseInterval))\n}\n\nfunc contains(arr []string, item string) bool {\n\tfor _, itemFromArray := range arr {\n\t\tif itemFromArray == item {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\ntype refreshError struct {\n\tmsg  string\n\tcode int\n\tdesc string\n}\n\nfunc (r *refreshError) Error() string {\n\treturn fmt.Sprintf(\"refresh token error: status %d, %q %s\", r.code, r.msg, r.desc)\n}\n\nfunc newInternalServerError() *refreshError {\n\treturn &refreshError{msg: errInvalidRequest, desc: \"\", code: http.StatusInternalServerError}\n}\n\nfunc newBadRequestError(desc string) *refreshError {\n\treturn &refreshError{msg: errInvalidRequest, desc: desc, code: http.StatusBadRequest}\n}\n\nvar (\n\tinvalidErr = newBadRequestError(\"Refresh token is invalid or has already been claimed by another client.\")\n\texpiredErr = newBadRequestError(\"Refresh token expired.\")\n)\n\nfunc (s *Server) refreshTokenErrHelper(w http.ResponseWriter, err *refreshError) {\n\ts.tokenErrHelper(w, err.msg, err.desc, err.code)\n}\n\nfunc (s *Server) extractRefreshTokenFromRequest(r *http.Request) (*internal.RefreshToken, *refreshError) {\n\tcode := r.PostFormValue(\"refresh_token\")\n\tif code == \"\" {\n\t\treturn nil, newBadRequestError(\"No refresh token is found in request.\")\n\t}\n\n\ttoken := new(internal.RefreshToken)\n\tif err := internal.Unmarshal(code, token); err != nil {\n\t\t// For backward compatibility, assume the refresh_token is a raw refresh token ID\n\t\t// if it fails to decode.\n\t\t//\n\t\t// Because refresh_token values that aren't unmarshable were generated by servers\n\t\t// that don't have a Token value, we'll still reject any attempts to claim a\n\t\t// refresh_token twice.\n\t\ttoken = &internal.RefreshToken{RefreshId: code, Token: \"\"}\n\t}\n\n\treturn token, nil\n}\n\ntype refreshContext struct {\n\tstorageToken *storage.RefreshToken\n\trequestToken *internal.RefreshToken\n\n\tconnector     Connector\n\tconnectorData []byte\n\n\tscopes []string\n}\n\n// getRefreshTokenFromStorage checks that refresh token is valid and exists in the storage and gets its info\nfunc (s *Server) getRefreshTokenFromStorage(ctx context.Context, clientID *string, token *internal.RefreshToken) (*refreshContext, *refreshError) {\n\trefreshCtx := refreshContext{requestToken: token}\n\n\t// Get RefreshToken\n\trefresh, err := s.storage.GetRefresh(ctx, token.RefreshId)\n\tif err != nil {\n\t\tif err != storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get refresh token\", \"err\", err)\n\t\t\treturn nil, newInternalServerError()\n\t\t}\n\t\treturn nil, invalidErr\n\t}\n\n\t// Only check ClientID if it was provided;\n\tif clientID != nil && (refresh.ClientID != *clientID) {\n\t\ts.logger.ErrorContext(ctx, \"trying to claim token for different client\", \"client_id\", clientID, \"refresh_client_id\", refresh.ClientID)\n\t\t// According to https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 Dex should respond with an\n\t\t//  invalid grant error if token has already been claimed by another client.\n\t\treturn nil, &refreshError{msg: errInvalidGrant, desc: invalidErr.desc, code: http.StatusBadRequest}\n\t}\n\n\tif refresh.Token != token.Token {\n\t\tswitch {\n\t\tcase !s.refreshTokenPolicy.AllowedToReuse(refresh.LastUsed):\n\t\t\tfallthrough\n\t\tcase refresh.ObsoleteToken != token.Token:\n\t\t\tfallthrough\n\t\tcase refresh.ObsoleteToken == \"\":\n\t\t\ts.logger.ErrorContext(ctx, \"refresh token claimed twice\", \"token_id\", refresh.ID)\n\t\t\treturn nil, invalidErr\n\t\t}\n\t}\n\n\tif s.refreshTokenPolicy.CompletelyExpired(refresh.CreatedAt) {\n\t\ts.logger.ErrorContext(ctx, \"refresh token expired\", \"token_id\", refresh.ID)\n\t\treturn nil, expiredErr\n\t}\n\n\tif s.refreshTokenPolicy.ExpiredBecauseUnused(refresh.LastUsed) {\n\t\ts.logger.ErrorContext(ctx, \"refresh token expired due to inactivity\", \"token_id\", refresh.ID)\n\t\treturn nil, expiredErr\n\t}\n\n\trefreshCtx.storageToken = &refresh\n\n\t// Get Connector\n\trefreshCtx.connector, err = s.getConnector(ctx, refresh.ConnectorID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"connector not found\", \"connector_id\", refresh.ConnectorID, \"err\", err)\n\t\treturn nil, newInternalServerError()\n\t}\n\tif !GrantTypeAllowed(refreshCtx.connector.GrantTypes, grantTypeRefreshToken) {\n\t\ts.logger.ErrorContext(ctx, \"connector does not allow refresh token grant\", \"connector_id\", refresh.ConnectorID)\n\t\treturn nil, &refreshError{msg: errInvalidRequest, desc: \"Connector does not support refresh tokens.\", code: http.StatusBadRequest}\n\t}\n\n\t// Get Connector Data\n\tsession, err := s.storage.GetOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID)\n\tswitch {\n\tcase err != nil:\n\t\tif err != storage.ErrNotFound {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get offline session\", \"err\", err)\n\t\t\treturn nil, newInternalServerError()\n\t\t}\n\tcase len(refresh.ConnectorData) > 0:\n\t\t// Use the old connector data if it exists, should be deleted once used\n\t\trefreshCtx.connectorData = refresh.ConnectorData\n\tdefault:\n\t\trefreshCtx.connectorData = session.ConnectorData\n\t}\n\n\treturn &refreshCtx, nil\n}\n\nfunc (s *Server) getRefreshScopes(r *http.Request, refresh *storage.RefreshToken) ([]string, *refreshError) {\n\t// Per the OAuth2 spec, if the client has omitted the scopes, default to the original\n\t// authorized scopes.\n\t//\n\t// https://tools.ietf.org/html/rfc6749#section-6\n\tscope := r.PostFormValue(\"scope\")\n\n\tif scope == \"\" {\n\t\treturn refresh.Scopes, nil\n\t}\n\n\trequestedScopes := strings.Fields(scope)\n\tvar unauthorizedScopes []string\n\n\t// Per the OAuth2 spec, if the client has omitted the scopes, default to the original\n\t// authorized scopes.\n\t//\n\t// https://tools.ietf.org/html/rfc6749#section-6\n\tfor _, requestScope := range requestedScopes {\n\t\tif !contains(refresh.Scopes, requestScope) {\n\t\t\tunauthorizedScopes = append(unauthorizedScopes, requestScope)\n\t\t}\n\t}\n\n\tif len(unauthorizedScopes) > 0 {\n\t\tdesc := fmt.Sprintf(\"Requested scopes contain unauthorized scope(s): %q.\", unauthorizedScopes)\n\t\treturn nil, newBadRequestError(desc)\n\t}\n\n\treturn requestedScopes, nil\n}\n\nfunc (s *Server) refreshWithConnector(ctx context.Context, rCtx *refreshContext, ident connector.Identity) (connector.Identity, *refreshError) {\n\t// Can the connector refresh the identity? If so, attempt to refresh the data\n\t// in the connector.\n\t//\n\t// TODO(ericchiang): We may want a strict mode where connectors that don't implement\n\t// this interface can't perform refreshing.\n\tif refreshConn, ok := rCtx.connector.Connector.(connector.RefreshConnector); ok {\n\t\t// Set connector data to the one received from an offline session\n\t\tident.ConnectorData = rCtx.connectorData\n\t\ts.logger.Debug(\"connector data before refresh\", \"connector_data\", ident.ConnectorData)\n\n\t\tnewIdent, err := refreshConn.Refresh(ctx, parseScopes(rCtx.scopes), ident)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to refresh identity\", \"err\", err)\n\t\t\treturn ident, newInternalServerError()\n\t\t}\n\n\t\treturn newIdent, nil\n\t}\n\treturn ident, nil\n}\n\n// updateOfflineSession updates offline session in the storage\nfunc (s *Server) updateOfflineSession(ctx context.Context, refresh *storage.RefreshToken, ident connector.Identity, lastUsed time.Time) *refreshError {\n\tofflineSessionUpdater := func(old storage.OfflineSessions) (storage.OfflineSessions, error) {\n\t\tif old.Refresh[refresh.ClientID].ID != refresh.ID {\n\t\t\treturn old, errors.New(\"refresh token invalid\")\n\t\t}\n\n\t\told.Refresh[refresh.ClientID].LastUsed = lastUsed\n\t\tif len(ident.ConnectorData) > 0 {\n\t\t\told.ConnectorData = ident.ConnectorData\n\t\t}\n\n\t\ts.logger.DebugContext(ctx, \"saved connector data\", \"user_id\", ident.UserID, \"connector_data\", ident.ConnectorData)\n\n\t\treturn old, nil\n\t}\n\n\t// Update LastUsed time stamp in refresh token reference object\n\t// in offline session for the user.\n\terr := s.storage.UpdateOfflineSessions(ctx, refresh.Claims.UserID, refresh.ConnectorID, offlineSessionUpdater)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to update offline session\", \"err\", err)\n\t\treturn newInternalServerError()\n\t}\n\n\treturn nil\n}\n\n// updateRefreshToken updates refresh token and offline session in the storage\nfunc (s *Server) updateRefreshToken(ctx context.Context, rCtx *refreshContext) (*internal.RefreshToken, connector.Identity, *refreshError) {\n\tvar rerr *refreshError\n\n\tnewToken := &internal.RefreshToken{\n\t\tToken:     rCtx.requestToken.Token,\n\t\tRefreshId: rCtx.requestToken.RefreshId,\n\t}\n\n\tlastUsed := s.now()\n\n\tident := connector.Identity{\n\t\tUserID:            rCtx.storageToken.Claims.UserID,\n\t\tUsername:          rCtx.storageToken.Claims.Username,\n\t\tPreferredUsername: rCtx.storageToken.Claims.PreferredUsername,\n\t\tEmail:             rCtx.storageToken.Claims.Email,\n\t\tEmailVerified:     rCtx.storageToken.Claims.EmailVerified,\n\t\tGroups:            rCtx.storageToken.Claims.Groups,\n\t}\n\n\trefreshTokenUpdater := func(old storage.RefreshToken) (storage.RefreshToken, error) {\n\t\trotationEnabled := s.refreshTokenPolicy.RotationEnabled()\n\t\treusingAllowed := s.refreshTokenPolicy.AllowedToReuse(old.LastUsed)\n\n\t\tswitch {\n\t\tcase !rotationEnabled && reusingAllowed:\n\t\t\t// If rotation is disabled and the offline session was updated not so long ago - skip further actions.\n\t\t\told.ConnectorData = nil\n\t\t\treturn old, nil\n\n\t\tcase rotationEnabled && reusingAllowed:\n\t\t\tif old.Token != rCtx.requestToken.Token && old.ObsoleteToken != rCtx.requestToken.Token {\n\t\t\t\treturn old, errors.New(\"refresh token claimed twice\")\n\t\t\t}\n\n\t\t\t// Return previously generated token for all requests with an obsolete tokens\n\t\t\tif old.ObsoleteToken == rCtx.requestToken.Token {\n\t\t\t\tnewToken.Token = old.Token\n\t\t\t}\n\n\t\t\t// Do not update last used time for offline session if token is allowed to be reused\n\t\t\tlastUsed = old.LastUsed\n\t\t\told.ConnectorData = nil\n\t\t\treturn old, nil\n\n\t\tcase rotationEnabled && !reusingAllowed:\n\t\t\tif old.Token != rCtx.requestToken.Token {\n\t\t\t\treturn old, errors.New(\"refresh token claimed twice\")\n\t\t\t}\n\n\t\t\t// Issue new refresh token\n\t\t\told.ObsoleteToken = old.Token\n\t\t\tnewToken.Token = storage.NewID()\n\t\t}\n\n\t\told.Token = newToken.Token\n\t\told.LastUsed = lastUsed\n\n\t\t// ConnectorData has been moved to OfflineSession\n\t\told.ConnectorData = nil\n\n\t\t// Call  only once if there is a request which is not in the reuse interval.\n\t\t// This is required to avoid multiple calls to the external IdP for concurrent requests.\n\t\t// Dex will call the connector's Refresh method only once if request is not in reuse interval.\n\t\tident, rerr = s.refreshWithConnector(ctx, rCtx, ident)\n\t\tif rerr != nil {\n\t\t\treturn old, rerr\n\t\t}\n\n\t\t// Update the claims of the refresh token.\n\t\t//\n\t\t// UserID intentionally ignored for now.\n\t\told.Claims.Username = ident.Username\n\t\told.Claims.PreferredUsername = ident.PreferredUsername\n\t\told.Claims.Email = ident.Email\n\t\told.Claims.EmailVerified = ident.EmailVerified\n\t\told.Claims.Groups = ident.Groups\n\n\t\treturn old, nil\n\t}\n\n\t// Update refresh token in the storage.\n\terr := s.storage.UpdateRefreshToken(ctx, rCtx.storageToken.ID, refreshTokenUpdater)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"failed to update refresh token\", \"err\", err)\n\t\treturn nil, ident, newInternalServerError()\n\t}\n\n\trerr = s.updateOfflineSession(ctx, rCtx.storageToken, ident, lastUsed)\n\tif rerr != nil {\n\t\treturn nil, ident, rerr\n\t}\n\n\treturn newToken, ident, nil\n}\n\n// handleRefreshToken handles a refresh token request https://tools.ietf.org/html/rfc6749#section-6\n// this method is the entrypoint for refresh tokens handling\nfunc (s *Server) handleRefreshToken(w http.ResponseWriter, r *http.Request, client storage.Client) {\n\ttoken, rerr := s.extractRefreshTokenFromRequest(r)\n\tif rerr != nil {\n\t\ts.refreshTokenErrHelper(w, rerr)\n\t\treturn\n\t}\n\n\trCtx, rerr := s.getRefreshTokenFromStorage(r.Context(), &client.ID, token)\n\tif rerr != nil {\n\t\ts.refreshTokenErrHelper(w, rerr)\n\t\treturn\n\t}\n\n\trCtx.scopes, rerr = s.getRefreshScopes(r, rCtx.storageToken)\n\tif rerr != nil {\n\t\ts.refreshTokenErrHelper(w, rerr)\n\t\treturn\n\t}\n\n\tnewToken, ident, rerr := s.updateRefreshToken(r.Context(), rCtx)\n\tif rerr != nil {\n\t\ts.refreshTokenErrHelper(w, rerr)\n\t\treturn\n\t}\n\n\tclaims := storage.Claims{\n\t\tUserID:            ident.UserID,\n\t\tUsername:          ident.Username,\n\t\tPreferredUsername: ident.PreferredUsername,\n\t\tEmail:             ident.Email,\n\t\tEmailVerified:     ident.EmailVerified,\n\t\tGroups:            ident.Groups,\n\t}\n\n\tauthTime := time.Time{}\n\tif s.sessionConfig != nil {\n\t\tui, err := s.storage.GetUserIdentity(r.Context(), ident.UserID, rCtx.storageToken.ConnectorID)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(r.Context(), \"failed to get user identity\", \"err\", err)\n\t\t\ts.refreshTokenErrHelper(w, newInternalServerError())\n\t\t\treturn\n\t\t}\n\t\tauthTime = ui.LastLogin\n\t}\n\n\taccessToken, _, err := s.newAccessToken(r.Context(), client.ID, claims, rCtx.scopes, rCtx.storageToken.Nonce, rCtx.storageToken.ConnectorID, authTime)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to create new access token\", \"err\", err)\n\t\ts.refreshTokenErrHelper(w, newInternalServerError())\n\t\treturn\n\t}\n\n\tidToken, expiry, err := s.newIDToken(r.Context(), client.ID, claims, rCtx.scopes, rCtx.storageToken.Nonce, accessToken, \"\", rCtx.storageToken.ConnectorID, authTime)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to create ID token\", \"err\", err)\n\t\ts.refreshTokenErrHelper(w, newInternalServerError())\n\t\treturn\n\t}\n\n\trawNewToken, err := internal.Marshal(newToken)\n\tif err != nil {\n\t\ts.logger.ErrorContext(r.Context(), \"failed to marshal refresh token\", \"err\", err)\n\t\ts.refreshTokenErrHelper(w, newInternalServerError())\n\t\treturn\n\t}\n\n\tresp := s.toAccessTokenResponse(idToken, accessToken, rawNewToken, expiry)\n\ts.writeAccessToken(w, resp)\n}\n"
  },
  {
    "path": "server/refreshhandlers_test.go",
    "content": "package server\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"path\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/server/internal\"\n\t\"github.com/dexidp/dex/storage\"\n)\n\nfunc mockRefreshTokenTestStorage(t *testing.T, s storage.Storage, useObsolete bool) {\n\tctx := t.Context()\n\tc := storage.Client{\n\t\tID:           \"test\",\n\t\tSecret:       \"barfoo\",\n\t\tRedirectURIs: []string{\"foo://bar.com/\", \"https://auth.example.com\"},\n\t\tName:         \"dex client\",\n\t\tLogoURL:      \"https://goo.gl/JIyzIC\",\n\t}\n\n\terr := s.CreateClient(ctx, c)\n\trequire.NoError(t, err)\n\n\tc1 := storage.Connector{\n\t\tID:     \"test\",\n\t\tType:   \"mockCallback\",\n\t\tName:   \"mockCallback\",\n\t\tConfig: nil,\n\t}\n\n\terr = s.CreateConnector(ctx, c1)\n\trequire.NoError(t, err)\n\n\trefresh := storage.RefreshToken{\n\t\tID:            \"test\",\n\t\tToken:         \"bar\",\n\t\tObsoleteToken: \"\",\n\t\tNonce:         \"foo\",\n\t\tClientID:      \"test\",\n\t\tConnectorID:   \"test\",\n\t\tScopes:        []string{\"openid\", \"email\", \"profile\"},\n\t\tCreatedAt:     time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:      time.Now().UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t}\n\n\tif useObsolete {\n\t\trefresh.Token = \"testtest\"\n\t\trefresh.ObsoleteToken = \"bar\"\n\t}\n\n\terr = s.CreateRefresh(ctx, refresh)\n\trequire.NoError(t, err)\n\n\tofflineSessions := storage.OfflineSessions{\n\t\tUserID:        \"1\",\n\t\tConnID:        \"test\",\n\t\tRefresh:       map[string]*storage.RefreshTokenRef{\"test\": {ID: \"test\", ClientID: \"test\"}},\n\t\tConnectorData: nil,\n\t}\n\n\terr = s.CreateOfflineSessions(ctx, offlineSessions)\n\trequire.NoError(t, err)\n}\n\nfunc TestRefreshTokenExpirationScenarios(t *testing.T) {\n\tt0 := time.Now()\n\ttests := []struct {\n\t\tname        string\n\t\tpolicy      *RefreshTokenPolicy\n\t\tuseObsolete bool\n\t\terror       string\n\t}{\n\t\t{\n\t\t\tname:   \"Normal\",\n\t\t\tpolicy: &RefreshTokenPolicy{rotateRefreshTokens: true},\n\t\t\terror:  ``,\n\t\t},\n\t\t{\n\t\t\tname: \"Not expired because used\",\n\t\t\tpolicy: &RefreshTokenPolicy{\n\t\t\t\trotateRefreshTokens: false,\n\t\t\t\tvalidIfNotUsedFor:   time.Second * 60,\n\t\t\t\tnow:                 func() time.Time { return t0.Add(time.Second * 25) },\n\t\t\t},\n\t\t\terror: ``,\n\t\t},\n\t\t{\n\t\t\tname: \"Expired because not used\",\n\t\t\tpolicy: &RefreshTokenPolicy{\n\t\t\t\trotateRefreshTokens: false,\n\t\t\t\tvalidIfNotUsedFor:   time.Second * 60,\n\t\t\t\tnow:                 func() time.Time { return t0.Add(time.Hour) },\n\t\t\t},\n\t\t\terror: `{\"error\":\"invalid_request\",\"error_description\":\"Refresh token expired.\"}`,\n\t\t},\n\t\t{\n\t\t\tname: \"Absolutely expired\",\n\t\t\tpolicy: &RefreshTokenPolicy{\n\t\t\t\trotateRefreshTokens: true,\n\t\t\t\tabsoluteLifetime:    time.Second * 60,\n\t\t\t\tnow:                 func() time.Time { return t0.Add(time.Hour) },\n\t\t\t},\n\t\t\terror: `{\"error\":\"invalid_request\",\"error_description\":\"Refresh token expired.\"}`,\n\t\t},\n\t\t{\n\t\t\tname:        \"Obsolete tokens are allowed\",\n\t\t\tuseObsolete: true,\n\t\t\tpolicy: &RefreshTokenPolicy{\n\t\t\t\trotateRefreshTokens: true,\n\t\t\t\treuseInterval:       time.Second * 30,\n\t\t\t\tnow:                 func() time.Time { return t0.Add(time.Second * 25) },\n\t\t\t},\n\t\t\terror: ``,\n\t\t},\n\t\t{\n\t\t\tname:        \"Obsolete tokens are not allowed\",\n\t\t\tuseObsolete: true,\n\t\t\tpolicy: &RefreshTokenPolicy{\n\t\t\t\trotateRefreshTokens: true,\n\t\t\t\tnow:                 func() time.Time { return t0.Add(time.Second * 25) },\n\t\t\t},\n\t\t\terror: `{\"error\":\"invalid_request\",\"error_description\":\"Refresh token is invalid or has already been claimed by another client.\"}`,\n\t\t},\n\t\t{\n\t\t\tname:        \"Obsolete tokens are allowed but token is expired globally\",\n\t\t\tuseObsolete: true,\n\t\t\tpolicy: &RefreshTokenPolicy{\n\t\t\t\trotateRefreshTokens: true,\n\t\t\t\treuseInterval:       time.Second * 30,\n\t\t\t\tabsoluteLifetime:    time.Second * 20,\n\t\t\t\tnow:                 func() time.Time { return t0.Add(time.Second * 25) },\n\t\t\t},\n\t\t\terror: `{\"error\":\"invalid_request\",\"error_description\":\"Refresh token expired.\"}`,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(*testing.T) {\n\t\t\t// Setup a dex server.\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.RefreshTokenPolicy = tc.policy\n\t\t\t\tc.Now = func() time.Time { return t0 }\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tmockRefreshTokenTestStorage(t, s.storage, tc.useObsolete)\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttokenData, err := internal.Marshal(&internal.RefreshToken{RefreshId: \"test\", Token: \"bar\"})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tu.Path = path.Join(u.Path, \"/token\")\n\t\t\tv := url.Values{}\n\t\t\tv.Add(\"grant_type\", \"refresh_token\")\n\t\t\tv.Add(\"refresh_token\", tokenData)\n\n\t\t\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(v.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded; param=value\")\n\t\t\treq.SetBasicAuth(\"test\", \"barfoo\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\n\t\t\tif tc.error == \"\" {\n\t\t\t\trequire.Equal(t, 200, rr.Code)\n\t\t\t} else {\n\t\t\t\trequire.Equal(t, rr.Body.String(), tc.error)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Check that we received expected refresh token\n\t\t\tvar ref struct {\n\t\t\t\tToken string `json:\"refresh_token\"`\n\t\t\t}\n\t\t\terr = json.Unmarshal(rr.Body.Bytes(), &ref)\n\t\t\trequire.NoError(t, err)\n\n\t\t\tif tc.policy.rotateRefreshTokens == false {\n\t\t\t\trequire.Equal(t, tokenData, ref.Token)\n\t\t\t} else {\n\t\t\t\trequire.NotEqual(t, tokenData, ref.Token)\n\t\t\t}\n\n\t\t\tif tc.useObsolete {\n\t\t\t\tupdatedTokenData, err := internal.Marshal(&internal.RefreshToken{RefreshId: \"test\", Token: \"testtest\"})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t\trequire.Equal(t, updatedTokenData, ref.Token)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// decodeJWTClaims decodes the payload of a JWT token without verifying the signature.\nfunc decodeJWTClaims(t *testing.T, token string) map[string]any {\n\tt.Helper()\n\tparts := strings.SplitN(token, \".\", 3)\n\trequire.Len(t, parts, 3, \"JWT should have 3 parts\")\n\n\tpayload, err := base64.RawURLEncoding.DecodeString(parts[1])\n\trequire.NoError(t, err)\n\n\tvar claims map[string]any\n\terr = json.Unmarshal(payload, &claims)\n\trequire.NoError(t, err)\n\treturn claims\n}\n\nfunc TestRefreshTokenAuthTime(t *testing.T) {\n\tt0 := time.Now().UTC().Round(time.Second)\n\tloginTime := t0.Add(-10 * time.Minute)\n\n\ttests := []struct {\n\t\tname               string\n\t\tsessionConfig      *SessionConfig\n\t\tcreateUserIdentity bool\n\t\twantAuthTime       bool\n\t\twantHTTPError      bool\n\t}{\n\t\t{\n\t\t\tname: \"sessions enabled with user identity\",\n\t\t\tsessionConfig: &SessionConfig{\n\t\t\t\tCookieName:       \"dex_session\",\n\t\t\t\tAbsoluteLifetime: 24 * time.Hour,\n\t\t\t},\n\t\t\tcreateUserIdentity: true,\n\t\t\twantAuthTime:       true,\n\t\t},\n\t\t{\n\t\t\tname:               \"sessions disabled\",\n\t\t\tsessionConfig:      nil,\n\t\t\tcreateUserIdentity: false,\n\t\t\twantAuthTime:       false,\n\t\t},\n\t\t{\n\t\t\tname: \"sessions enabled but user identity missing\",\n\t\t\tsessionConfig: &SessionConfig{\n\t\t\t\tCookieName:       \"dex_session\",\n\t\t\t\tAbsoluteLifetime: 24 * time.Hour,\n\t\t\t},\n\t\t\tcreateUserIdentity: false,\n\t\t\twantHTTPError:      true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Now = func() time.Time { return t0 }\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\ts.sessionConfig = tc.sessionConfig\n\n\t\t\tmockRefreshTokenTestStorage(t, s.storage, false)\n\n\t\t\tif tc.createUserIdentity {\n\t\t\t\t// The mock connector returns UserID \"0-385-28089-0\" on Refresh,\n\t\t\t\t// so the UserIdentity must use that ID to be found by handleRefreshToken.\n\t\t\t\terr := s.storage.CreateUserIdentity(t.Context(), storage.UserIdentity{\n\t\t\t\t\tUserID:      \"0-385-28089-0\",\n\t\t\t\t\tConnectorID: \"test\",\n\t\t\t\t\tClaims: storage.Claims{\n\t\t\t\t\t\tUserID:        \"0-385-28089-0\",\n\t\t\t\t\t\tUsername:      \"Kilgore Trout\",\n\t\t\t\t\t\tEmail:         \"kilgore@kilgore.trout\",\n\t\t\t\t\t\tEmailVerified: true,\n\t\t\t\t\t\tGroups:        []string{\"authors\"},\n\t\t\t\t\t},\n\t\t\t\t\tCreatedAt: loginTime,\n\t\t\t\t\tLastLogin: loginTime,\n\t\t\t\t})\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\n\t\t\tu, err := url.Parse(s.issuerURL.String())\n\t\t\trequire.NoError(t, err)\n\n\t\t\ttokenData, err := internal.Marshal(&internal.RefreshToken{RefreshId: \"test\", Token: \"bar\"})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tu.Path = path.Join(u.Path, \"/token\")\n\t\t\tv := url.Values{}\n\t\t\tv.Add(\"grant_type\", \"refresh_token\")\n\t\t\tv.Add(\"refresh_token\", tokenData)\n\n\t\t\treq, _ := http.NewRequest(\"POST\", u.String(), bytes.NewBufferString(v.Encode()))\n\t\t\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded; param=value\")\n\t\t\treq.SetBasicAuth(\"test\", \"barfoo\")\n\n\t\t\trr := httptest.NewRecorder()\n\t\t\ts.ServeHTTP(rr, req)\n\n\t\t\tif tc.wantHTTPError {\n\t\t\t\tassert.Equal(t, http.StatusInternalServerError, rr.Code)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.Equal(t, http.StatusOK, rr.Code)\n\n\t\t\tvar resp struct {\n\t\t\t\tAccessToken  string `json:\"access_token\"`\n\t\t\t\tIDToken      string `json:\"id_token\"`\n\t\t\t\tRefreshToken string `json:\"refresh_token\"`\n\t\t\t}\n\t\t\terr = json.Unmarshal(rr.Body.Bytes(), &resp)\n\t\t\trequire.NoError(t, err)\n\n\t\t\taccessClaims := decodeJWTClaims(t, resp.AccessToken)\n\n\t\t\tif tc.wantAuthTime {\n\t\t\t\tassert.Equal(t, float64(loginTime.Unix()), accessClaims[\"auth_time\"],\n\t\t\t\t\t\"access token auth_time should match UserIdentity.LastLogin\")\n\t\t\t} else {\n\t\t\t\tassert.Nil(t, accessClaims[\"auth_time\"],\n\t\t\t\t\t\"access token should not have auth_time when sessions are disabled\")\n\t\t\t}\n\n\t\t\t// TODO: newIDToken in handleRefreshToken is currently called with time.Time{},\n\t\t\t// so the ID token does not include auth_time. Once fixed, uncomment:\n\t\t\t// if tc.wantAuthTime {\n\t\t\t// \tidClaims := decodeJWTClaims(t, resp.IDToken)\n\t\t\t// \tassert.Equal(t, float64(loginTime.Unix()), idClaims[\"auth_time\"],\n\t\t\t// \t\t\"id token auth_time should match UserIdentity.LastLogin\")\n\t\t\t// }\n\t\t})\n\t}\n}\n\nfunc TestRefreshTokenPolicy(t *testing.T) {\n\tlastTime := time.Now()\n\tl := slog.New(slog.DiscardHandler)\n\n\tr, err := NewRefreshTokenPolicy(l, true, \"1m\", \"1m\", \"1m\")\n\trequire.NoError(t, err)\n\n\tt.Run(\"Allowed\", func(t *testing.T) {\n\t\tr.now = func() time.Time { return lastTime }\n\t\trequire.Equal(t, true, r.AllowedToReuse(lastTime))\n\t\trequire.Equal(t, false, r.ExpiredBecauseUnused(lastTime))\n\t\trequire.Equal(t, false, r.CompletelyExpired(lastTime))\n\t})\n\n\tt.Run(\"Expired\", func(t *testing.T) {\n\t\tr.now = func() time.Time { return lastTime.Add(2 * time.Minute) }\n\t\trequire.Equal(t, false, r.AllowedToReuse(lastTime))\n\t\trequire.Equal(t, true, r.ExpiredBecauseUnused(lastTime))\n\t\trequire.Equal(t, true, r.CompletelyExpired(lastTime))\n\t})\n}\n"
  },
  {
    "path": "server/server.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io/fs\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/netip\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"slices\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\tgosundheit \"github.com/AppsFlyer/go-sundheit\"\n\t\"github.com/google/uuid\"\n\t\"github.com/gorilla/handlers\"\n\t\"github.com/gorilla/mux\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"golang.org/x/crypto/bcrypt\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/connector/atlassiancrowd\"\n\t\"github.com/dexidp/dex/connector/authproxy\"\n\t\"github.com/dexidp/dex/connector/bitbucketcloud\"\n\t\"github.com/dexidp/dex/connector/gitea\"\n\t\"github.com/dexidp/dex/connector/github\"\n\t\"github.com/dexidp/dex/connector/gitlab\"\n\t\"github.com/dexidp/dex/connector/google\"\n\t\"github.com/dexidp/dex/connector/keystone\"\n\t\"github.com/dexidp/dex/connector/ldap\"\n\t\"github.com/dexidp/dex/connector/linkedin\"\n\t\"github.com/dexidp/dex/connector/microsoft\"\n\t\"github.com/dexidp/dex/connector/mock\"\n\t\"github.com/dexidp/dex/connector/oauth\"\n\t\"github.com/dexidp/dex/connector/oidc\"\n\t\"github.com/dexidp/dex/connector/openshift\"\n\t\"github.com/dexidp/dex/connector/saml\"\n\t\"github.com/dexidp/dex/pkg/featureflags\"\n\t\"github.com/dexidp/dex/server/signer\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/web\"\n)\n\n// LocalConnector is the local passwordDB connector which is an internal\n// connector maintained by the server.\nconst LocalConnector = \"local\"\n\n// Connector is a connector with resource version metadata.\ntype Connector struct {\n\tType            string\n\tResourceVersion string\n\tConnector       connector.Connector\n\tGrantTypes      []string\n}\n\n// GrantTypeAllowed checks if the given grant type is allowed for this connector.\n// If no grant types are configured, all are allowed.\nfunc GrantTypeAllowed(configuredTypes []string, grantType string) bool {\n\treturn len(configuredTypes) == 0 || slices.Contains(configuredTypes, grantType)\n}\n\n// Config holds the server's configuration options.\n//\n// Multiple servers using the same storage are expected to be configured identically.\ntype Config struct {\n\tIssuer string\n\n\t// The backing persistence layer.\n\tStorage storage.Storage\n\n\tAllowedGrantTypes []string\n\n\t// Valid values are \"code\" to enable the code flow and \"token\" to enable the implicit\n\t// flow. If no response types are supplied this value defaults to \"code\".\n\tSupportedResponseTypes []string\n\n\t// Headers is a map of headers to be added to the all responses.\n\tHeaders http.Header\n\n\t// Header to extract real ip from.\n\tRealIPHeader       string\n\tTrustedRealIPCIDRs []netip.Prefix\n\n\t// List of allowed origins for CORS requests on discovery, token and keys endpoint.\n\t// If none are indicated, CORS requests are disabled. Passing in \"*\" will allow any\n\t// domain.\n\tAllowedOrigins []string\n\n\t// List of allowed headers for CORS requests on discovery, token, and keys endpoint.\n\tAllowedHeaders []string\n\n\t// If enabled, the server won't prompt the user to approve authorization requests.\n\t// Logging in implies approval.\n\tSkipApprovalScreen bool\n\n\t// If enabled, the connectors selection page will always be shown even if there's only one\n\tAlwaysShowLoginScreen bool\n\n\tIDTokensValidFor       time.Duration // Defaults to 24 hours\n\tAuthRequestsValidFor   time.Duration // Defaults to 24 hours\n\tDeviceRequestsValidFor time.Duration // Defaults to 5 minutes\n\n\t// Refresh token expiration settings\n\tRefreshTokenPolicy *RefreshTokenPolicy\n\n\t// If set, the server will use this connector to handle password grants\n\tPasswordConnector string\n\n\t// PKCE configuration\n\tPKCE PKCEConfig\n\n\tGCFrequency time.Duration // Defaults to 5 minutes\n\n\t// If specified, the server will use this function for determining time.\n\tNow func() time.Time\n\n\tWeb WebConfig\n\n\tLogger *slog.Logger\n\n\t// Signer is used to sign tokens.\n\tSigner signer.Signer\n\n\tPrometheusRegistry *prometheus.Registry\n\n\tHealthChecker gosundheit.Health\n\n\t// If enabled, the server will continue starting even if some connectors fail to initialize.\n\t// This allows the server to operate with a subset of connectors if some are misconfigured.\n\tContinueOnConnectorFailure bool\n\n\t// SessionConfig holds session settings. Nil when sessions are disabled.\n\tSessionConfig *SessionConfig\n\n\t// MFAProviders maps authenticator IDs to their provider implementations.\n\tMFAProviders map[string]MFAProvider\n\n\t// DefaultMFAChain is applied to clients that don't specify their own mfaChain.\n\tDefaultMFAChain []string\n}\n\n// SessionConfig holds resolved session configuration.\ntype SessionConfig struct {\n\tCookieName                 string\n\tAbsoluteLifetime           time.Duration\n\tValidIfNotUsedFor          time.Duration\n\tRememberMeCheckedByDefault bool\n}\n\n// WebConfig holds the server's frontend templates and asset configuration.\ntype WebConfig struct {\n\t// A file path to static web assets.\n\t//\n\t// It is expected to contain the following directories:\n\t//\n\t//   * static - Static static served at \"( issuer URL )/static\".\n\t//   * templates - HTML templates controlled by dex.\n\t//   * themes/(theme) - Static static served at \"( issuer URL )/theme\".\n\tDir string\n\n\t// Alternative way to programmatically configure static web assets.\n\t// If Dir is specified, WebFS is ignored.\n\t// It's expected to contain the same files and directories as mentioned above.\n\t//\n\t// Note: this is experimental. Might get removed without notice!\n\tWebFS fs.FS\n\n\t// Defaults to \"( issuer URL )/theme/logo.png\"\n\tLogoURL string\n\n\t// Defaults to \"dex\"\n\tIssuer string\n\n\t// Defaults to \"light\"\n\tTheme string\n\n\t// Map of extra values passed into the templates\n\tExtra map[string]string\n}\n\n// PKCEConfig holds PKCE (Proof Key for Code Exchange) settings.\ntype PKCEConfig struct {\n\t// If true, PKCE is required for all authorization code flows.\n\tEnforce bool\n\t// Supported code challenge methods. Defaults to [\"S256\", \"plain\"].\n\tCodeChallengeMethodsSupported []string\n}\n\nfunc value(val, defaultValue time.Duration) time.Duration {\n\tif val == 0 {\n\t\treturn defaultValue\n\t}\n\treturn val\n}\n\n// Server is the top level object.\ntype Server struct {\n\tissuerURL url.URL\n\n\t// mutex for the connectors map.\n\tmu sync.Mutex\n\t// Map of connector IDs to connectors.\n\tconnectors map[string]Connector\n\n\tstorage storage.Storage\n\n\tmux http.Handler\n\n\ttemplates *templates\n\n\t// If enabled, don't prompt user for approval after logging in through connector.\n\tskipApproval bool\n\n\t// If enabled, show the connector selection screen even if there's only one\n\talwaysShowLogin bool\n\n\t// Used for password grant\n\tpasswordConnector string\n\n\tsupportedResponseTypes map[string]bool\n\n\tsupportedGrantTypes []string\n\n\tpkce PKCEConfig\n\n\tnow func() time.Time\n\n\tidTokensValidFor       time.Duration\n\tauthRequestsValidFor   time.Duration\n\tdeviceRequestsValidFor time.Duration\n\n\trefreshTokenPolicy *RefreshTokenPolicy\n\n\tlogger *slog.Logger\n\n\tsigner signer.Signer\n\n\tsessionConfig *SessionConfig\n\n\tmfaProviders    map[string]MFAProvider\n\tdefaultMFAChain []string\n}\n\n// NewServer constructs a server from the provided config.\nfunc NewServer(ctx context.Context, c Config) (*Server, error) {\n\treturn newServer(ctx, c)\n}\n\nfunc newServer(ctx context.Context, c Config) (*Server, error) {\n\tissuerURL, err := url.Parse(c.Issuer)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"server: can't parse issuer URL\")\n\t}\n\n\tif c.Storage == nil {\n\t\treturn nil, errors.New(\"server: storage cannot be nil\")\n\t}\n\n\tif len(c.SupportedResponseTypes) == 0 {\n\t\tc.SupportedResponseTypes = []string{responseTypeCode}\n\t}\n\tif len(c.AllowedHeaders) == 0 {\n\t\tc.AllowedHeaders = []string{\"Authorization\"}\n\t}\n\n\tsupportedChallengeMethods := map[string]bool{\n\t\tcodeChallengeMethodS256:  true,\n\t\tcodeChallengeMethodPlain: true,\n\t}\n\tif len(c.PKCE.CodeChallengeMethodsSupported) == 0 {\n\t\tc.PKCE.CodeChallengeMethodsSupported = []string{codeChallengeMethodS256, codeChallengeMethodPlain}\n\t}\n\tfor _, m := range c.PKCE.CodeChallengeMethodsSupported {\n\t\tif !supportedChallengeMethods[m] {\n\t\t\treturn nil, fmt.Errorf(\"unsupported PKCE challenge method %q\", m)\n\t\t}\n\t}\n\n\tallSupportedGrants := map[string]bool{\n\t\tgrantTypeAuthorizationCode: true,\n\t\tgrantTypeRefreshToken:      true,\n\t\tgrantTypeDeviceCode:        true,\n\t\tgrantTypeTokenExchange:     true,\n\t}\n\tsupportedRes := make(map[string]bool)\n\n\tfor _, respType := range c.SupportedResponseTypes {\n\t\tswitch respType {\n\t\tcase responseTypeCode, responseTypeIDToken, responseTypeCodeIDToken:\n\t\t\t// continue\n\t\tcase responseTypeToken, responseTypeCodeToken, responseTypeIDTokenToken, responseTypeCodeIDTokenToken:\n\t\t\t// response_type=token is an implicit flow, let's add it to the discovery info\n\t\t\t// https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.1\n\t\t\tallSupportedGrants[grantTypeImplicit] = true\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unsupported response_type %q\", respType)\n\t\t}\n\t\tsupportedRes[respType] = true\n\t}\n\n\tif c.PasswordConnector != \"\" {\n\t\tallSupportedGrants[grantTypePassword] = true\n\t}\n\n\tallSupportedGrants[grantTypeClientCredentials] = true\n\n\tvar supportedGrants []string\n\tif len(c.AllowedGrantTypes) > 0 {\n\t\tfor _, grant := range c.AllowedGrantTypes {\n\t\t\tif allSupportedGrants[grant] {\n\t\t\t\tsupportedGrants = append(supportedGrants, grant)\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor grant := range allSupportedGrants {\n\t\t\tsupportedGrants = append(supportedGrants, grant)\n\t\t}\n\t}\n\tsort.Strings(supportedGrants)\n\n\twebFS := web.FS()\n\tif c.Web.Dir != \"\" {\n\t\twebFS = os.DirFS(c.Web.Dir)\n\t} else if c.Web.WebFS != nil {\n\t\twebFS = c.Web.WebFS\n\t}\n\n\tweb := webConfig{\n\t\twebFS:     webFS,\n\t\tlogoURL:   c.Web.LogoURL,\n\t\tissuerURL: c.Issuer,\n\t\tissuer:    c.Web.Issuer,\n\t\ttheme:     c.Web.Theme,\n\t\textra:     c.Web.Extra,\n\t}\n\n\tstatic, theme, robots, tmpls, err := loadWebConfig(web)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"server: failed to load web static: %v\", err)\n\t}\n\n\tnow := c.Now\n\tif now == nil {\n\t\tnow = time.Now\n\t}\n\n\ts := &Server{\n\t\tissuerURL:              *issuerURL,\n\t\tconnectors:             make(map[string]Connector),\n\t\tstorage:                newKeyCacher(c.Storage, now),\n\t\tsupportedResponseTypes: supportedRes,\n\t\tsupportedGrantTypes:    supportedGrants,\n\t\tpkce:                   c.PKCE,\n\t\tidTokensValidFor:       value(c.IDTokensValidFor, 24*time.Hour),\n\t\tauthRequestsValidFor:   value(c.AuthRequestsValidFor, 24*time.Hour),\n\t\tdeviceRequestsValidFor: value(c.DeviceRequestsValidFor, 5*time.Minute),\n\t\trefreshTokenPolicy:     c.RefreshTokenPolicy,\n\t\tskipApproval:           c.SkipApprovalScreen,\n\t\talwaysShowLogin:        c.AlwaysShowLoginScreen,\n\t\tnow:                    now,\n\t\ttemplates:              tmpls,\n\t\tpasswordConnector:      c.PasswordConnector,\n\t\tlogger:                 c.Logger,\n\t\tsigner:                 c.Signer,\n\t\tsessionConfig:          c.SessionConfig,\n\t\tmfaProviders:           c.MFAProviders,\n\t\tdefaultMFAChain:        c.DefaultMFAChain,\n\t}\n\n\t// Retrieves connector objects in backend storage. This list includes the static connectors\n\t// defined in the ConfigMap and dynamic connectors retrieved from the storage.\n\tstorageConnectors, err := c.Storage.ListConnectors(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"server: failed to list connector objects from storage: %v\", err)\n\t}\n\n\tif len(storageConnectors) == 0 && len(s.connectors) == 0 {\n\t\treturn nil, errors.New(\"server: no connectors specified\")\n\t}\n\n\tvar failedCount int\n\tfor _, conn := range storageConnectors {\n\t\tif _, err := s.OpenConnector(conn); err != nil {\n\t\t\tfailedCount++\n\t\t\tif c.ContinueOnConnectorFailure {\n\t\t\t\ts.logger.Error(\"server: Failed to open connector\", \"id\", conn.ID, \"err\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"server: Failed to open connector %s: %v\", conn.ID, err)\n\t\t}\n\t}\n\n\tif c.ContinueOnConnectorFailure && failedCount == len(storageConnectors) {\n\t\treturn nil, fmt.Errorf(\"server: failed to open all connectors (%d/%d)\", failedCount, len(storageConnectors))\n\t}\n\n\tif featureflags.SessionsEnabled.Enabled() {\n\t\ts.logger.InfoContext(ctx, \"sessions feature flag is enabled\")\n\t}\n\n\tinstrumentHandler := func(_ string, handler http.Handler) http.HandlerFunc {\n\t\treturn handler.ServeHTTP\n\t}\n\n\tif c.PrometheusRegistry != nil {\n\t\trequestCounter := prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: \"http_requests_total\",\n\t\t\tHelp: \"Count of all HTTP requests.\",\n\t\t}, []string{\"code\", \"method\", \"handler\"})\n\n\t\tdurationHist := prometheus.NewHistogramVec(prometheus.HistogramOpts{\n\t\t\tName:    \"request_duration_seconds\",\n\t\t\tHelp:    \"A histogram of latencies for requests.\",\n\t\t\tBuckets: []float64{.25, .5, 1, 2.5, 5, 10},\n\t\t}, []string{\"code\", \"method\", \"handler\"})\n\n\t\tsizeHist := prometheus.NewHistogramVec(prometheus.HistogramOpts{\n\t\t\tName:    \"response_size_bytes\",\n\t\t\tHelp:    \"A histogram of response sizes for requests.\",\n\t\t\tBuckets: []float64{200, 500, 900, 1500},\n\t\t}, []string{\"code\", \"method\", \"handler\"})\n\n\t\tc.PrometheusRegistry.MustRegister(requestCounter, durationHist, sizeHist)\n\n\t\tinstrumentHandler = func(handlerName string, handler http.Handler) http.HandlerFunc {\n\t\t\treturn promhttp.InstrumentHandlerDuration(durationHist.MustCurryWith(prometheus.Labels{\"handler\": handlerName}),\n\t\t\t\tpromhttp.InstrumentHandlerCounter(requestCounter.MustCurryWith(prometheus.Labels{\"handler\": handlerName}),\n\t\t\t\t\tpromhttp.InstrumentHandlerResponseSize(sizeHist.MustCurryWith(prometheus.Labels{\"handler\": handlerName}), handler),\n\t\t\t\t),\n\t\t\t)\n\t\t}\n\t}\n\n\tparseRealIP := func(r *http.Request) (string, error) {\n\t\tremoteAddr, _, err := net.SplitHostPort(r.RemoteAddr)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tremoteIP, err := netip.ParseAddr(remoteAddr)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tfor _, n := range c.TrustedRealIPCIDRs {\n\t\t\tif !n.Contains(remoteIP) {\n\t\t\t\treturn remoteAddr, nil // Fallback to the address from the request if the header is provided\n\t\t\t}\n\t\t}\n\n\t\tipVal := r.Header.Get(c.RealIPHeader)\n\t\tif ipVal != \"\" {\n\t\t\tip, err := netip.ParseAddr(ipVal)\n\t\t\tif err == nil {\n\t\t\t\treturn ip.String(), nil\n\t\t\t}\n\t\t}\n\n\t\treturn remoteAddr, nil\n\t}\n\n\thandlerWithHeaders := func(handlerName string, handler http.Handler) http.HandlerFunc {\n\t\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t\tfor k, v := range c.Headers {\n\t\t\t\tw.Header()[k] = v\n\t\t\t}\n\n\t\t\t// Context values are used for logging purposes with the log/slog logger.\n\t\t\trCtx := r.Context()\n\t\t\trCtx = WithRequestID(rCtx)\n\n\t\t\tif c.RealIPHeader != \"\" {\n\t\t\t\trealIP, err := parseRealIP(r)\n\t\t\t\tif err == nil {\n\t\t\t\t\trCtx = WithRemoteIP(rCtx, realIP)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tr = r.WithContext(rCtx)\n\t\t\tinstrumentHandler(handlerName, handler)(w, r)\n\t\t}\n\t}\n\n\tr := mux.NewRouter().SkipClean(true).UseEncodedPath()\n\thandle := func(p string, h http.Handler) {\n\t\tr.Handle(path.Join(issuerURL.Path, p), handlerWithHeaders(p, h))\n\t}\n\thandleFunc := func(p string, h http.HandlerFunc) {\n\t\thandle(p, h)\n\t}\n\thandlePrefix := func(p string, h http.Handler) {\n\t\tprefix := path.Join(issuerURL.Path, p)\n\t\tr.PathPrefix(prefix).Handler(http.StripPrefix(prefix, h))\n\t}\n\thandleWithCORS := func(p string, h http.HandlerFunc) {\n\t\tvar handler http.Handler = h\n\t\tif len(c.AllowedOrigins) > 0 {\n\t\t\tcors := handlers.CORS(\n\t\t\t\thandlers.AllowedOrigins(c.AllowedOrigins),\n\t\t\t\thandlers.AllowedHeaders(c.AllowedHeaders),\n\t\t\t)\n\t\t\thandler = cors(handler)\n\t\t}\n\t\tr.Handle(path.Join(issuerURL.Path, p), handlerWithHeaders(p, handler))\n\t}\n\tr.NotFoundHandler = http.NotFoundHandler()\n\n\tdiscoveryHandler, err := s.discoveryHandler(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thandleWithCORS(\"/.well-known/openid-configuration\", discoveryHandler)\n\t// Handle the root path for the better user experience.\n\thandleWithCORS(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t_, err := fmt.Fprintf(w, `<!DOCTYPE html>\n\t\t\t<title>Dex</title>\n\t\t\t<h1>Dex IdP</h1>\n\t\t\t<h3>A Federated OpenID Connect Provider</h3>\n\t\t\t<p><a href=%q>Discovery</a></p>`,\n\t\t\ts.issuerURL.JoinPath(\".well-known\", \"openid-configuration\").String())\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"failed to write response\", \"err\", err)\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Handling the / path error.\")\n\t\t\treturn\n\t\t}\n\t})\n\n\t// TODO(ericchiang): rate limit certain paths based on IP.\n\thandleWithCORS(\"/token\", s.handleToken)\n\thandleWithCORS(\"/keys\", s.handlePublicKeys)\n\thandleWithCORS(\"/userinfo\", s.handleUserInfo)\n\thandleWithCORS(\"/token/introspect\", s.handleIntrospect)\n\thandleFunc(\"/auth\", s.handleAuthorization)\n\thandleFunc(\"/auth/{connector}\", s.handleConnectorLogin)\n\thandleFunc(\"/auth/{connector}/login\", s.handlePasswordLogin)\n\thandleFunc(\"/device\", s.handleDeviceExchange)\n\thandleFunc(\"/device/auth/verify_code\", s.verifyUserCode)\n\thandleFunc(\"/device/code\", s.handleDeviceCode)\n\t// TODO(nabokihms): \"/device/token\" endpoint is deprecated, consider using /token endpoint instead\n\thandleFunc(\"/device/token\", s.handleDeviceTokenDeprecated)\n\thandleFunc(deviceCallbackURI, s.handleDeviceCallback)\n\thandleFunc(\"/callback\", func(w http.ResponseWriter, r *http.Request) {\n\t\t// Strip the X-Remote-* headers to prevent security issues on\n\t\t// misconfigured authproxy connector setups.\n\t\tfor key := range r.Header {\n\t\t\tif strings.HasPrefix(strings.ToLower(key), \"x-remote-\") {\n\t\t\t\tr.Header.Del(key)\n\t\t\t}\n\t\t}\n\t\ts.handleConnectorCallback(w, r)\n\t})\n\t// For easier connector-specific web server configuration, e.g. for the\n\t// \"authproxy\" connector.\n\thandleFunc(\"/callback/{connector}\", s.handleConnectorCallback)\n\thandleFunc(\"/approval\", s.handleApproval)\n\thandleFunc(\"/mfa/verify\", s.handleMFAVerify)\n\thandle(\"/healthz\", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif !c.HealthChecker.IsHealthy() {\n\t\t\ts.renderError(r, w, http.StatusInternalServerError, \"Health check failed.\")\n\t\t\treturn\n\t\t}\n\t\tfmt.Fprintf(w, \"Health check passed\")\n\t}))\n\n\thandlePrefix(\"/static\", static)\n\thandlePrefix(\"/theme\", theme)\n\thandleFunc(\"/robots.txt\", robots)\n\n\ts.mux = r\n\n\ts.signer.Start(ctx)\n\ts.startGarbageCollection(ctx, value(c.GCFrequency, 5*time.Minute), now)\n\n\treturn s, nil\n}\n\nfunc (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n\ts.mux.ServeHTTP(w, r)\n}\n\nfunc (s *Server) absPath(pathItems ...string) string {\n\tpaths := make([]string, len(pathItems)+1)\n\tpaths[0] = s.issuerURL.Path\n\tcopy(paths[1:], pathItems)\n\treturn path.Join(paths...)\n}\n\nfunc (s *Server) absURL(pathItems ...string) string {\n\tu := s.issuerURL\n\tu.Path = s.absPath(pathItems...)\n\treturn u.String()\n}\n\nfunc newPasswordDB(s storage.Storage) interface {\n\tconnector.Connector\n\tconnector.PasswordConnector\n} {\n\treturn passwordDB{s}\n}\n\ntype passwordDB struct {\n\ts storage.Storage\n}\n\nfunc resolvePasswordName(p storage.Password) string {\n\tif p.Name != \"\" {\n\t\treturn p.Name\n\t}\n\treturn p.Username\n}\n\nfunc resolvePasswordEmailVerified(p storage.Password) bool {\n\tif p.EmailVerified != nil {\n\t\treturn *p.EmailVerified\n\t}\n\treturn true\n}\n\nfunc (db passwordDB) Login(ctx context.Context, s connector.Scopes, email, password string) (connector.Identity, bool, error) {\n\tp, err := db.s.GetPassword(ctx, email)\n\tif err != nil {\n\t\tif err != storage.ErrNotFound {\n\t\t\treturn connector.Identity{}, false, fmt.Errorf(\"get password: %v\", err)\n\t\t}\n\t\treturn connector.Identity{}, false, nil\n\t}\n\t// This check prevents dex users from logging in using static passwords\n\t// configured with hash costs that are too high or low.\n\tif err := checkCost(p.Hash); err != nil {\n\t\treturn connector.Identity{}, false, err\n\t}\n\tif err := bcrypt.CompareHashAndPassword(p.Hash, []byte(password)); err != nil {\n\t\treturn connector.Identity{}, false, nil\n\t}\n\treturn connector.Identity{\n\t\tUserID:            p.UserID,\n\t\tUsername:          resolvePasswordName(p),\n\t\tPreferredUsername: p.PreferredUsername,\n\t\tEmail:             p.Email,\n\t\tEmailVerified:     resolvePasswordEmailVerified(p),\n\t\tGroups:            p.Groups,\n\t}, true, nil\n}\n\nfunc (db passwordDB) Refresh(ctx context.Context, s connector.Scopes, identity connector.Identity) (connector.Identity, error) {\n\t// If the user has been deleted, the refresh token will be rejected.\n\tp, err := db.s.GetPassword(ctx, identity.Email)\n\tif err != nil {\n\t\tif err == storage.ErrNotFound {\n\t\t\treturn connector.Identity{}, errors.New(\"user not found\")\n\t\t}\n\t\treturn connector.Identity{}, fmt.Errorf(\"get password: %v\", err)\n\t}\n\n\t// User removed but a new user with the same email exists.\n\tif p.UserID != identity.UserID {\n\t\treturn connector.Identity{}, errors.New(\"user not found\")\n\t}\n\n\t// If a user has updated their username, that will be reflected in the\n\t// refreshed token.\n\t//\n\t// No other fields are expected to be refreshable as email is effectively used\n\t// as an ID.\n\tidentity.Username = resolvePasswordName(p)\n\tidentity.PreferredUsername = p.PreferredUsername\n\tidentity.EmailVerified = resolvePasswordEmailVerified(p)\n\tidentity.Groups = p.Groups\n\n\treturn identity, nil\n}\n\nfunc (db passwordDB) Prompt() string {\n\treturn \"Email Address\"\n}\n\n// newKeyCacher returns a storage which caches keys so long as the next\nfunc newKeyCacher(s storage.Storage, now func() time.Time) storage.Storage {\n\tif now == nil {\n\t\tnow = time.Now\n\t}\n\treturn &keyCacher{Storage: s, now: now}\n}\n\ntype keyCacher struct {\n\tstorage.Storage\n\n\tnow  func() time.Time\n\tkeys atomic.Value // Always holds nil or type *storage.Keys.\n}\n\nfunc (k *keyCacher) GetKeys(ctx context.Context) (storage.Keys, error) {\n\tkeys, ok := k.keys.Load().(*storage.Keys)\n\tif ok && keys != nil && k.now().Before(keys.NextRotation) {\n\t\treturn *keys, nil\n\t}\n\n\tstorageKeys, err := k.Storage.GetKeys(ctx)\n\tif err != nil {\n\t\treturn storageKeys, err\n\t}\n\n\tif k.now().Before(storageKeys.NextRotation) {\n\t\tk.keys.Store(&storageKeys)\n\t}\n\treturn storageKeys, nil\n}\n\nfunc (s *Server) startGarbageCollection(ctx context.Context, frequency time.Duration, now func() time.Time) {\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-time.After(frequency):\n\t\t\t\tif r, err := s.storage.GarbageCollect(ctx, now()); err != nil {\n\t\t\t\t\ts.logger.ErrorContext(ctx, \"garbage collection failed\", \"err\", err)\n\t\t\t\t} else if !r.IsEmpty() {\n\t\t\t\t\ts.logger.InfoContext(ctx, \"garbage collection run, delete auth\",\n\t\t\t\t\t\t\"requests\", r.AuthRequests, \"auth_codes\", r.AuthCodes,\n\t\t\t\t\t\t\"device_requests\", r.DeviceRequests, \"device_tokens\", r.DeviceTokens,\n\t\t\t\t\t\t\"auth_sessions\", r.AuthSessions)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n}\n\n// ConnectorConfig is a configuration that can open a connector.\ntype ConnectorConfig interface {\n\tOpen(id string, logger *slog.Logger) (connector.Connector, error)\n}\n\n// ConnectorsConfig variable provides an easy way to return a config struct\n// depending on the connector type.\nvar ConnectorsConfig = map[string]func() ConnectorConfig{\n\t\"keystone\":        func() ConnectorConfig { return new(keystone.Config) },\n\t\"mockCallback\":    func() ConnectorConfig { return new(mock.CallbackConfig) },\n\t\"mockPassword\":    func() ConnectorConfig { return new(mock.PasswordConfig) },\n\t\"ldap\":            func() ConnectorConfig { return new(ldap.Config) },\n\t\"gitea\":           func() ConnectorConfig { return new(gitea.Config) },\n\t\"github\":          func() ConnectorConfig { return new(github.Config) },\n\t\"gitlab\":          func() ConnectorConfig { return new(gitlab.Config) },\n\t\"google\":          func() ConnectorConfig { return new(google.Config) },\n\t\"oidc\":            func() ConnectorConfig { return new(oidc.Config) },\n\t\"oauth\":           func() ConnectorConfig { return new(oauth.Config) },\n\t\"saml\":            func() ConnectorConfig { return new(saml.Config) },\n\t\"authproxy\":       func() ConnectorConfig { return new(authproxy.Config) },\n\t\"linkedin\":        func() ConnectorConfig { return new(linkedin.Config) },\n\t\"microsoft\":       func() ConnectorConfig { return new(microsoft.Config) },\n\t\"bitbucket-cloud\": func() ConnectorConfig { return new(bitbucketcloud.Config) },\n\t\"openshift\":       func() ConnectorConfig { return new(openshift.Config) },\n\t\"atlassian-crowd\": func() ConnectorConfig { return new(atlassiancrowd.Config) },\n\t// Keep around for backwards compatibility.\n\t\"samlExperimental\": func() ConnectorConfig { return new(saml.Config) },\n}\n\n// openConnector will parse the connector config and open the connector.\nfunc openConnector(logger *slog.Logger, conn storage.Connector) (connector.Connector, error) {\n\tvar c connector.Connector\n\n\tf, ok := ConnectorsConfig[conn.Type]\n\tif !ok {\n\t\treturn c, fmt.Errorf(\"unknown connector type %q\", conn.Type)\n\t}\n\n\tconnConfig := f()\n\tif len(conn.Config) != 0 {\n\t\tdata := []byte(string(conn.Config))\n\t\tif err := json.Unmarshal(data, connConfig); err != nil {\n\t\t\treturn c, fmt.Errorf(\"parse connector config: %v\", err)\n\t\t}\n\t}\n\n\tc, err := connConfig.Open(conn.ID, logger)\n\tif err != nil {\n\t\treturn c, fmt.Errorf(\"failed to create connector %s: %v\", conn.ID, err)\n\t}\n\n\treturn c, nil\n}\n\n// OpenConnector updates server connector map with specified connector object.\nfunc (s *Server) OpenConnector(conn storage.Connector) (Connector, error) {\n\tvar c connector.Connector\n\n\tif conn.Type == LocalConnector {\n\t\tc = newPasswordDB(s.storage)\n\t} else {\n\t\tvar err error\n\t\tc, err = openConnector(s.logger, conn)\n\t\tif err != nil {\n\t\t\treturn Connector{}, fmt.Errorf(\"failed to open connector: %v\", err)\n\t\t}\n\t}\n\n\tconnector := Connector{\n\t\tType:            conn.Type,\n\t\tResourceVersion: conn.ResourceVersion,\n\t\tConnector:       c,\n\t\tGrantTypes:      conn.GrantTypes,\n\t}\n\ts.mu.Lock()\n\ts.connectors[conn.ID] = connector\n\ts.mu.Unlock()\n\n\treturn connector, nil\n}\n\n// CloseConnector removes the connector from the server's in-memory map.\nfunc (s *Server) CloseConnector(id string) {\n\ts.mu.Lock()\n\tdelete(s.connectors, id)\n\ts.mu.Unlock()\n}\n\n// getConnector retrieves the connector object with the given id from the storage\n// and updates the connector list for server if necessary.\nfunc (s *Server) getConnector(ctx context.Context, id string) (Connector, error) {\n\tstorageConnector, err := s.storage.GetConnector(ctx, id)\n\tif err != nil {\n\t\treturn Connector{}, fmt.Errorf(\"failed to get connector object from storage: %v\", err)\n\t}\n\n\tvar conn Connector\n\tvar ok bool\n\ts.mu.Lock()\n\tconn, ok = s.connectors[id]\n\ts.mu.Unlock()\n\n\tif !ok || storageConnector.ResourceVersion != conn.ResourceVersion {\n\t\t// Connector object does not exist in server connectors map or\n\t\t// has been updated in the storage. Need to get latest.\n\t\tconn, err := s.OpenConnector(storageConnector)\n\t\tif err != nil {\n\t\t\treturn Connector{}, fmt.Errorf(\"failed to open connector: %v\", err)\n\t\t}\n\t\treturn conn, nil\n\t}\n\n\treturn conn, nil\n}\n\ntype logRequestKey string\n\nconst (\n\tRequestKeyRequestID logRequestKey = \"request_id\"\n\tRequestKeyRemoteIP  logRequestKey = \"client_remote_addr\"\n)\n\nfunc WithRequestID(ctx context.Context) context.Context {\n\treturn context.WithValue(ctx, RequestKeyRequestID, uuid.NewString())\n}\n\nfunc WithRemoteIP(ctx context.Context, ip string) context.Context {\n\treturn context.WithValue(ctx, RequestKeyRemoteIP, ip)\n}\n"
  },
  {
    "path": "server/server_test.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"path\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tgosundheit \"github.com/AppsFlyer/go-sundheit\"\n\t\"github.com/coreos/go-oidc/v3/oidc\"\n\t\"github.com/go-jose/go-jose/v4\"\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/bcrypt\"\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/dexidp/dex/connector\"\n\t\"github.com/dexidp/dex/connector/mock\"\n\t\"github.com/dexidp/dex/server/signer\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/memory\"\n)\n\nfunc mustLoad(s string) *rsa.PrivateKey {\n\tblock, _ := pem.Decode([]byte(s))\n\tif block == nil {\n\t\tpanic(\"no pem data found\")\n\t}\n\tkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn key\n}\n\nvar testKey = mustLoad(`-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEArmoiX5G36MKPiVGS1sicruEaGRrbhPbIKOf97aGGQRjXVngo\nKnwd2L4T9CRyABgQm3tLHHcT5crODoy46wX2g9onTZWViWWuhJ5wxXNmUbCAPWHb\nj9SunW53WuLYZ/IJLNZt5XYCAFPjAakWp8uMuuDwWo5EyFaw85X3FSMhVmmaYDd0\ncn+1H4+NS/52wX7tWmyvGUNJ8lzjFAnnOtBJByvkyIC7HDphkLQV4j//sMNY1mPX\nHbsYgFv2J/LIJtkjdYO2UoDhZG3Gvj16fMy2JE2owA8IX4/s+XAmA2PiTfd0J5b4\ndrAKEcdDl83G6L3depEkTkfvp0ZLsh9xupAvIwIDAQABAoIBABKGgWonPyKA7+AF\nAxS/MC0/CZebC6/+ylnV8lm4K1tkuRKdJp8EmeL4pYPsDxPFepYZLWwzlbB1rxdK\niSWld36fwEb0WXLDkxrQ/Wdrj3Wjyqs6ZqjLTVS5dAH6UEQSKDlT+U5DD4lbX6RA\ngoCGFUeQNtdXfyTMWHU2+4yKM7NKzUpczFky+0d10Mg0ANj3/4IILdr3hqkmMSI9\n1TB9ksWBXJxt3nGxAjzSFihQFUlc231cey/HhYbvAX5fN0xhLxOk88adDcdXE7br\n3Ser1q6XaaFQSMj4oi1+h3RAT9MUjJ6johEqjw0PbEZtOqXvA1x5vfFdei6SqgKn\nAm3BspkCgYEA2lIiKEkT/Je6ZH4Omhv9atbGoBdETAstL3FnNQjkyVau9f6bxQkl\n4/sz985JpaiasORQBiTGY8JDT/hXjROkut91agi2Vafhr29L/mto7KZglfDsT4b2\n9z/EZH8wHw7eYhvdoBbMbqNDSI8RrGa4mpLpuN+E0wsFTzSZEL+QMQUCgYEAzIQh\nxnreQvDAhNradMqLmxRpayn1ORaPReD4/off+mi7hZRLKtP0iNgEVEWHJ6HEqqi1\nr38XAc8ap/lfOVMar2MLyCFOhYspdHZ+TGLZfr8gg/Fzeq9IRGKYadmIKVwjMeyH\nREPqg1tyrvMOE0HI5oqkko8JTDJ0OyVC0Vc6+AcCgYAqCzkywugLc/jcU35iZVOH\nWLdFq1Vmw5w/D7rNdtoAgCYPj6nV5y4Z2o2mgl6ifXbU7BMRK9Hc8lNeOjg6HfdS\nWahV9DmRA1SuIWPkKjE5qczd81i+9AHpmakrpWbSBF4FTNKAewOBpwVVGuBPcDTK\n59IE3V7J+cxa9YkotYuCNQKBgCwGla7AbHBEm2z+H+DcaUktD7R+B8gOTzFfyLoi\nTdj+CsAquDO0BQQgXG43uWySql+CifoJhc5h4v8d853HggsXa0XdxaWB256yk2Wm\nMePTCRDePVm/ufLetqiyp1kf+IOaw1Oyux0j5oA62mDS3Iikd+EE4Z+BjPvefY/L\nE2qpAoGAZo5Wwwk7q8b1n9n/ACh4LpE+QgbFdlJxlfFLJCKstl37atzS8UewOSZj\nFDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ\nNp4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=\n-----END RSA PRIVATE KEY-----`)\n\nfunc newTestServer(t *testing.T, updateConfig func(c *Config)) (*httptest.Server, *Server) {\n\tvar server *Server\n\ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tserver.ServeHTTP(w, r)\n\t}))\n\n\tlogger := newLogger(t)\n\tctx := t.Context()\n\n\tsig, err := signer.NewMockSigner(testKey)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create mock signer: %v\", err)\n\t}\n\n\tconfig := Config{\n\t\tIssuer:  s.URL,\n\t\tStorage: memory.New(logger),\n\t\tWeb: WebConfig{\n\t\t\tDir: \"../web\",\n\t\t},\n\t\tLogger:             logger,\n\t\tPrometheusRegistry: prometheus.NewRegistry(),\n\t\tHealthChecker:      gosundheit.New(),\n\t\tSkipApprovalScreen: true, // Don't prompt for approval, just immediately redirect with code.\n\t\tAllowedGrantTypes: []string{ // all implemented types\n\t\t\tgrantTypeDeviceCode,\n\t\t\tgrantTypeAuthorizationCode,\n\t\t\tgrantTypeClientCredentials,\n\t\t\tgrantTypeRefreshToken,\n\t\t\tgrantTypeTokenExchange,\n\t\t\tgrantTypeImplicit,\n\t\t\tgrantTypePassword,\n\t\t},\n\t\tSigner: sig,\n\t}\n\tif updateConfig != nil {\n\t\tupdateConfig(&config)\n\t}\n\ts.URL = config.Issuer\n\n\tconnector := storage.Connector{\n\t\tID:              \"mock\",\n\t\tType:            \"mockCallback\",\n\t\tName:            \"Mock\",\n\t\tResourceVersion: \"1\",\n\t}\n\tif err := config.Storage.CreateConnector(ctx, connector); err != nil {\n\t\tt.Fatalf(\"create connector: %v\", err)\n\t}\n\n\tif server, err = newServer(ctx, config); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\t// Default rotation policy\n\tif server.refreshTokenPolicy == nil {\n\t\tserver.refreshTokenPolicy, err = NewRefreshTokenPolicy(logger, false, \"\", \"\", \"\")\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"failed to prepare rotation policy: %v\", err)\n\t\t}\n\t\tserver.refreshTokenPolicy.now = config.Now\n\t}\n\n\treturn s, server\n}\n\nfunc newTestServerMultipleConnectors(t *testing.T, updateConfig func(c *Config)) (*httptest.Server, *Server) {\n\tvar server *Server\n\ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tserver.ServeHTTP(w, r)\n\t}))\n\n\tlogger := newLogger(t)\n\tctx := t.Context()\n\n\tsig, err := signer.NewMockSigner(testKey)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to create mock signer: %v\", err)\n\t}\n\n\tconfig := Config{\n\t\tIssuer:  s.URL,\n\t\tStorage: memory.New(logger),\n\t\tWeb: WebConfig{\n\t\t\tDir: \"../web\",\n\t\t},\n\t\tLogger:             logger,\n\t\tPrometheusRegistry: prometheus.NewRegistry(),\n\t\tSigner:             sig,\n\t}\n\tif updateConfig != nil {\n\t\tupdateConfig(&config)\n\t}\n\ts.URL = config.Issuer\n\n\tconnector := storage.Connector{\n\t\tID:              \"mock\",\n\t\tType:            \"mockCallback\",\n\t\tName:            \"Mock\",\n\t\tResourceVersion: \"1\",\n\t}\n\tconnector2 := storage.Connector{\n\t\tID:              \"mock2\",\n\t\tType:            \"mockCallback\",\n\t\tName:            \"Mock\",\n\t\tResourceVersion: \"1\",\n\t}\n\tif err := config.Storage.CreateConnector(ctx, connector); err != nil {\n\t\tt.Fatalf(\"create connector: %v\", err)\n\t}\n\tif err := config.Storage.CreateConnector(ctx, connector2); err != nil {\n\t\tt.Fatalf(\"create connector: %v\", err)\n\t}\n\n\tif server, err = newServer(ctx, config); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tserver.skipApproval = true // Don't prompt for approval, just immediately redirect with code.\n\treturn s, server\n}\n\nfunc TestNewTestServer(t *testing.T) {\n\tnewTestServer(t, nil)\n}\n\nfunc TestDiscovery(t *testing.T) {\n\thttpServer, _ := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t})\n\tdefer httpServer.Close()\n\n\tp, err := oidc.NewProvider(t.Context(), httpServer.URL)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t}\n\n\tvar got map[string]*json.RawMessage\n\tif err := p.Claims(&got); err != nil {\n\t\tt.Fatalf(\"failed to decode claims: %v\", err)\n\t}\n\n\trequired := []string{\n\t\t\"issuer\",\n\t\t\"authorization_endpoint\",\n\t\t\"token_endpoint\",\n\t\t\"jwks_uri\",\n\t\t\"userinfo_endpoint\",\n\t}\n\tfor _, field := range required {\n\t\tif _, ok := got[field]; !ok {\n\t\t\tt.Errorf(\"server discovery is missing required field %q\", field)\n\t\t}\n\t}\n}\n\ntype oauth2Tests struct {\n\tclientID string\n\ttests    []test\n}\n\ntype test struct {\n\tname string\n\t// If specified these set of scopes will be used during the test case.\n\tscopes []string\n\t// handleToken provides the OAuth2 token response for the integration test.\n\thandleToken func(context.Context, *oidc.Provider, *oauth2.Config, *oauth2.Token, *mock.Callback) error\n\n\t// extra parameters to pass when requesting auth_code\n\tauthCodeOptions []oauth2.AuthCodeOption\n\n\t// extra parameters to pass when retrieving id token\n\tretrieveTokenOptions []oauth2.AuthCodeOption\n\n\t// define an error response, when the test expects an error on the auth endpoint\n\tauthError *OAuth2ErrorResponse\n\n\t// define an error response, when the test expects an error on the token endpoint\n\ttokenError ErrorResponse\n}\n\n// Defines an expected error by HTTP Status Code and\n// the OAuth2 error int the response json\ntype ErrorResponse struct {\n\tError      string\n\tStatusCode int\n}\n\n// https://tools.ietf.org/html/rfc6749#section-5.2\ntype OAuth2ErrorResponse struct {\n\tError            string `json:\"error\"`\n\tErrorDescription string `json:\"error_description\"`\n\tErrorURI         string `json:\"error_uri\"`\n}\n\nfunc makeOAuth2Tests(clientID string, clientSecret string, now func() time.Time) oauth2Tests {\n\trequestedScopes := []string{oidc.ScopeOpenID, \"email\", \"profile\", \"groups\", \"offline_access\"}\n\n\t// Used later when configuring test servers to set how long id_tokens will be valid for.\n\t//\n\t// The actual value of 30s is completely arbitrary. We just need to set a value\n\t// so tests can compute the expected \"expires_in\" field.\n\tidTokensValidFor := time.Second * 30\n\n\toidcConfig := &oidc.Config{SkipClientIDCheck: true}\n\n\tbasicIDTokenVerify := func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\tidToken, ok := token.Extra(\"id_token\").(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"no id token found\")\n\t\t}\n\t\tif _, err := p.Verifier(oidcConfig).Verify(ctx, idToken); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to verify id token: %v\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn oauth2Tests{\n\t\tclientID: clientID,\n\t\ttests: []test{\n\t\t\t{\n\t\t\t\tname: \"verify ID Token\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\tidToken, ok := token.Extra(\"id_token\").(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn fmt.Errorf(\"no id token found\")\n\t\t\t\t\t}\n\t\t\t\t\tif _, err := p.Verifier(oidcConfig).Verify(ctx, idToken); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to verify id token: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"fetch userinfo\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\tui, err := p.UserInfo(ctx, config.TokenSource(ctx, token))\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to fetch userinfo: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif conn.Identity.Email != ui.Email {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected email to be %v, got %v\", conn.Identity.Email, ui.Email)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"verify id token and oauth2 token expiry\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\texpectedExpiry := now().Add(idTokensValidFor)\n\n\t\t\t\t\ttimeEq := func(t1, t2 time.Time, within time.Duration) bool {\n\t\t\t\t\t\treturn t1.Sub(t2) < within\n\t\t\t\t\t}\n\n\t\t\t\t\t// TODO: This is a flaky test. We need something better (eg. clockwork).\n\t\t\t\t\tif !timeEq(token.Expiry, expectedExpiry, 2*time.Second) {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected expired_in to be %s, got %s\", expectedExpiry, token.Expiry)\n\t\t\t\t\t}\n\n\t\t\t\t\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn fmt.Errorf(\"no id token found\")\n\t\t\t\t\t}\n\t\t\t\t\tidToken, err := p.Verifier(oidcConfig).Verify(ctx, rawIDToken)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to verify id token: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif !timeEq(idToken.Expiry, expectedExpiry, time.Second) {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected id token expiry to be %s, got %s\", expectedExpiry, token.Expiry)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"verify at_hash\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn fmt.Errorf(\"no id token found\")\n\t\t\t\t\t}\n\t\t\t\t\tidToken, err := p.Verifier(oidcConfig).Verify(ctx, rawIDToken)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to verify id token: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tvar claims struct {\n\t\t\t\t\t\tAtHash string `json:\"at_hash\"`\n\t\t\t\t\t}\n\t\t\t\t\tif err := idToken.Claims(&claims); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to decode raw claims: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif claims.AtHash == \"\" {\n\t\t\t\t\t\treturn errors.New(\"no at_hash value in id_token\")\n\t\t\t\t\t}\n\t\t\t\t\twantAtHash, err := accessTokenHash(jose.RS256, token.AccessToken)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"computed expected at hash: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif wantAtHash != claims.AtHash {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected at_hash=%q got=%q\", wantAtHash, claims.AtHash)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"refresh token\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\t// have to use time.Now because the OAuth2 package uses it.\n\t\t\t\t\ttoken.Expiry = time.Now().Add(time.Second * -10)\n\t\t\t\t\tif token.Valid() {\n\t\t\t\t\t\treturn errors.New(\"token shouldn't be valid\")\n\t\t\t\t\t}\n\n\t\t\t\t\tnewToken, err := config.TokenSource(ctx, token).Token()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to refresh token: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif token.RefreshToken == newToken.RefreshToken {\n\t\t\t\t\t\treturn fmt.Errorf(\"old refresh token was the same as the new token %q\", token.RefreshToken)\n\t\t\t\t\t}\n\n\t\t\t\t\tif _, err := config.TokenSource(ctx, token).Token(); err == nil {\n\t\t\t\t\t\treturn errors.New(\"was able to redeem the same refresh token twice\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"refresh with explicit scopes\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\tv := url.Values{}\n\t\t\t\t\tv.Add(\"client_id\", clientID)\n\t\t\t\t\tv.Add(\"client_secret\", clientSecret)\n\t\t\t\t\tv.Add(\"grant_type\", \"refresh_token\")\n\t\t\t\t\tv.Add(\"refresh_token\", token.RefreshToken)\n\t\t\t\t\tv.Add(\"scope\", strings.Join(requestedScopes, \" \"))\n\t\t\t\t\tresp, err := http.PostForm(p.Endpoint().TokenURL, v)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\t\t\tdump, err := httputil.DumpResponse(resp, true)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn fmt.Errorf(\"unexpected response: %s\", dump)\n\t\t\t\t\t}\n\t\t\t\t\tif resp.Header.Get(\"Cache-Control\") != \"no-store\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"cache-control header doesn't included in token response\")\n\t\t\t\t\t}\n\t\t\t\t\tif resp.Header.Get(\"Pragma\") != \"no-cache\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"pragma header doesn't included in token response\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"refresh with extra spaces\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\tv := url.Values{}\n\t\t\t\t\tv.Add(\"client_id\", clientID)\n\t\t\t\t\tv.Add(\"client_secret\", clientSecret)\n\t\t\t\t\tv.Add(\"grant_type\", \"refresh_token\")\n\t\t\t\t\tv.Add(\"refresh_token\", token.RefreshToken)\n\n\t\t\t\t\t// go-oidc adds an additional space before scopes when refreshing.\n\t\t\t\t\t// Since we support that client we choose to be more relaxed about\n\t\t\t\t\t// scope parsing, disregarding extra whitespace.\n\t\t\t\t\tv.Add(\"scope\", \" \"+strings.Join(requestedScopes, \" \"))\n\t\t\t\t\tresp, err := http.PostForm(p.Endpoint().TokenURL, v)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\t\t\tdump, err := httputil.DumpResponse(resp, true)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn fmt.Errorf(\"unexpected response: %s\", dump)\n\t\t\t\t\t}\n\t\t\t\t\tif resp.Header.Get(\"Cache-Control\") != \"no-store\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"cache-control header doesn't included in token response\")\n\t\t\t\t\t}\n\t\t\t\t\tif resp.Header.Get(\"Pragma\") != \"no-cache\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"pragma header doesn't included in token response\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"refresh with unauthorized scopes\",\n\t\t\t\tscopes: []string{\"openid\", \"email\"},\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\tv := url.Values{}\n\t\t\t\t\tv.Add(\"client_id\", clientID)\n\t\t\t\t\tv.Add(\"client_secret\", clientSecret)\n\t\t\t\t\tv.Add(\"grant_type\", \"refresh_token\")\n\t\t\t\t\tv.Add(\"refresh_token\", token.RefreshToken)\n\t\t\t\t\t// Request a scope that wasn't requested initially.\n\t\t\t\t\tv.Add(\"scope\", \"oidc email profile\")\n\t\t\t\t\tresp, err := http.PostForm(p.Endpoint().TokenURL, v)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\tif resp.StatusCode == http.StatusOK {\n\t\t\t\t\t\tdump, err := httputil.DumpResponse(resp, true)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tpanic(err)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn fmt.Errorf(\"unexpected response: %s\", dump)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname:   \"refresh with different client id\",\n\t\t\t\tscopes: []string{\"openid\", \"email\"},\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\tv := url.Values{}\n\t\t\t\t\tv.Add(\"client_id\", clientID)\n\t\t\t\t\tv.Add(\"client_secret\", clientSecret)\n\t\t\t\t\tv.Add(\"grant_type\", \"refresh_token\")\n\t\t\t\t\tv.Add(\"refresh_token\", \"existedrefrestoken\")\n\t\t\t\t\tv.Add(\"scope\", \"oidc email\")\n\t\t\t\t\tresp, err := http.PostForm(p.Endpoint().TokenURL, v)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\n\t\t\t\t\tdefer resp.Body.Close()\n\t\t\t\t\tif resp.StatusCode != http.StatusBadRequest {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected status code %d, got %d\", http.StatusBadRequest, resp.StatusCode)\n\t\t\t\t\t}\n\n\t\t\t\t\tvar respErr struct {\n\t\t\t\t\t\tError       string `json:\"error\"`\n\t\t\t\t\t\tDescription string `json:\"error_description\"`\n\t\t\t\t\t}\n\n\t\t\t\t\tif err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"cannot decode token response: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif respErr.Error != errInvalidGrant {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected error %q, got %q\", errInvalidGrant, respErr.Error)\n\t\t\t\t\t}\n\n\t\t\t\t\texpectedMsg := \"Refresh token is invalid or has already been claimed by another client.\"\n\t\t\t\t\tif respErr.Description != expectedMsg {\n\t\t\t\t\t\treturn fmt.Errorf(\"expected error description %q, got %q\", expectedMsg, respErr.Description)\n\t\t\t\t\t}\n\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t// This test ensures that the connector.RefreshConnector interface is being\n\t\t\t\t// used when clients request a refresh token.\n\t\t\t\tname: \"refresh with identity changes\",\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\t// have to use time.Now because the OAuth2 package uses it.\n\t\t\t\t\ttoken.Expiry = time.Now().Add(time.Second * -10)\n\t\t\t\t\tif token.Valid() {\n\t\t\t\t\t\treturn errors.New(\"token shouldn't be valid\")\n\t\t\t\t\t}\n\n\t\t\t\t\tident := connector.Identity{\n\t\t\t\t\t\tUserID:        \"fooid\",\n\t\t\t\t\t\tUsername:      \"foo\",\n\t\t\t\t\t\tEmail:         \"foo@bar.com\",\n\t\t\t\t\t\tEmailVerified: true,\n\t\t\t\t\t\tGroups:        []string{\"foo\", \"bar\"},\n\t\t\t\t\t}\n\t\t\t\t\tconn.Identity = ident\n\n\t\t\t\t\ttype claims struct {\n\t\t\t\t\t\tUsername      string   `json:\"name\"`\n\t\t\t\t\t\tEmail         string   `json:\"email\"`\n\t\t\t\t\t\tEmailVerified bool     `json:\"email_verified\"`\n\t\t\t\t\t\tGroups        []string `json:\"groups\"`\n\t\t\t\t\t}\n\t\t\t\t\twant := claims{ident.Username, ident.Email, ident.EmailVerified, ident.Groups}\n\n\t\t\t\t\tnewToken, err := config.TokenSource(ctx, token).Token()\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to refresh token: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\trawIDToken, ok := newToken.Extra(\"id_token\").(string)\n\t\t\t\t\tif !ok {\n\t\t\t\t\t\treturn fmt.Errorf(\"no id_token in refreshed token\")\n\t\t\t\t\t}\n\t\t\t\t\tidToken, err := p.Verifier(oidcConfig).Verify(ctx, rawIDToken)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to verify id token: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tvar got claims\n\t\t\t\t\tif err := idToken.Claims(&got); err != nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"failed to unmarshal claims: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif diff := pretty.Compare(want, got); diff != \"\" {\n\t\t\t\t\t\treturn fmt.Errorf(\"got identity != want identity: %s\", diff)\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"unsupported grant type\",\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"grant_type\", \"unsupported\"),\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t\ttokenError: ErrorResponse{\n\t\t\t\t\tError:      errUnsupportedGrantType,\n\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t// This test ensures that PKCE work in \"plain\" mode (no code_challenge_method specified)\n\t\t\t\tname: \"PKCE with plain\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge\", \"challenge123\"),\n\t\t\t\t},\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_verifier\", \"challenge123\"),\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t},\n\t\t\t{\n\t\t\t\t// This test ensures that PKCE works in \"S256\" mode\n\t\t\t\tname: \"PKCE with S256\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge\", \"lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w\"),\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge_method\", \"S256\"),\n\t\t\t\t},\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_verifier\", \"challenge123\"),\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t},\n\t\t\t{\n\t\t\t\t// This test ensures that PKCE does fail with wrong code_verifier in \"plain\" mode\n\t\t\t\tname: \"PKCE with plain and wrong code_verifier\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge\", \"challenge123\"),\n\t\t\t\t},\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_verifier\", \"challenge124\"),\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t\ttokenError: ErrorResponse{\n\t\t\t\t\tError:      errInvalidGrant,\n\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t// This test ensures that PKCE fail with wrong code_verifier in \"S256\" mode\n\t\t\t\tname: \"PKCE with S256 and wrong code_verifier\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge\", \"lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w\"),\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge_method\", \"S256\"),\n\t\t\t\t},\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_verifier\", \"challenge124\"),\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t\ttokenError: ErrorResponse{\n\t\t\t\t\tError:      errInvalidGrant,\n\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t// Ensure that, when PKCE flow started on /auth\n\t\t\t\t// we stay in PKCE flow on /token\n\t\t\t\tname: \"PKCE flow expected on /token\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge\", \"lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w\"),\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge_method\", \"S256\"),\n\t\t\t\t},\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\t// No PKCE call on /token\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t\ttokenError: ErrorResponse{\n\t\t\t\t\tError:      errInvalidGrant,\n\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t// Ensure that when no PKCE flow was started on /auth\n\t\t\t\t// we cannot switch to PKCE on /token\n\t\t\t\tname:            \"No PKCE flow started on /auth\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\t// No PKCE call on /auth\n\t\t\t\t},\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_verifier\", \"challenge123\"),\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t\ttokenError: ErrorResponse{\n\t\t\t\t\tError:      errInvalidRequest,\n\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\t// Make sure that, when we start with \"S256\" on /auth, we cannot downgrade to \"plain\" on /token\n\t\t\t\tname: \"PKCE with S256 and try to downgrade to plain\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge\", \"lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w\"),\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge_method\", \"S256\"),\n\t\t\t\t},\n\t\t\t\tretrieveTokenOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_verifier\", \"lyyl-X4a69qrqgEfUL8wodWic3Be9ZZ5eovBgIKKi-w\"),\n\t\t\t\t\toauth2.SetAuthURLParam(\"code_challenge_method\", \"plain\"),\n\t\t\t\t},\n\t\t\t\thandleToken: basicIDTokenVerify,\n\t\t\t\ttokenError: ErrorResponse{\n\t\t\t\t\tError:      errInvalidGrant,\n\t\t\t\t\tStatusCode: http.StatusBadRequest,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: \"Request parameter in authorization query\",\n\t\t\t\tauthCodeOptions: []oauth2.AuthCodeOption{\n\t\t\t\t\toauth2.SetAuthURLParam(\"request\", \"anything\"),\n\t\t\t\t},\n\t\t\t\tauthError: &OAuth2ErrorResponse{\n\t\t\t\t\tError:            errRequestNotSupported,\n\t\t\t\t\tErrorDescription: \"Server does not support request parameter.\",\n\t\t\t\t},\n\t\t\t\thandleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\n// TestOAuth2CodeFlow runs integration tests against a test server. The tests stand up a server\n// which requires no interaction to login, logs in through a test client, then passes the client\n// and returned token to the test.\nfunc TestOAuth2CodeFlow(t *testing.T) {\n\tclientID := \"testclient\"\n\tclientSecret := \"testclientsecret\"\n\trequestedScopes := []string{oidc.ScopeOpenID, \"email\", \"profile\", \"groups\", \"offline_access\"}\n\n\tt0 := time.Now()\n\n\t// Always have the time function used by the server return the same time so\n\t// we can predict expected values of \"expires_in\" fields exactly.\n\tnow := func() time.Time { return t0 }\n\n\t// Used later when configuring test servers to set how long id_tokens will be valid for.\n\t//\n\t// The actual value of 30s is completely arbitrary. We just need to set a value\n\t// so tests can compute the expected \"expires_in\" field.\n\tidTokensValidFor := time.Second * 30\n\n\t// Connector used by the tests.\n\tvar conn *mock.Callback\n\n\ttests := makeOAuth2Tests(clientID, clientSecret, now)\n\tfor _, tc := range tests.tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tctx := t.Context()\n\n\t\t\t// Setup a dex server.\n\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\tc.Issuer += \"/non-root-path\"\n\t\t\t\tc.Now = now\n\t\t\t\tc.IDTokensValidFor = idTokensValidFor\n\t\t\t})\n\t\t\tdefer httpServer.Close()\n\n\t\t\tmockConn := s.connectors[\"mock\"]\n\t\t\tconn = mockConn.Connector.(*mock.Callback)\n\n\t\t\t// Query server's provider metadata.\n\t\t\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t\t\t}\n\n\t\t\tvar (\n\t\t\t\t// If the OAuth2 client didn't get a response, we need\n\t\t\t\t// to print the requests the user saw.\n\t\t\t\tgotCode           bool\n\t\t\t\treqDump, respDump []byte // Auth step, not token.\n\t\t\t\tstate             = \"a_state\"\n\t\t\t)\n\t\t\tdefer func() {\n\t\t\t\tif !gotCode && tc.authError == nil {\n\t\t\t\t\tt.Errorf(\"never got a code in callback\\n%s\\n%s\", reqDump, respDump)\n\t\t\t\t}\n\t\t\t}()\n\n\t\t\t// Setup OAuth2 client.\n\t\t\tvar oauth2Config *oauth2.Config\n\t\t\toauth2Client := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif r.URL.Path != \"/callback\" {\n\t\t\t\t\t// User is visiting app first time. Redirect to dex.\n\t\t\t\t\thttp.Redirect(w, r, oauth2Config.AuthCodeURL(state, tc.authCodeOptions...), http.StatusSeeOther)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// User is at '/callback' so they were just redirected _from_ dex.\n\t\t\t\tq := r.URL.Query()\n\n\t\t\t\t// Did dex return an error?\n\t\t\t\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\t\t\t\tdescription := q.Get(\"error_description\")\n\n\t\t\t\t\tif tc.authError == nil {\n\t\t\t\t\t\tif description != \"\" {\n\t\t\t\t\t\t\tt.Errorf(\"got error from server %s: %s\", errType, description)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tt.Errorf(\"got error from server %s\", errType)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\trequire.Equal(t, *tc.authError, OAuth2ErrorResponse{Error: errType, ErrorDescription: description})\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Grab code, exchange for token.\n\t\t\t\tif code := q.Get(\"code\"); code != \"\" {\n\t\t\t\t\tgotCode = true\n\t\t\t\t\ttoken, err := oauth2Config.Exchange(ctx, code, tc.retrieveTokenOptions...)\n\t\t\t\t\tif tc.tokenError.StatusCode != 0 {\n\t\t\t\t\t\tcheckErrorResponse(err, t, tc)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"failed to exchange code for token: %v\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\t\t\t\t\terr = tc.handleToken(ctx, p, oauth2Config, token, conn)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Errorf(\"%s: %v\", tc.name, err)\n\t\t\t\t\t}\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\t// Ensure state matches.\n\t\t\t\tif gotState := q.Get(\"state\"); gotState != state {\n\t\t\t\t\tt.Errorf(\"state did not match, want=%q got=%q\", state, gotState)\n\t\t\t\t}\n\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t}))\n\n\t\t\tdefer oauth2Client.Close()\n\n\t\t\t// Register the client above with dex.\n\t\t\tredirectURL := oauth2Client.URL + \"/callback\"\n\t\t\tclient := storage.Client{\n\t\t\t\tID:           clientID,\n\t\t\t\tSecret:       clientSecret,\n\t\t\t\tRedirectURIs: []string{redirectURL},\n\t\t\t}\n\t\t\tif err := s.storage.CreateClient(ctx, client); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t\t\t}\n\n\t\t\tif err := s.storage.CreateRefresh(ctx, storage.RefreshToken{\n\t\t\t\tID:       \"existedrefrestoken\",\n\t\t\t\tClientID: \"unexcistedclientid\",\n\t\t\t}); err != nil {\n\t\t\t\tt.Fatalf(\"failed to create existed refresh token: %v\", err)\n\t\t\t}\n\n\t\t\t// Create the OAuth2 config.\n\t\t\toauth2Config = &oauth2.Config{\n\t\t\t\tClientID:     client.ID,\n\t\t\t\tClientSecret: client.Secret,\n\t\t\t\tEndpoint:     p.Endpoint(),\n\t\t\t\tScopes:       requestedScopes,\n\t\t\t\tRedirectURL:  redirectURL,\n\t\t\t}\n\t\t\tif len(tc.scopes) != 0 {\n\t\t\t\toauth2Config.Scopes = tc.scopes\n\t\t\t}\n\n\t\t\t// Login!\n\t\t\t//\n\t\t\t//   1. First request to client, redirects to dex.\n\t\t\t//   2. Dex \"logs in\" the user, redirects to client with \"code\".\n\t\t\t//   3. Client exchanges \"code\" for \"token\" (id_token, refresh_token, etc.).\n\t\t\t//   4. Test is run with OAuth2 token response.\n\t\t\t//\n\t\t\tresp, err := http.Get(oauth2Client.URL + \"/login\")\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"get failed: %v\", err)\n\t\t\t}\n\t\t\tdefer resp.Body.Close()\n\n\t\t\tif reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t\tif respDump, err = httputil.DumpResponse(resp, true); err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\n\t\t\ttokens, err := s.storage.ListRefreshTokens(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to get existed refresh token: %v\", err)\n\t\t\t}\n\n\t\t\tfor _, token := range tokens {\n\t\t\t\tif /* token was updated */ token.ObsoleteToken != \"\" && token.ConnectorData != nil {\n\t\t\t\t\tt.Fatalf(\"token connectorData with id %q field is not nil: %s\", token.ID, token.ConnectorData)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestOAuth2ImplicitFlow(t *testing.T) {\n\tctx := t.Context()\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t// Enable support for the implicit flow.\n\t\tc.SupportedResponseTypes = []string{\"code\", \"token\", \"id_token\"}\n\t})\n\tdefer httpServer.Close()\n\n\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t}\n\n\tvar (\n\t\treqDump, respDump []byte\n\t\tgotIDToken        bool\n\t\tstate             = \"a_state\"\n\t\tnonce             = \"a_nonce\"\n\t)\n\tdefer func() {\n\t\tif !gotIDToken {\n\t\t\tt.Errorf(\"never got a id token in fragment\\n%s\\n%s\", reqDump, respDump)\n\t\t}\n\t}()\n\n\tvar oauth2Config *oauth2.Config\n\toauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/callback\" {\n\t\t\tq := r.URL.Query()\n\t\t\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\t\t\tif desc := q.Get(\"error_description\"); desc != \"\" {\n\t\t\t\t\tt.Errorf(\"got error from server %s: %s\", errType, desc)\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"got error from server %s\", errType)\n\t\t\t\t}\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Fragment is checked by the client since net/http servers don't preserve URL fragments.\n\t\t\t// E.g.\n\t\t\t//\n\t\t\t//    r.URL.Fragment\n\t\t\t//\n\t\t\t// Will always be empty.\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\t}\n\t\tu := oauth2Config.AuthCodeURL(state, oauth2.SetAuthURLParam(\"response_type\", \"id_token token\"), oidc.Nonce(nonce))\n\t\thttp.Redirect(w, r, u, http.StatusSeeOther)\n\t}))\n\n\tdefer oauth2Server.Close()\n\n\tredirectURL := oauth2Server.URL + \"/callback\"\n\tclient := storage.Client{\n\t\tID:           \"testclient\",\n\t\tSecret:       \"testclientsecret\",\n\t\tRedirectURIs: []string{redirectURL},\n\t}\n\tif err := s.storage.CreateClient(ctx, client); err != nil {\n\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t}\n\n\tidTokenVerifier := p.Verifier(&oidc.Config{\n\t\tClientID: client.ID,\n\t})\n\n\toauth2Config = &oauth2.Config{\n\t\tClientID:     client.ID,\n\t\tClientSecret: client.Secret,\n\t\tEndpoint:     p.Endpoint(),\n\t\tScopes:       []string{oidc.ScopeOpenID, \"profile\", \"email\", \"offline_access\"},\n\t\tRedirectURL:  redirectURL,\n\t}\n\n\tcheckIDToken := func(u *url.URL) error {\n\t\tif u.Fragment == \"\" {\n\t\t\treturn fmt.Errorf(\"url has no fragment: %s\", u)\n\t\t}\n\t\tv, err := url.ParseQuery(u.Fragment)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to parse fragment: %v\", err)\n\t\t}\n\t\trawIDToken := v.Get(\"id_token\")\n\t\tif rawIDToken == \"\" {\n\t\t\treturn errors.New(\"no id_token in fragment\")\n\t\t}\n\t\tidToken, err := idTokenVerifier.Verify(ctx, rawIDToken)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to verify id_token: %v\", err)\n\t\t}\n\t\tif idToken.Nonce != nonce {\n\t\t\treturn fmt.Errorf(\"failed to verify id_token: nonce was %v, but want %v\", idToken.Nonce, nonce)\n\t\t}\n\t\treturn nil\n\t}\n\n\thttpClient := &http.Client{\n\t\t// net/http servers don't preserve URL fragments when passing the request to\n\t\t// handlers. The only way to get at that values is to check the redirect on\n\t\t// the client side.\n\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {\n\t\t\tif len(via) > 10 {\n\t\t\t\treturn errors.New(\"too many redirects\")\n\t\t\t}\n\n\t\t\t// If we're being redirected back to the client server, inspect the URL fragment\n\t\t\t// for an ID Token.\n\t\t\tu := req.URL.String()\n\t\t\tif strings.HasPrefix(u, oauth2Server.URL) {\n\t\t\t\tif err := checkIDToken(req.URL); err == nil {\n\t\t\t\t\tgotIDToken = true\n\t\t\t\t} else {\n\t\t\t\t\tt.Error(err)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tresp, err := httpClient.Get(oauth2Server.URL + \"/login\")\n\tif err != nil {\n\t\tt.Fatalf(\"get failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif respDump, err = httputil.DumpResponse(resp, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestCrossClientScopes(t *testing.T) {\n\tctx := t.Context()\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t})\n\tdefer httpServer.Close()\n\n\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t}\n\n\tvar (\n\t\treqDump, respDump []byte\n\t\tgotCode           bool\n\t\tstate             = \"a_state\"\n\t)\n\tdefer func() {\n\t\tif !gotCode {\n\t\t\tt.Errorf(\"never got a code in callback\\n%s\\n%s\", reqDump, respDump)\n\t\t}\n\t}()\n\n\ttestClientID := \"testclient\"\n\tpeerID := \"peer\"\n\n\tvar oauth2Config *oauth2.Config\n\toauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/callback\" {\n\t\t\tq := r.URL.Query()\n\t\t\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\t\t\tif desc := q.Get(\"error_description\"); desc != \"\" {\n\t\t\t\t\tt.Errorf(\"got error from server %s: %s\", errType, desc)\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"got error from server %s\", errType)\n\t\t\t\t}\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif code := q.Get(\"code\"); code != \"\" {\n\t\t\t\tgotCode = true\n\t\t\t\ttoken, err := oauth2Config.Exchange(ctx, code)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"failed to exchange code for token: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Errorf(\"no id token found: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tidToken, err := p.Verifier(&oidc.Config{ClientID: testClientID}).Verify(ctx, rawIDToken)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"failed to parse ID Token: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tsort.Strings(idToken.Audience)\n\t\t\t\texpAudience := []string{peerID, testClientID}\n\t\t\t\tif !reflect.DeepEqual(idToken.Audience, expAudience) {\n\t\t\t\t\tt.Errorf(\"expected audience %q, got %q\", expAudience, idToken.Audience)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif gotState := q.Get(\"state\"); gotState != state {\n\t\t\t\tt.Errorf(\"state did not match, want=%q got=%q\", state, gotState)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\t}\n\t\thttp.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusSeeOther)\n\t}))\n\n\tdefer oauth2Server.Close()\n\n\tredirectURL := oauth2Server.URL + \"/callback\"\n\tclient := storage.Client{\n\t\tID:           testClientID,\n\t\tSecret:       \"testclientsecret\",\n\t\tRedirectURIs: []string{redirectURL},\n\t}\n\tif err := s.storage.CreateClient(ctx, client); err != nil {\n\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t}\n\n\tpeer := storage.Client{\n\t\tID:           peerID,\n\t\tSecret:       \"foobar\",\n\t\tTrustedPeers: []string{\"testclient\"},\n\t}\n\n\tif err := s.storage.CreateClient(ctx, peer); err != nil {\n\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t}\n\n\toauth2Config = &oauth2.Config{\n\t\tClientID:     client.ID,\n\t\tClientSecret: client.Secret,\n\t\tEndpoint:     p.Endpoint(),\n\t\tScopes: []string{\n\t\t\toidc.ScopeOpenID, \"profile\", \"email\",\n\t\t\t\"audience:server:client_id:\" + client.ID,\n\t\t\t\"audience:server:client_id:\" + peer.ID,\n\t\t},\n\t\tRedirectURL: redirectURL,\n\t}\n\n\tresp, err := http.Get(oauth2Server.URL + \"/login\")\n\tif err != nil {\n\t\tt.Fatalf(\"get failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif respDump, err = httputil.DumpResponse(resp, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestCrossClientScopesWithAzpInAudienceByDefault(t *testing.T) {\n\tctx := t.Context()\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Issuer += \"/non-root-path\"\n\t})\n\tdefer httpServer.Close()\n\n\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t}\n\n\tvar (\n\t\treqDump, respDump []byte\n\t\tgotCode           bool\n\t\tstate             = \"a_state\"\n\t)\n\tdefer func() {\n\t\tif !gotCode {\n\t\t\tt.Errorf(\"never got a code in callback\\n%s\\n%s\", reqDump, respDump)\n\t\t}\n\t}()\n\n\ttestClientID := \"testclient\"\n\tpeerID := \"peer\"\n\n\tvar oauth2Config *oauth2.Config\n\toauth2Server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path == \"/callback\" {\n\t\t\tq := r.URL.Query()\n\t\t\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\t\t\tif desc := q.Get(\"error_description\"); desc != \"\" {\n\t\t\t\t\tt.Errorf(\"got error from server %s: %s\", errType, desc)\n\t\t\t\t} else {\n\t\t\t\t\tt.Errorf(\"got error from server %s\", errType)\n\t\t\t\t}\n\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif code := q.Get(\"code\"); code != \"\" {\n\t\t\t\tgotCode = true\n\t\t\t\ttoken, err := oauth2Config.Exchange(ctx, code)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"failed to exchange code for token: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\trawIDToken, ok := token.Extra(\"id_token\").(string)\n\t\t\t\tif !ok {\n\t\t\t\t\tt.Errorf(\"no id token found: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tidToken, err := p.Verifier(&oidc.Config{ClientID: testClientID}).Verify(ctx, rawIDToken)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"failed to parse ID Token: %v\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tsort.Strings(idToken.Audience)\n\t\t\t\texpAudience := []string{peerID, testClientID}\n\t\t\t\tif !reflect.DeepEqual(idToken.Audience, expAudience) {\n\t\t\t\t\tt.Errorf(\"expected audience %q, got %q\", expAudience, idToken.Audience)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif gotState := q.Get(\"state\"); gotState != state {\n\t\t\t\tt.Errorf(\"state did not match, want=%q got=%q\", state, gotState)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\treturn\n\t\t}\n\t\thttp.Redirect(w, r, oauth2Config.AuthCodeURL(state), http.StatusSeeOther)\n\t}))\n\n\tdefer oauth2Server.Close()\n\n\tredirectURL := oauth2Server.URL + \"/callback\"\n\tclient := storage.Client{\n\t\tID:           testClientID,\n\t\tSecret:       \"testclientsecret\",\n\t\tRedirectURIs: []string{redirectURL},\n\t}\n\tif err := s.storage.CreateClient(ctx, client); err != nil {\n\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t}\n\n\tpeer := storage.Client{\n\t\tID:           peerID,\n\t\tSecret:       \"foobar\",\n\t\tTrustedPeers: []string{\"testclient\"},\n\t}\n\n\tif err := s.storage.CreateClient(ctx, peer); err != nil {\n\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t}\n\n\toauth2Config = &oauth2.Config{\n\t\tClientID:     client.ID,\n\t\tClientSecret: client.Secret,\n\t\tEndpoint:     p.Endpoint(),\n\t\tScopes: []string{\n\t\t\toidc.ScopeOpenID, \"profile\", \"email\",\n\t\t\t\"audience:server:client_id:\" + peer.ID,\n\t\t},\n\t\tRedirectURL: redirectURL,\n\t}\n\n\tresp, err := http.Get(oauth2Server.URL + \"/login\")\n\tif err != nil {\n\t\tt.Fatalf(\"get failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif reqDump, err = httputil.DumpRequest(resp.Request, false); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif respDump, err = httputil.DumpResponse(resp, true); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestPasswordDB(t *testing.T) {\n\tctx := t.Context()\n\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\tconn := newPasswordDB(s)\n\n\tpw := \"hi\"\n\n\th, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\ts.CreatePassword(ctx, storage.Password{\n\t\tEmail:             \"jane@example.com\",\n\t\tUsername:          \"jane\",\n\t\tName:              \"Jane Doe\",\n\t\tPreferredUsername: \"jane-public\",\n\t\tEmailVerified:     boolPtr(false),\n\t\tUserID:            \"foobar\",\n\t\tGroups:            []string{\"team-a\", \"team-a/admins\"},\n\t\tHash:              h,\n\t})\n\n\ttests := []struct {\n\t\tname         string\n\t\tusername     string\n\t\tpassword     string\n\t\twantIdentity connector.Identity\n\t\twantInvalid  bool\n\t\twantErr      bool\n\t}{\n\t\t{\n\t\t\tname:     \"valid password\",\n\t\t\tusername: \"jane@example.com\",\n\t\t\tpassword: pw,\n\t\t\twantIdentity: connector.Identity{\n\t\t\t\tEmail:             \"jane@example.com\",\n\t\t\t\tUsername:          \"Jane Doe\",\n\t\t\t\tPreferredUsername: \"jane-public\",\n\t\t\t\tUserID:            \"foobar\",\n\t\t\t\tEmailVerified:     false,\n\t\t\t\tGroups:            []string{\"team-a\", \"team-a/admins\"},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname:        \"unknown user\",\n\t\t\tusername:    \"john@example.com\",\n\t\t\tpassword:    pw,\n\t\t\twantInvalid: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"invalid password\",\n\t\t\tusername:    \"jane@example.com\",\n\t\t\tpassword:    \"not the correct password\",\n\t\t\twantInvalid: true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tident, valid, err := conn.Login(t.Context(), connector.Scopes{}, tc.username, tc.password)\n\t\tif err != nil {\n\t\t\tif !tc.wantErr {\n\t\t\t\tt.Errorf(\"%s: %v\", tc.name, err)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif tc.wantErr {\n\t\t\tt.Errorf(\"%s: expected error\", tc.name)\n\t\t\tcontinue\n\t\t}\n\n\t\tif !valid {\n\t\t\tif !tc.wantInvalid {\n\t\t\t\tt.Errorf(\"%s: expected valid password\", tc.name)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tif tc.wantInvalid {\n\t\t\tt.Errorf(\"%s: expected invalid password\", tc.name)\n\t\t\tcontinue\n\t\t}\n\n\t\tif diff := pretty.Compare(tc.wantIdentity, ident); diff != \"\" {\n\t\t\tt.Errorf(\"%s: %s\", tc.name, diff)\n\t\t}\n\t}\n}\n\nfunc TestPasswordDBUsernamePrompt(t *testing.T) {\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\tconn := newPasswordDB(s)\n\n\texpected := \"Email Address\"\n\tif actual := conn.Prompt(); actual != expected {\n\t\tt.Errorf(\"expected %v, got %v\", expected, actual)\n\t}\n}\n\ntype storageWithKeysTrigger struct {\n\tstorage.Storage\n\tf func()\n}\n\nfunc (s storageWithKeysTrigger) GetKeys(ctx context.Context) (storage.Keys, error) {\n\ts.f()\n\treturn s.Storage.GetKeys(ctx)\n}\n\nfunc TestKeyCacher(t *testing.T) {\n\ttNow := time.Now()\n\tnow := func() time.Time { return tNow }\n\tctx := t.Context()\n\tlogger := newLogger(t)\n\ts := memory.New(logger)\n\n\ttests := []struct {\n\t\tbefore            func()\n\t\twantCallToStorage bool\n\t}{\n\t\t{\n\t\t\tbefore:            func() {},\n\t\t\twantCallToStorage: true,\n\t\t},\n\t\t{\n\t\t\tbefore: func() {\n\t\t\t\ts.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) {\n\t\t\t\t\told.NextRotation = tNow.Add(time.Minute)\n\t\t\t\t\treturn old, nil\n\t\t\t\t})\n\t\t\t},\n\t\t\twantCallToStorage: true,\n\t\t},\n\t\t{\n\t\t\tbefore:            func() {},\n\t\t\twantCallToStorage: false,\n\t\t},\n\t\t{\n\t\t\tbefore: func() {\n\t\t\t\ttNow = tNow.Add(time.Hour)\n\t\t\t},\n\t\t\twantCallToStorage: true,\n\t\t},\n\t\t{\n\t\t\tbefore: func() {\n\t\t\t\ttNow = tNow.Add(time.Hour)\n\t\t\t\ts.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) {\n\t\t\t\t\told.NextRotation = tNow.Add(time.Minute)\n\t\t\t\t\treturn old, nil\n\t\t\t\t})\n\t\t\t},\n\t\t\twantCallToStorage: true,\n\t\t},\n\t\t{\n\t\t\tbefore:            func() {},\n\t\t\twantCallToStorage: false,\n\t\t},\n\t}\n\n\tgotCall := false\n\ts = newKeyCacher(storageWithKeysTrigger{s, func() { gotCall = true }}, now)\n\tfor i, tc := range tests {\n\t\tgotCall = false\n\t\ttc.before()\n\t\ts.GetKeys(t.Context())\n\t\tif gotCall != tc.wantCallToStorage {\n\t\t\tt.Errorf(\"case %d: expected call to storage=%t got call to storage=%t\", i, tc.wantCallToStorage, gotCall)\n\t\t}\n\t}\n}\n\nfunc checkErrorResponse(err error, t *testing.T, tc test) {\n\tif err == nil {\n\t\tt.Errorf(\"%s: DANGEROUS! got a token when we should not get one!\", tc.name)\n\t\treturn\n\t}\n\tif rErr, ok := err.(*oauth2.RetrieveError); ok {\n\t\tif rErr.Response.StatusCode != tc.tokenError.StatusCode {\n\t\t\tt.Errorf(\"%s: got wrong StatusCode from server %d. expected %d\",\n\t\t\t\ttc.name, rErr.Response.StatusCode, tc.tokenError.StatusCode)\n\t\t}\n\t\tdetails := new(OAuth2ErrorResponse)\n\t\tif err := json.Unmarshal(rErr.Body, details); err != nil {\n\t\t\tt.Errorf(\"%s: could not parse return json: %s\", tc.name, err)\n\t\t\treturn\n\t\t}\n\t\tif tc.tokenError.Error != \"\" && details.Error != tc.tokenError.Error {\n\t\t\tt.Errorf(\"%s: got wrong Error in response: %s (%s). expected %s\",\n\t\t\t\ttc.name, details.Error, details.ErrorDescription, tc.tokenError.Error)\n\t\t}\n\t} else {\n\t\tt.Errorf(\"%s: unexpected error type: %s. expected *oauth2.RetrieveError\", tc.name, reflect.TypeOf(err))\n\t}\n}\n\ntype oauth2Client struct {\n\tconfig *oauth2.Config\n\ttoken  *oauth2.Token\n\tserver *httptest.Server\n}\n\n// TestRefreshTokenFlow tests the refresh token code flow for oauth2. The test verifies\n// that only valid refresh tokens can be used to refresh an expired token.\nfunc TestRefreshTokenFlow(t *testing.T) {\n\tstate := \"state\"\n\tnow := time.Now\n\n\tctx := t.Context()\n\n\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\tc.Now = now\n\t})\n\tdefer httpServer.Close()\n\n\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t}\n\n\tvar oauth2Client oauth2Client\n\n\toauth2Client.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.URL.Path != \"/callback\" {\n\t\t\t// User is visiting app first time. Redirect to dex.\n\t\t\thttp.Redirect(w, r, oauth2Client.config.AuthCodeURL(state), http.StatusSeeOther)\n\t\t\treturn\n\t\t}\n\n\t\t// User is at '/callback' so they were just redirected _from_ dex.\n\t\tq := r.URL.Query()\n\n\t\tif errType := q.Get(\"error\"); errType != \"\" {\n\t\t\tif desc := q.Get(\"error_description\"); desc != \"\" {\n\t\t\t\tt.Errorf(\"got error from server %s: %s\", errType, desc)\n\t\t\t} else {\n\t\t\t\tt.Errorf(\"got error from server %s\", errType)\n\t\t\t}\n\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\treturn\n\t\t}\n\n\t\t// Grab code, exchange for token.\n\t\tif code := q.Get(\"code\"); code != \"\" {\n\t\t\ttoken, err := oauth2Client.config.Exchange(ctx, code)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"failed to exchange code for token: %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\toauth2Client.token = token\n\t\t}\n\n\t\t// Ensure state matches.\n\t\tif gotState := q.Get(\"state\"); gotState != state {\n\t\t\tt.Errorf(\"state did not match, want=%q got=%q\", state, gotState)\n\t\t}\n\t\tw.WriteHeader(http.StatusOK)\n\t}))\n\tdefer oauth2Client.server.Close()\n\n\t// Register the client above with dex.\n\tredirectURL := oauth2Client.server.URL + \"/callback\"\n\tclient := storage.Client{\n\t\tID:           \"testclient\",\n\t\tSecret:       \"testclientsecret\",\n\t\tRedirectURIs: []string{redirectURL},\n\t}\n\tif err := s.storage.CreateClient(ctx, client); err != nil {\n\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t}\n\n\toauth2Client.config = &oauth2.Config{\n\t\tClientID:     client.ID,\n\t\tClientSecret: client.Secret,\n\t\tEndpoint:     p.Endpoint(),\n\t\tScopes:       []string{oidc.ScopeOpenID, \"email\", \"offline_access\"},\n\t\tRedirectURL:  redirectURL,\n\t}\n\n\tresp, err := http.Get(oauth2Client.server.URL + \"/login\")\n\tif err != nil {\n\t\tt.Fatalf(\"get failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\ttok := &oauth2.Token{\n\t\tRefreshToken: oauth2Client.token.RefreshToken,\n\t\tExpiry:       time.Now().Add(-time.Hour),\n\t}\n\n\t// Login in again to receive a new token.\n\tresp, err = http.Get(oauth2Client.server.URL + \"/login\")\n\tif err != nil {\n\t\tt.Fatalf(\"get failed: %v\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\t// try to refresh expired token with old refresh token.\n\tif _, err := oauth2Client.config.TokenSource(ctx, tok).Token(); err == nil {\n\t\tt.Errorf(\"Token refreshed with invalid refresh token, error expected.\")\n\t}\n}\n\n// TestOAuth2DeviceFlow runs device flow integration tests against a test server\nfunc TestOAuth2DeviceFlow(t *testing.T) {\n\tclientID := \"testclient\"\n\tclientSecret := \"\"\n\trequestedScopes := []string{oidc.ScopeOpenID, \"email\", \"profile\", \"groups\", \"offline_access\"}\n\n\tt0 := time.Now()\n\n\t// Always have the time function used by the server return the same time so\n\t// we can predict expected values of \"expires_in\" fields exactly.\n\tnow := func() time.Time { return t0 }\n\n\t// Connector used by the tests.\n\tvar conn *mock.Callback\n\tidTokensValidFor := time.Second * 30\n\n\ttests := makeOAuth2Tests(clientID, clientSecret, now)\n\ttestCases := []struct {\n\t\tname          string\n\t\ttokenEndpoint string\n\t\toauth2Tests   oauth2Tests\n\t}{\n\t\t{\n\t\t\tname:          \"Actual token endpoint for devices\",\n\t\t\ttokenEndpoint: \"/token\",\n\t\t\toauth2Tests:   tests,\n\t\t},\n\t\t// TODO(nabokihms): delete temporary tests after removing the deprecated token endpoint support\n\t\t{\n\t\t\tname:          \"Deprecated token endpoint for devices\",\n\t\t\ttokenEndpoint: \"/device/token\",\n\t\t\toauth2Tests:   tests,\n\t\t},\n\t}\n\n\tfor _, testCase := range testCases {\n\t\tfor _, tc := range testCase.oauth2Tests.tests {\n\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\tctx := t.Context()\n\n\t\t\t\t// Setup a dex server.\n\t\t\t\thttpServer, s := newTestServer(t, func(c *Config) {\n\t\t\t\t\tc.Issuer += \"/non-root-path\"\n\t\t\t\t\tc.Now = now\n\t\t\t\t\tc.IDTokensValidFor = idTokensValidFor\n\t\t\t\t})\n\t\t\t\tdefer httpServer.Close()\n\n\t\t\t\tmockConn := s.connectors[\"mock\"]\n\t\t\t\tconn = mockConn.Connector.(*mock.Callback)\n\n\t\t\t\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t\t\t\t}\n\n\t\t\t\t// Add the Clients to the test server\n\t\t\t\tclient := storage.Client{\n\t\t\t\t\tID:           clientID,\n\t\t\t\t\tRedirectURIs: []string{s.absPath(deviceCallbackURI)},\n\t\t\t\t\tPublic:       true,\n\t\t\t\t}\n\t\t\t\tif err := s.storage.CreateClient(ctx, client); err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t\t\t\t}\n\n\t\t\t\tif err := s.storage.CreateRefresh(ctx, storage.RefreshToken{\n\t\t\t\t\tID:       \"existedrefrestoken\",\n\t\t\t\t\tClientID: \"unexcistedclientid\",\n\t\t\t\t}); err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to create existed refresh token: %v\", err)\n\t\t\t\t}\n\n\t\t\t\t// Grab the issuer that we'll reuse for the different endpoints to hit\n\t\t\t\tissuer, err := url.Parse(s.issuerURL.String())\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Could not parse issuer URL %v\", err)\n\t\t\t\t}\n\n\t\t\t\t// Send a new Device Request\n\t\t\t\tcodeURL, _ := url.Parse(issuer.String())\n\t\t\t\tcodeURL.Path = path.Join(codeURL.Path, \"device/code\")\n\n\t\t\t\tdata := url.Values{}\n\t\t\t\tdata.Set(\"client_id\", clientID)\n\t\t\t\tdata.Add(\"scope\", strings.Join(requestedScopes, \" \"))\n\t\t\t\tresp, err := http.PostForm(codeURL.String(), data)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Could not request device code: %v\", err)\n\t\t\t\t}\n\t\t\t\tdefer resp.Body.Close()\n\t\t\t\tresponseBody, err := io.ReadAll(resp.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Could read device code response %v\", err)\n\t\t\t\t}\n\t\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\t\tt.Errorf(\"%v - Unexpected Response Type.  Expected 200 got  %v.  Response: %v\", tc.name, resp.StatusCode, string(responseBody))\n\t\t\t\t}\n\t\t\t\tif resp.Header.Get(\"Cache-Control\") != \"no-store\" {\n\t\t\t\t\tt.Errorf(\"Cache-Control header doesn't exist in Device Code Response\")\n\t\t\t\t}\n\n\t\t\t\t// Parse the code response\n\t\t\t\tvar deviceCode deviceCodeResponse\n\t\t\t\tif err := json.Unmarshal(responseBody, &deviceCode); err != nil {\n\t\t\t\t\tt.Errorf(\"Unexpected Device Code Response Format %v\", string(responseBody))\n\t\t\t\t}\n\n\t\t\t\t// Mock the user hitting the verification URI and posting the form\n\t\t\t\tverifyURL, _ := url.Parse(issuer.String())\n\t\t\t\tverifyURL.Path = path.Join(verifyURL.Path, \"/device/auth/verify_code\")\n\t\t\t\turlData := url.Values{}\n\t\t\t\turlData.Set(\"user_code\", deviceCode.UserCode)\n\t\t\t\tresp, err = http.PostForm(verifyURL.String(), urlData)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Error Posting Form: %v\", err)\n\t\t\t\t}\n\t\t\t\tdefer resp.Body.Close()\n\t\t\t\tresponseBody, err = io.ReadAll(resp.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Could read verification response %v\", err)\n\t\t\t\t}\n\t\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\t\tt.Errorf(\"%v - Unexpected Response Type.  Expected 200 got  %v.  Response: %v\", tc.name, resp.StatusCode, string(responseBody))\n\t\t\t\t}\n\n\t\t\t\t// Hit the Token Endpoint, and try and get an access token\n\t\t\t\ttokenURL, _ := url.Parse(issuer.String())\n\t\t\t\ttokenURL.Path = path.Join(tokenURL.Path, testCase.tokenEndpoint)\n\t\t\t\tv := url.Values{}\n\t\t\t\tv.Add(\"grant_type\", grantTypeDeviceCode)\n\t\t\t\tv.Add(\"device_code\", deviceCode.DeviceCode)\n\t\t\t\tresp, err = http.PostForm(tokenURL.String(), v)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Could not request device token: %v\", err)\n\t\t\t\t}\n\t\t\t\tdefer resp.Body.Close()\n\t\t\t\tresponseBody, err = io.ReadAll(resp.Body)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"Could read device token response %v\", err)\n\t\t\t\t}\n\t\t\t\tif resp.StatusCode != http.StatusOK {\n\t\t\t\t\tt.Errorf(\"%v - Unexpected Token Response Type.  Expected 200 got  %v.  Response: %v\", tc.name, resp.StatusCode, string(responseBody))\n\t\t\t\t}\n\n\t\t\t\t// Parse the response\n\t\t\t\tvar tokenRes accessTokenResponse\n\t\t\t\tif err := json.Unmarshal(responseBody, &tokenRes); err != nil {\n\t\t\t\t\tt.Errorf(\"Unexpected Device Access Token Response Format %v\", string(responseBody))\n\t\t\t\t}\n\n\t\t\t\ttoken := &oauth2.Token{\n\t\t\t\t\tAccessToken:  tokenRes.AccessToken,\n\t\t\t\t\tTokenType:    tokenRes.TokenType,\n\t\t\t\t\tRefreshToken: tokenRes.RefreshToken,\n\t\t\t\t}\n\t\t\t\traw := make(map[string]interface{})\n\t\t\t\tjson.Unmarshal(responseBody, &raw) // no error checks for optional fields\n\t\t\t\ttoken = token.WithExtra(raw)\n\t\t\t\tif secs := tokenRes.ExpiresIn; secs > 0 {\n\t\t\t\t\ttoken.Expiry = time.Now().Add(time.Duration(secs) * time.Second)\n\t\t\t\t}\n\n\t\t\t\t// Run token tests to validate info is correct\n\t\t\t\t// Create the OAuth2 config.\n\t\t\t\toauth2Config := &oauth2.Config{\n\t\t\t\t\tClientID:     client.ID,\n\t\t\t\t\tClientSecret: client.Secret,\n\t\t\t\t\tEndpoint:     p.Endpoint(),\n\t\t\t\t\tScopes:       requestedScopes,\n\t\t\t\t\tRedirectURL:  s.absURL(deviceCallbackURI),\n\t\t\t\t}\n\t\t\t\tif len(tc.scopes) != 0 {\n\t\t\t\t\toauth2Config.Scopes = tc.scopes\n\t\t\t\t}\n\t\t\t\terr = tc.handleToken(ctx, p, oauth2Config, token, conn)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"%s: %v\", tc.name, err)\n\t\t\t\t}\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc TestServerSupportedGrants(t *testing.T) {\n\ttests := []struct {\n\t\tname      string\n\t\tconfig    func(c *Config)\n\t\tresGrants []string\n\t}{\n\t\t{\n\t\t\tname:      \"Simple\",\n\t\t\tconfig:    func(c *Config) {},\n\t\t\tresGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange},\n\t\t},\n\t\t{\n\t\t\tname:      \"Minimal\",\n\t\t\tconfig:    func(c *Config) { c.AllowedGrantTypes = []string{grantTypeTokenExchange} },\n\t\t\tresGrants: []string{grantTypeTokenExchange},\n\t\t},\n\t\t{\n\t\t\tname: \"With password connector\",\n\t\t\tconfig: func(c *Config) {\n\t\t\t\tc.PasswordConnector = \"local\"\n\t\t\t},\n\t\t\tresGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange},\n\t\t},\n\t\t{\n\t\t\tname: \"Without client credentials\",\n\t\t\tconfig: func(c *Config) {\n\t\t\t\tc.AllowedGrantTypes = []string{\n\t\t\t\t\tgrantTypeAuthorizationCode,\n\t\t\t\t\tgrantTypeRefreshToken,\n\t\t\t\t\tgrantTypeDeviceCode,\n\t\t\t\t\tgrantTypeTokenExchange,\n\t\t\t\t}\n\t\t\t},\n\t\t\tresGrants: []string{grantTypeAuthorizationCode, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange},\n\t\t},\n\t\t{\n\t\t\tname: \"With token response\",\n\t\t\tconfig: func(c *Config) {\n\t\t\t\tc.SupportedResponseTypes = append(c.SupportedResponseTypes, responseTypeToken)\n\t\t\t},\n\t\t\tresGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeImplicit, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange},\n\t\t},\n\t\t{\n\t\t\tname: \"All\",\n\t\t\tconfig: func(c *Config) {\n\t\t\t\tc.PasswordConnector = \"local\"\n\t\t\t\tc.SupportedResponseTypes = append(c.SupportedResponseTypes, responseTypeToken)\n\t\t\t},\n\t\t\tresGrants: []string{grantTypeAuthorizationCode, grantTypeClientCredentials, grantTypeImplicit, grantTypePassword, grantTypeRefreshToken, grantTypeDeviceCode, grantTypeTokenExchange},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t_, srv := newTestServer(t, tc.config)\n\t\t\trequire.Equal(t, tc.resGrants, srv.supportedGrantTypes)\n\t\t})\n\t}\n}\n\nfunc TestHeaders(t *testing.T) {\n\tctx := t.Context()\n\n\thttpServer, _ := newTestServer(t, func(c *Config) {\n\t\tc.Headers = map[string][]string{\n\t\t\t\"Strict-Transport-Security\": {\"max-age=31536000; includeSubDomains\"},\n\t\t}\n\t})\n\tdefer httpServer.Close()\n\n\tp, err := oidc.NewProvider(ctx, httpServer.URL)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get provider: %v\", err)\n\t}\n\n\tresp, err := http.Get(p.Endpoint().TokenURL)\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, \"max-age=31536000; includeSubDomains\", resp.Header.Get(\"Strict-Transport-Security\"))\n}\n\nfunc TestConnectorFailureHandling(t *testing.T) {\n\tctx := t.Context()\n\n\ttests := []struct {\n\t\tname                       string\n\t\tconnectors                 []storage.Connector\n\t\tcontinueOnConnectorFailure bool\n\t\twantErr                    bool\n\t\twantErrContains            string\n\t\texpectConnectors           []string // IDs of connectors that should be loaded successfully\n\t}{\n\t\t{\n\t\t\tname: \"all connectors succeed with flag enabled\",\n\t\t\tconnectors: []storage.Connector{\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock1\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Mock1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock2\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Mock2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontinueOnConnectorFailure: true,\n\t\t\twantErr:                    false,\n\t\t\texpectConnectors:           []string{\"mock1\", \"mock2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"all connectors succeed with flag disabled\",\n\t\t\tconnectors: []storage.Connector{\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock1\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Mock1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock2\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Mock2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontinueOnConnectorFailure: false,\n\t\t\twantErr:                    false,\n\t\t\texpectConnectors:           []string{\"mock1\", \"mock2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"partial connector failure with flag enabled\",\n\t\t\tconnectors: []storage.Connector{\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock-good\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Good Mock\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"bad-connector\",\n\t\t\t\t\tType: \"nonexistent\",\n\t\t\t\t\tName: \"Bad Connector\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock-good2\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Good Mock 2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontinueOnConnectorFailure: true,\n\t\t\twantErr:                    false,\n\t\t\texpectConnectors:           []string{\"mock-good\", \"mock-good2\"},\n\t\t},\n\t\t{\n\t\t\tname: \"partial connector failure with flag disabled\",\n\t\t\tconnectors: []storage.Connector{\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock-good\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Good Mock\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"bad-connector\",\n\t\t\t\t\tType: \"nonexistent\",\n\t\t\t\t\tName: \"Bad Connector\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"mock-good2\",\n\t\t\t\t\tType: \"mockCallback\",\n\t\t\t\t\tName: \"Good Mock 2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontinueOnConnectorFailure: false,\n\t\t\twantErr:                    true,\n\t\t\twantErrContains:            \"Failed to open connector bad-connector\",\n\t\t\texpectConnectors:           []string{}, // Server creation should fail\n\t\t},\n\t\t{\n\t\t\tname: \"all connectors fail with flag enabled\",\n\t\t\tconnectors: []storage.Connector{\n\t\t\t\t{\n\t\t\t\t\tID:   \"bad1\",\n\t\t\t\t\tType: \"nonexistent1\",\n\t\t\t\t\tName: \"Bad 1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"bad2\",\n\t\t\t\t\tType: \"nonexistent2\",\n\t\t\t\t\tName: \"Bad 2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontinueOnConnectorFailure: true,\n\t\t\twantErr:                    true,\n\t\t\twantErrContains:            \"failed to open all connectors (2/2)\",\n\t\t},\n\t\t{\n\t\t\tname: \"all connectors fail with flag disabled\",\n\t\t\tconnectors: []storage.Connector{\n\t\t\t\t{\n\t\t\t\t\tID:   \"bad1\",\n\t\t\t\t\tType: \"nonexistent1\",\n\t\t\t\t\tName: \"Bad 1\",\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tID:   \"bad2\",\n\t\t\t\t\tType: \"nonexistent2\",\n\t\t\t\t\tName: \"Bad 2\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tcontinueOnConnectorFailure: false,\n\t\t\twantErr:                    true,\n\t\t\twantErrContains:            \"Failed to open connector\",\n\t\t},\n\t\t{\n\t\t\tname:                       \"no connectors\",\n\t\t\tconnectors:                 []storage.Connector{},\n\t\t\tcontinueOnConnectorFailure: true,\n\t\t\twantErr:                    true,\n\t\t\twantErrContains:            \"no connectors specified\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlogger := newLogger(t)\n\n\t\t\tsig, err := signer.NewMockSigner(testKey)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create mock signer: %v\", err)\n\t\t\t}\n\n\t\t\tconfig := Config{\n\t\t\t\tIssuer:  \"http://localhost\",\n\t\t\t\tStorage: memory.New(logger),\n\t\t\t\tWeb: WebConfig{\n\t\t\t\t\tDir: \"../web\",\n\t\t\t\t},\n\t\t\t\tLogger:                     logger,\n\t\t\t\tPrometheusRegistry:         prometheus.NewRegistry(),\n\t\t\t\tHealthChecker:              gosundheit.New(),\n\t\t\t\tContinueOnConnectorFailure: tc.continueOnConnectorFailure,\n\t\t\t\tSigner:                     sig,\n\t\t\t}\n\n\t\t\t// Create connectors in storage\n\t\t\tfor _, conn := range tc.connectors {\n\t\t\t\tif err := config.Storage.CreateConnector(ctx, conn); err != nil {\n\t\t\t\t\tt.Fatalf(\"failed to create connector: %v\", err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tserver, err := newServer(ctx, config)\n\n\t\t\tif tc.wantErr {\n\t\t\t\tif err == nil {\n\t\t\t\t\tt.Errorf(\"expected error but got none\")\n\t\t\t\t} else if tc.wantErrContains != \"\" && !strings.Contains(err.Error(), tc.wantErrContains) {\n\t\t\t\t\tt.Errorf(\"expected error containing %q, got %q\", tc.wantErrContains, err.Error())\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"unexpected error: %v\", err)\n\t\t\t\t} else {\n\t\t\t\t\t// Verify expected connectors are loaded\n\t\t\t\t\tfor _, id := range tc.expectConnectors {\n\t\t\t\t\t\tif _, exists := server.connectors[id]; !exists {\n\t\t\t\t\t\t\tt.Errorf(\"expected connector %q to be loaded\", id)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Verify failed connectors are not loaded\n\t\t\t\t\tfor _, conn := range tc.connectors {\n\t\t\t\t\t\t_, shouldExist := false, false\n\t\t\t\t\t\tfor _, expectedID := range tc.expectConnectors {\n\t\t\t\t\t\t\tif conn.ID == expectedID {\n\t\t\t\t\t\t\t\tshouldExist = true\n\t\t\t\t\t\t\t\tbreak\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t_, exists := server.connectors[conn.ID]\n\t\t\t\t\t\tif shouldExist && !exists {\n\t\t\t\t\t\t\tt.Errorf(\"connector %q should have been loaded but wasn't\", conn.ID)\n\t\t\t\t\t\t} else if !shouldExist && exists {\n\t\t\t\t\t\t\tt.Errorf(\"connector %q should not have been loaded but was\", conn.ID)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/session.go",
    "content": "package server\n\nimport (\n\t\"context\"\n\t\"crypto/hmac\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// rememberMeDefault returns a pointer to the default remember-me value if sessions are enabled, nil otherwise.\nfunc (s *Server) rememberMeDefault() *bool {\n\tif s.sessionConfig == nil {\n\t\treturn nil\n\t}\n\tv := s.sessionConfig.RememberMeCheckedByDefault\n\treturn &v\n}\n\n// remoteIP returns the real IP from context (set by parseRealIP middleware) or falls back to r.RemoteAddr.\nfunc remoteIP(r *http.Request) string {\n\tif ip, ok := r.Context().Value(RequestKeyRemoteIP).(string); ok && ip != \"\" {\n\t\treturn ip\n\t}\n\treturn r.RemoteAddr\n}\n\n// sessionCookieValue encodes session identity into a cookie value.\n// Format: base64url(userID) + \".\" + base64url(connectorID) + \".\" + nonce\n// TODO(nabokihms): consider cookie encoding\nfunc sessionCookieValue(userID, connectorID, nonce string) string {\n\treturn base64.RawURLEncoding.EncodeToString([]byte(userID)) +\n\t\t\".\" + base64.RawURLEncoding.EncodeToString([]byte(connectorID)) +\n\t\t\".\" + nonce\n}\n\n// parseSessionCookie decodes a session cookie value into its components.\nfunc parseSessionCookie(value string) (userID, connectorID, nonce string, err error) {\n\tparts := strings.SplitN(value, \".\", 3)\n\tif len(parts) != 3 {\n\t\treturn \"\", \"\", \"\", fmt.Errorf(\"invalid session cookie format\")\n\t}\n\n\tuserIDBytes, err := base64.RawURLEncoding.DecodeString(parts[0])\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", fmt.Errorf(\"decode userID: %w\", err)\n\t}\n\n\tconnectorIDBytes, err := base64.RawURLEncoding.DecodeString(parts[1])\n\tif err != nil {\n\t\treturn \"\", \"\", \"\", fmt.Errorf(\"decode connectorID: %w\", err)\n\t}\n\n\treturn string(userIDBytes), string(connectorIDBytes), parts[2], nil\n}\n\nfunc (s *Server) sessionCookiePath() string {\n\tif s.issuerURL.Path == \"\" {\n\t\treturn \"/\"\n\t}\n\treturn s.issuerURL.Path\n}\n\nfunc (s *Server) setSessionCookie(w http.ResponseWriter, userID, connectorID, nonce string, rememberMe bool) {\n\tcookie := &http.Cookie{\n\t\tName:     s.sessionConfig.CookieName,\n\t\tValue:    sessionCookieValue(userID, connectorID, nonce),\n\t\tPath:     s.sessionCookiePath(),\n\t\tHttpOnly: true,\n\t\tSecure:   s.issuerURL.Scheme == \"https\",\n\t\tSameSite: http.SameSiteLaxMode,\n\t}\n\tif rememberMe {\n\t\tcookie.MaxAge = int(s.sessionConfig.AbsoluteLifetime.Seconds())\n\t}\n\thttp.SetCookie(w, cookie)\n}\n\nfunc (s *Server) clearSessionCookie(w http.ResponseWriter) {\n\thttp.SetCookie(w, &http.Cookie{\n\t\tName:     s.sessionConfig.CookieName,\n\t\tValue:    \"\",\n\t\tPath:     s.sessionCookiePath(),\n\t\tHttpOnly: true,\n\t\tSecure:   s.issuerURL.Scheme == \"https\",\n\t\tSameSite: http.SameSiteLaxMode,\n\t\tMaxAge:   -1,\n\t})\n}\n\n// getValidAuthSession returns a valid, non-expired session or nil.\n// It parses the session cookie to extract (userID, connectorID, nonce),\n// looks up the session by composite key, and verifies the nonce.\n// Invalid or expired session cookies are cleared automatically.\nfunc (s *Server) getValidAuthSession(ctx context.Context, w http.ResponseWriter, r *http.Request, authReq *storage.AuthRequest) *storage.AuthSession {\n\tif s.sessionConfig == nil {\n\t\treturn nil\n\t}\n\n\tcookie, err := r.Cookie(s.sessionConfig.CookieName)\n\tif err != nil || cookie.Value == \"\" {\n\t\treturn nil\n\t}\n\n\tuserID, connectorID, nonce, err := parseSessionCookie(cookie.Value)\n\tif err != nil {\n\t\ts.logger.DebugContext(ctx, \"invalid session cookie format\", \"err\", err)\n\t\ts.clearSessionCookie(w)\n\t\treturn nil\n\t}\n\n\tsession, err := s.storage.GetAuthSession(ctx, userID, connectorID)\n\tif err != nil {\n\t\tif !errors.Is(err, storage.ErrNotFound) {\n\t\t\ts.logger.ErrorContext(ctx, \"failed to get auth session\", \"err\", err)\n\t\t}\n\t\ts.clearSessionCookie(w)\n\t\treturn nil\n\t}\n\n\t// Verify nonce to prevent cookie forgery.\n\tif session.Nonce != nonce {\n\t\ts.logger.DebugContext(ctx, \"auth session nonce mismatch\")\n\t\ts.clearSessionCookie(w)\n\t\treturn nil\n\t}\n\n\tnow := s.now()\n\n\t// Check absolute lifetime using the stored expiry (set once at creation).\n\tif !session.AbsoluteExpiry.IsZero() && now.After(session.AbsoluteExpiry) {\n\t\ts.logger.InfoContext(ctx, \"auth session expired (absolute lifetime)\",\n\t\t\t\"user_id\", session.UserID, \"connector_id\", session.ConnectorID)\n\t\tif err := s.storage.DeleteAuthSession(ctx, session.UserID, session.ConnectorID); err != nil {\n\t\t\ts.logger.DebugContext(ctx, \"failed to delete expired auth session\", \"err\", err)\n\t\t}\n\t\ts.clearSessionCookie(w)\n\t\treturn nil\n\t}\n\n\t// Check idle timeout using the stored expiry (updated on every activity).\n\tif !session.IdleExpiry.IsZero() && now.After(session.IdleExpiry) {\n\t\ts.logger.InfoContext(ctx, \"auth session expired (idle timeout)\",\n\t\t\t\"user_id\", session.UserID, \"connector_id\", session.ConnectorID)\n\t\tif err := s.storage.DeleteAuthSession(ctx, session.UserID, session.ConnectorID); err != nil {\n\t\t\ts.logger.DebugContext(ctx, \"failed to delete expired auth session\", \"err\", err)\n\t\t}\n\t\ts.clearSessionCookie(w)\n\t\treturn nil\n\t}\n\n\t// Only reuse sessions from the same connector.\n\tif session.ConnectorID != authReq.ConnectorID {\n\t\treturn nil\n\t}\n\n\treturn &session\n}\n\n// createOrUpdateAuthSession creates a new session or updates an existing one\n// after a successful login, and sets the session cookie.\n// rememberMe controls whether the cookie is persistent (survives browser close).\nfunc (s *Server) createOrUpdateAuthSession(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq storage.AuthRequest, rememberMe bool) error {\n\tif s.sessionConfig == nil {\n\t\treturn nil\n\t}\n\n\tnow := s.now()\n\tuserID := authReq.Claims.UserID\n\tconnectorID := authReq.ConnectorID\n\n\tclientState := &storage.ClientAuthState{\n\t\tActive:       true,\n\t\tExpiresAt:    now.Add(s.sessionConfig.AbsoluteLifetime),\n\t\tLastActivity: now,\n\t}\n\n\t// Try to reuse existing session for this (userID, connectorID).\n\tsession, err := s.storage.GetAuthSession(ctx, userID, connectorID)\n\tif err == nil {\n\t\t// Session exists, update it.\n\t\ts.logger.DebugContext(ctx, \"updating existing auth session\",\n\t\t\t\"user_id\", userID, \"connector_id\", connectorID, \"client_id\", authReq.ClientID)\n\n\t\tif err := s.storage.UpdateAuthSession(ctx, userID, connectorID, func(old storage.AuthSession) (storage.AuthSession, error) {\n\t\t\told.LastActivity = now\n\t\t\told.IdleExpiry = now.Add(s.sessionConfig.ValidIfNotUsedFor)\n\t\t\tif old.ClientStates == nil {\n\t\t\t\told.ClientStates = make(map[string]*storage.ClientAuthState)\n\t\t\t}\n\t\t\told.ClientStates[authReq.ClientID] = clientState\n\t\t\treturn old, nil\n\t\t}); err != nil {\n\t\t\treturn fmt.Errorf(\"update auth session: %w\", err)\n\t\t}\n\n\t\ts.setSessionCookie(w, userID, connectorID, session.Nonce, rememberMe)\n\t\treturn nil\n\t}\n\n\t// Unexpected error, exit the method.\n\tif !errors.Is(err, storage.ErrNotFound) {\n\t\treturn fmt.Errorf(\"get auth session: %w\", err)\n\t}\n\n\tnonce := storage.NewID()\n\tnewSession := storage.AuthSession{\n\t\tUserID:      userID,\n\t\tConnectorID: connectorID,\n\t\tNonce:       nonce,\n\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\tauthReq.ClientID: clientState,\n\t\t},\n\t\tCreatedAt:      now,\n\t\tLastActivity:   now,\n\t\tIPAddress:      remoteIP(r),\n\t\tUserAgent:      r.UserAgent(),\n\t\tAbsoluteExpiry: now.Add(s.sessionConfig.AbsoluteLifetime),\n\t\tIdleExpiry:     now.Add(s.sessionConfig.ValidIfNotUsedFor),\n\t}\n\n\tif err := s.storage.CreateAuthSession(ctx, newSession); err != nil {\n\t\treturn fmt.Errorf(\"create auth session: %w\", err)\n\t}\n\n\ts.logger.DebugContext(ctx, \"created new auth session\",\n\t\t\"user_id\", userID, \"connector_id\", connectorID, \"client_id\", authReq.ClientID)\n\ts.setSessionCookie(w, userID, connectorID, nonce, rememberMe)\n\treturn nil\n}\n\n// trySessionLogin checks if the user has a valid session for the same connector.\n// If so, it finalizes login from the stored identity and returns a redirect URL.\n// Returns (\"\", false) if session-based login is not possible.\nfunc (s *Server) trySessionLogin(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq *storage.AuthRequest) (string, bool) {\n\tsession := s.getValidAuthSession(ctx, w, r, authReq)\n\treturn s.trySessionLoginWithSession(ctx, r, w, authReq, session)\n}\n\n// trySessionLoginWithSession is like trySessionLogin but accepts a pre-retrieved session.\n// This allows callers to inspect the session (e.g., for id_token_hint comparison) before\n// attempting session-based login.\nfunc (s *Server) trySessionLoginWithSession(ctx context.Context, r *http.Request, w http.ResponseWriter, authReq *storage.AuthRequest, session *storage.AuthSession) (string, bool) {\n\tif session == nil {\n\t\treturn \"\", false\n\t}\n\n\tclientState, ok := session.ClientStates[authReq.ClientID]\n\tif !ok || !clientState.Active {\n\t\treturn \"\", false\n\t}\n\n\tnow := s.now()\n\tif now.After(clientState.ExpiresAt) {\n\t\treturn \"\", false\n\t}\n\n\t// Load identity from storage.\n\tui, err := s.storage.GetUserIdentity(ctx, session.UserID, session.ConnectorID)\n\tif err != nil {\n\t\ts.logger.ErrorContext(ctx, \"session: failed to get user identity\", \"err\", err)\n\t\treturn \"\", false\n\t}\n\n\t// Check max_age: if the user's last authentication is too old, force re-auth.\n\tif authReq.MaxAge >= 0 {\n\t\tif now.Sub(ui.LastLogin) > time.Duration(authReq.MaxAge)*time.Second {\n\t\t\treturn \"\", false\n\t\t}\n\t}\n\n\tclaims := storage.Claims{\n\t\tUserID:            ui.Claims.UserID,\n\t\tUsername:          ui.Claims.Username,\n\t\tPreferredUsername: ui.Claims.PreferredUsername,\n\t\tEmail:             ui.Claims.Email,\n\t\tEmailVerified:     ui.Claims.EmailVerified,\n\t\tGroups:            ui.Claims.Groups,\n\t}\n\n\t// Update AuthRequest with stored identity and auth_time from last login.\n\tif err := s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) {\n\t\ta.LoggedIn = true\n\t\ta.Claims = claims\n\t\ta.ConnectorID = session.ConnectorID\n\t\ta.AuthTime = ui.LastLogin\n\t\treturn a, nil\n\t}); err != nil {\n\t\ts.logger.ErrorContext(ctx, \"session: failed to update auth request\", \"err\", err)\n\t\treturn \"\", false\n\t}\n\n\ts.logger.DebugContext(ctx, \"session: re-authenticated from session\",\n\t\t\"user_id\", session.UserID, \"connector_id\", session.ConnectorID)\n\n\t// Update session activity.\n\t_ = s.storage.UpdateAuthSession(ctx, session.UserID, session.ConnectorID, func(old storage.AuthSession) (storage.AuthSession, error) {\n\t\told.LastActivity = now\n\t\told.IdleExpiry = now.Add(s.sessionConfig.ValidIfNotUsedFor)\n\t\tif cs, ok := old.ClientStates[authReq.ClientID]; ok {\n\t\t\tcs.LastActivity = now\n\t\t}\n\t\treturn old, nil\n\t})\n\n\t// Build HMAC for approval URL.\n\th := hmac.New(sha256.New, authReq.HMACKey)\n\th.Write([]byte(authReq.ID))\n\tmac := base64.RawURLEncoding.EncodeToString(h.Sum(nil))\n\n\t// Skip approval if globally configured or user already consented to the requested scopes.\n\tif !authReq.ForceApprovalPrompt && (s.skipApproval || scopesCoveredByConsent(ui.Consents[authReq.ClientID], authReq.Scopes)) {\n\t\t// Re-read to get the updated AuthRequest (LoggedIn, Claims, ConnectorID set above).\n\t\tupdated, err := s.storage.GetAuthRequest(ctx, authReq.ID)\n\t\tif err != nil {\n\t\t\ts.logger.ErrorContext(ctx, \"session: failed to get auth request\", \"err\", err)\n\t\t\treturn \"\", false\n\t\t}\n\t\ts.sendCodeResponse(w, r, updated)\n\t\treturn \"\", true\n\t}\n\n\treturnURL := path.Join(s.issuerURL.Path, \"/approval\") + \"?req=\" + authReq.ID + \"&hmac=\" + mac\n\treturn returnURL, true\n}\n\n// updateSessionTokenIssuedAt updates the session's LastTokenIssuedAt for the given client.\nfunc (s *Server) updateSessionTokenIssuedAt(r *http.Request, clientID string) {\n\tif s.sessionConfig == nil {\n\t\treturn\n\t}\n\n\tcookie, err := r.Cookie(s.sessionConfig.CookieName)\n\tif err != nil || cookie.Value == \"\" {\n\t\treturn\n\t}\n\n\tuserID, connectorID, _, err := parseSessionCookie(cookie.Value)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tnow := s.now()\n\t_ = s.storage.UpdateAuthSession(r.Context(), userID, connectorID, func(old storage.AuthSession) (storage.AuthSession, error) {\n\t\told.LastActivity = now\n\t\told.IdleExpiry = now.Add(s.sessionConfig.ValidIfNotUsedFor)\n\t\tif cs, ok := old.ClientStates[clientID]; ok {\n\t\t\tcs.LastTokenIssuedAt = now\n\t\t\tcs.LastActivity = now\n\t\t}\n\t\treturn old, nil\n\t})\n}\n"
  },
  {
    "path": "server/session_test.go",
    "content": "package server\n\nimport (\n\t\"crypto\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/memory\"\n)\n\nfunc newTestSessionServer(t *testing.T) *Server {\n\tt.Helper()\n\n\tnow := time.Date(2026, 3, 16, 12, 0, 0, 0, time.UTC)\n\tissuerURL, err := url.Parse(\"https://example.com/dex\")\n\trequire.NoError(t, err)\n\n\treturn &Server{\n\t\tstorage: memory.New(nil),\n\t\tlogger:  slog.Default(),\n\t\tnow:     func() time.Time { return now },\n\t\tsessionConfig: &SessionConfig{\n\t\t\tCookieName:        \"dex_session\",\n\t\t\tAbsoluteLifetime:  24 * time.Hour,\n\t\t\tValidIfNotUsedFor: 1 * time.Hour,\n\t\t},\n\t\tissuerURL: *issuerURL,\n\t}\n}\n\nfunc TestSetSessionCookie(t *testing.T) {\n\ts := newTestSessionServer(t)\n\tw := httptest.NewRecorder()\n\n\ts.setSessionCookie(w, \"user1\", \"conn1\", \"nonce123\", false)\n\n\tcookies := w.Result().Cookies()\n\trequire.Len(t, cookies, 1)\n\n\tc := cookies[0]\n\tassert.Equal(t, \"dex_session\", c.Name)\n\tassert.Equal(t, sessionCookieValue(\"user1\", \"conn1\", \"nonce123\"), c.Value)\n\tassert.Equal(t, \"/dex\", c.Path)\n\tassert.True(t, c.HttpOnly)\n\tassert.True(t, c.Secure)\n\tassert.Equal(t, http.SameSiteLaxMode, c.SameSite)\n}\n\nfunc TestSetSessionCookie_HTTP(t *testing.T) {\n\ts := newTestSessionServer(t)\n\tu, _ := url.Parse(\"http://localhost:5556/dex\")\n\ts.issuerURL = *u\n\tw := httptest.NewRecorder()\n\n\ts.setSessionCookie(w, \"user1\", \"conn1\", \"nonce123\", false)\n\n\tcookies := w.Result().Cookies()\n\trequire.Len(t, cookies, 1)\n\tassert.False(t, cookies[0].Secure)\n}\n\nfunc TestClearSessionCookie(t *testing.T) {\n\ts := newTestSessionServer(t)\n\tw := httptest.NewRecorder()\n\n\ts.clearSessionCookie(w)\n\n\tcookies := w.Result().Cookies()\n\trequire.Len(t, cookies, 1)\n\tassert.Equal(t, -1, cookies[0].MaxAge)\n\tassert.Equal(t, \"\", cookies[0].Value)\n}\n\nfunc TestSessionCookieValueRoundtrip(t *testing.T) {\n\ttests := []struct {\n\t\tname        string\n\t\tuserID      string\n\t\tconnectorID string\n\t\tnonce       string\n\t}{\n\t\t{\"simple\", \"user1\", \"ldap\", \"abc123\"},\n\t\t{\"with special chars\", \"user@example.com\", \"oidc-provider\", \"xyz789\"},\n\t\t{\"unicode\", \"юзер\", \"коннектор\", \"nonce\"},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvalue := sessionCookieValue(tt.userID, tt.connectorID, tt.nonce)\n\t\t\tgotUser, gotConn, gotNonce, err := parseSessionCookie(value)\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tt.userID, gotUser)\n\t\t\tassert.Equal(t, tt.connectorID, gotConn)\n\t\t\tassert.Equal(t, tt.nonce, gotNonce)\n\t\t})\n\t}\n}\n\nfunc TestParseSessionCookie_Invalid(t *testing.T) {\n\t//nolint:dogsled // only for tests\n\t_, _, _, err := parseSessionCookie(\"invalid\")\n\tassert.Error(t, err)\n\t//nolint:dogsled // only for tests\n\t_, _, _, err = parseSessionCookie(\"a.b\")\n\tassert.Error(t, err)\n}\n\nfunc TestGetValidAuthSession(t *testing.T) {\n\tctx := t.Context()\n\tauthReq := &storage.AuthRequest{ConnectorID: \"conn1\"}\n\n\tt.Run(\"no session config\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.sessionConfig = nil\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq))\n\t})\n\n\tt.Run(\"no cookie\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq))\n\t})\n\n\tt.Run(\"invalid cookie format\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: \"invalid-format\"})\n\t\tw := httptest.NewRecorder()\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, w, r, authReq))\n\t\t// Cookie should be cleared.\n\t\tassert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)\n\t})\n\n\tt.Run(\"session not found\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"nouser\", \"noconn\", \"nonce\")})\n\t\tw := httptest.NewRecorder()\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, w, r, authReq))\n\t\t// Cookie should be cleared.\n\t\tassert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)\n\t})\n\n\tt.Run(\"valid session\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\t\tnonce := \"test-nonce\"\n\n\t\tsession := storage.AuthSession{\n\t\t\tUserID:         \"user1\",\n\t\t\tConnectorID:    \"conn1\",\n\t\t\tNonce:          nonce,\n\t\t\tClientStates:   map[string]*storage.ClientAuthState{},\n\t\t\tCreatedAt:      now.Add(-30 * time.Minute),\n\t\t\tLastActivity:   now.Add(-5 * time.Minute),\n\t\t\tIPAddress:      \"127.0.0.1\",\n\t\t\tUserAgent:      \"test\",\n\t\t\tAbsoluteExpiry: now.Add(24 * time.Hour),\n\t\t\tIdleExpiry:     now.Add(1 * time.Hour),\n\t\t}\n\t\trequire.NoError(t, s.storage.CreateAuthSession(ctx, session))\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user1\", \"conn1\", nonce)})\n\n\t\tresult := s.getValidAuthSession(ctx, httptest.NewRecorder(), r, authReq)\n\t\trequire.NotNil(t, result)\n\t\tassert.Equal(t, \"user1\", result.UserID)\n\t\tassert.Equal(t, \"conn1\", result.ConnectorID)\n\t})\n\n\tt.Run(\"connector mismatch\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\t\tnonce := \"test-nonce-conn\"\n\n\t\tsession := storage.AuthSession{\n\t\t\tUserID:         \"user1\",\n\t\t\tConnectorID:    \"ldap\",\n\t\t\tNonce:          nonce,\n\t\t\tClientStates:   map[string]*storage.ClientAuthState{},\n\t\t\tCreatedAt:      now.Add(-30 * time.Minute),\n\t\t\tLastActivity:   now.Add(-5 * time.Minute),\n\t\t\tIPAddress:      \"127.0.0.1\",\n\t\t\tUserAgent:      \"test\",\n\t\t\tAbsoluteExpiry: now.Add(24 * time.Hour),\n\t\t\tIdleExpiry:     now.Add(1 * time.Hour),\n\t\t}\n\t\trequire.NoError(t, s.storage.CreateAuthSession(ctx, session))\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user1\", \"ldap\", nonce)})\n\n\t\tgithubReq := &storage.AuthRequest{ConnectorID: \"github\"}\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, httptest.NewRecorder(), r, githubReq))\n\t})\n\n\tt.Run(\"nonce mismatch\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\n\t\tsession := storage.AuthSession{\n\t\t\tUserID:         \"user2\",\n\t\t\tConnectorID:    \"conn2\",\n\t\t\tNonce:          \"correct-nonce\",\n\t\t\tClientStates:   map[string]*storage.ClientAuthState{},\n\t\t\tCreatedAt:      now.Add(-30 * time.Minute),\n\t\t\tLastActivity:   now.Add(-5 * time.Minute),\n\t\t\tIPAddress:      \"127.0.0.1\",\n\t\t\tUserAgent:      \"test\",\n\t\t\tAbsoluteExpiry: now.Add(24 * time.Hour),\n\t\t\tIdleExpiry:     now.Add(1 * time.Hour),\n\t\t}\n\t\trequire.NoError(t, s.storage.CreateAuthSession(ctx, session))\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user2\", \"conn2\", \"wrong-nonce\")})\n\n\t\tconn2Req := &storage.AuthRequest{ConnectorID: \"conn2\"}\n\t\tw := httptest.NewRecorder()\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, w, r, conn2Req))\n\t\tassert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)\n\t})\n\n\tt.Run(\"expired absolute lifetime\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\t\tnonce := \"expired-nonce\"\n\n\t\tsession := storage.AuthSession{\n\t\t\tUserID:         \"user3\",\n\t\t\tConnectorID:    \"conn3\",\n\t\t\tNonce:          nonce,\n\t\t\tClientStates:   map[string]*storage.ClientAuthState{},\n\t\t\tCreatedAt:      now.Add(-25 * time.Hour),\n\t\t\tLastActivity:   now.Add(-1 * time.Minute),\n\t\t\tIPAddress:      \"127.0.0.1\",\n\t\t\tUserAgent:      \"test\",\n\t\t\tAbsoluteExpiry: now.Add(-1 * time.Hour),\n\t\t\tIdleExpiry:     now.Add(1 * time.Hour),\n\t\t}\n\t\trequire.NoError(t, s.storage.CreateAuthSession(ctx, session))\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user3\", \"conn3\", nonce)})\n\n\t\tconn3Req := &storage.AuthRequest{ConnectorID: \"conn3\"}\n\t\tw := httptest.NewRecorder()\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, w, r, conn3Req))\n\t\tassert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)\n\n\t\t// Session should be deleted.\n\t\t_, err := s.storage.GetAuthSession(ctx, \"user3\", \"conn3\")\n\t\tassert.ErrorIs(t, err, storage.ErrNotFound)\n\t})\n\n\tt.Run(\"expired idle timeout\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\t\tnonce := \"idle-nonce\"\n\n\t\tsession := storage.AuthSession{\n\t\t\tUserID:         \"user4\",\n\t\t\tConnectorID:    \"conn4\",\n\t\t\tNonce:          nonce,\n\t\t\tClientStates:   map[string]*storage.ClientAuthState{},\n\t\t\tCreatedAt:      now.Add(-2 * time.Hour),\n\t\t\tLastActivity:   now.Add(-2 * time.Hour),\n\t\t\tIPAddress:      \"127.0.0.1\",\n\t\t\tUserAgent:      \"test\",\n\t\t\tAbsoluteExpiry: now.Add(22 * time.Hour),\n\t\t\tIdleExpiry:     now.Add(-1 * time.Hour),\n\t\t}\n\t\trequire.NoError(t, s.storage.CreateAuthSession(ctx, session))\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user4\", \"conn4\", nonce)})\n\n\t\tconn4Req := &storage.AuthRequest{ConnectorID: \"conn4\"}\n\t\tw := httptest.NewRecorder()\n\t\tassert.Nil(t, s.getValidAuthSession(ctx, w, r, conn4Req))\n\t\tassert.Equal(t, -1, w.Result().Cookies()[0].MaxAge)\n\n\t\t// Session should be deleted.\n\t\t_, err := s.storage.GetAuthSession(ctx, \"user4\", \"conn4\")\n\t\tassert.ErrorIs(t, err, storage.ErrNotFound)\n\t})\n}\n\nfunc TestCreateOrUpdateAuthSession(t *testing.T) {\n\tctx := t.Context()\n\n\tt.Run(\"create new session\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tw := httptest.NewRecorder()\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\n\t\tauthReq := storage.AuthRequest{\n\t\t\tID:          \"auth-1\",\n\t\t\tClientID:    \"client-1\",\n\t\t\tClaims:      storage.Claims{UserID: \"user-1\"},\n\t\t\tConnectorID: \"mock\",\n\t\t}\n\n\t\terr := s.createOrUpdateAuthSession(ctx, r, w, authReq, false)\n\t\trequire.NoError(t, err)\n\n\t\t// Cookie should be set.\n\t\tcookies := w.Result().Cookies()\n\t\trequire.Len(t, cookies, 1)\n\n\t\tuserID, connectorID, nonce, err := parseSessionCookie(cookies[0].Value)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"user-1\", userID)\n\t\tassert.Equal(t, \"mock\", connectorID)\n\t\tassert.NotEmpty(t, nonce)\n\n\t\t// Session should exist in storage.\n\t\tsession, err := s.storage.GetAuthSession(ctx, \"user-1\", \"mock\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, \"user-1\", session.UserID)\n\t\tassert.Equal(t, \"mock\", session.ConnectorID)\n\t\trequire.Contains(t, session.ClientStates, \"client-1\")\n\t\tassert.True(t, session.ClientStates[\"client-1\"].Active)\n\t})\n\n\tt.Run(\"update existing session\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\t\tnonce := \"existing-nonce\"\n\n\t\texistingSession := storage.AuthSession{\n\t\t\tUserID:      \"user-1\",\n\t\t\tConnectorID: \"mock\",\n\t\t\tNonce:       nonce,\n\t\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\t\"client-1\": {\n\t\t\t\t\tActive:       true,\n\t\t\t\t\tExpiresAt:    now.Add(24 * time.Hour),\n\t\t\t\t\tLastActivity: now.Add(-10 * time.Minute),\n\t\t\t\t},\n\t\t\t},\n\t\t\tCreatedAt:      now.Add(-30 * time.Minute),\n\t\t\tLastActivity:   now.Add(-10 * time.Minute),\n\t\t\tIPAddress:      \"127.0.0.1\",\n\t\t\tUserAgent:      \"test\",\n\t\t\tAbsoluteExpiry: now.Add(24 * time.Hour),\n\t\t\tIdleExpiry:     now.Add(50 * time.Minute),\n\t\t}\n\t\trequire.NoError(t, s.storage.CreateAuthSession(ctx, existingSession))\n\n\t\tw := httptest.NewRecorder()\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\n\t\tauthReq := storage.AuthRequest{\n\t\t\tID:          \"auth-2\",\n\t\t\tClientID:    \"client-2\",\n\t\t\tClaims:      storage.Claims{UserID: \"user-1\"},\n\t\t\tConnectorID: \"mock\",\n\t\t}\n\n\t\terr := s.createOrUpdateAuthSession(ctx, r, w, authReq, false)\n\t\trequire.NoError(t, err)\n\n\t\t// Cookie should be set with existing nonce.\n\t\tcookies := w.Result().Cookies()\n\t\trequire.Len(t, cookies, 1)\n\t\t_, _, gotNonce, err := parseSessionCookie(cookies[0].Value)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, nonce, gotNonce)\n\n\t\t// Session should have both clients.\n\t\tsession, err := s.storage.GetAuthSession(ctx, \"user-1\", \"mock\")\n\t\trequire.NoError(t, err)\n\t\tassert.Len(t, session.ClientStates, 2)\n\t\tassert.Contains(t, session.ClientStates, \"client-1\")\n\t\tassert.Contains(t, session.ClientStates, \"client-2\")\n\t})\n\n\tt.Run(\"nil session config\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.sessionConfig = nil\n\t\tw := httptest.NewRecorder()\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\n\t\terr := s.createOrUpdateAuthSession(ctx, r, w, storage.AuthRequest{}, false)\n\t\tassert.NoError(t, err)\n\t\tassert.Empty(t, w.Result().Cookies())\n\t})\n}\n\n// setupSessionLoginFixture creates the necessary storage objects for trySessionLogin tests.\nfunc setupSessionLoginFixture(t *testing.T, s *Server) storage.AuthRequest {\n\tt.Helper()\n\tctx := t.Context()\n\tnow := s.now()\n\n\trequire.NoError(t, s.storage.CreateAuthSession(ctx, storage.AuthSession{\n\t\tUserID:      \"user-1\",\n\t\tConnectorID: \"mock\",\n\t\tNonce:       \"test-nonce\",\n\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\"client-1\": {\n\t\t\t\tActive:       true,\n\t\t\t\tExpiresAt:    now.Add(24 * time.Hour),\n\t\t\t\tLastActivity: now.Add(-1 * time.Minute),\n\t\t\t},\n\t\t},\n\t\tCreatedAt:      now.Add(-30 * time.Minute),\n\t\tLastActivity:   now.Add(-1 * time.Minute),\n\t\tIPAddress:      \"127.0.0.1\",\n\t\tUserAgent:      \"test\",\n\t\tAbsoluteExpiry: now.Add(24 * time.Hour),\n\t\tIdleExpiry:     now.Add(59 * time.Minute),\n\t}))\n\n\trequire.NoError(t, s.storage.CreateUserIdentity(ctx, storage.UserIdentity{\n\t\tUserID:      \"user-1\",\n\t\tConnectorID: \"mock\",\n\t\tClaims: storage.Claims{\n\t\t\tUserID:   \"user-1\",\n\t\t\tUsername: \"testuser\",\n\t\t\tEmail:    \"test@example.com\",\n\t\t},\n\t\tConsents:  map[string][]string{\"client-1\": {\"openid\", \"email\"}},\n\t\tCreatedAt: now.Add(-1 * time.Hour),\n\t\tLastLogin: now.Add(-30 * time.Minute),\n\t}))\n\n\tauthReq := storage.AuthRequest{\n\t\tID:          storage.NewID(),\n\t\tClientID:    \"client-1\",\n\t\tConnectorID: \"mock\",\n\t\tScopes:      []string{\"openid\", \"email\"},\n\t\tRedirectURI: \"http://localhost/callback\",\n\t\tMaxAge:      -1,\n\t\tHMACKey:     storage.NewHMACKey(crypto.SHA256),\n\t\tExpiry:      now.Add(10 * time.Minute),\n\t}\n\trequire.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))\n\treturn authReq\n}\n\nfunc sessionCookieRequest(userID, connectorID, nonce string) *http.Request {\n\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(userID, connectorID, nonce)})\n\treturn r\n}\n\nfunc TestTrySessionLogin(t *testing.T) {\n\tctx := t.Context()\n\n\tt.Run(\"no session\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tauthReq := storage.AuthRequest{ConnectorID: \"mock\"}\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.False(t, ok)\n\t})\n\n\tt.Run(\"successful login with skipApproval\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = true\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.True(t, ok)\n\t})\n\n\tt.Run(\"successful login redirects to approval\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = false\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\t\tauthReq.ForceApprovalPrompt = true\n\n\t\trequire.NoError(t, s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) {\n\t\t\ta.ForceApprovalPrompt = true\n\t\t\treturn a, nil\n\t\t}))\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\tredirectURL, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.True(t, ok)\n\t\tassert.Contains(t, redirectURL, \"/approval\")\n\t\tassert.Contains(t, redirectURL, \"req=\"+authReq.ID)\n\t})\n\n\tt.Run(\"skips approval when consent already given\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = false\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.True(t, ok)\n\t})\n\n\tt.Run(\"connector mismatch returns false\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\t\tauthReq.ConnectorID = \"github\"\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.False(t, ok)\n\t})\n\n\tt.Run(\"no client state for requested client\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\t\tauthReq.ClientID = \"unknown-client\"\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.False(t, ok)\n\t})\n\n\tt.Run(\"expired client state returns false\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\n\t\trequire.NoError(t, s.storage.CreateAuthSession(t.Context(), storage.AuthSession{\n\t\t\tUserID:      \"user-exp\",\n\t\t\tConnectorID: \"mock\",\n\t\t\tNonce:       \"nonce-exp\",\n\t\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\t\"client-1\": {\n\t\t\t\t\tActive:    true,\n\t\t\t\t\tExpiresAt: now.Add(-1 * time.Hour),\n\t\t\t\t},\n\t\t\t},\n\t\t\tCreatedAt:      now.Add(-2 * time.Hour),\n\t\t\tLastActivity:   now.Add(-1 * time.Minute),\n\t\t\tAbsoluteExpiry: now.Add(22 * time.Hour),\n\t\t\tIdleExpiry:     now.Add(59 * time.Minute),\n\t\t}))\n\n\t\trequire.NoError(t, s.storage.CreateUserIdentity(t.Context(), storage.UserIdentity{\n\t\t\tUserID:      \"user-exp\",\n\t\t\tConnectorID: \"mock\",\n\t\t\tClaims:      storage.Claims{UserID: \"user-exp\"},\n\t\t\tConsents:    make(map[string][]string),\n\t\t\tCreatedAt:   now,\n\t\t\tLastLogin:   now,\n\t\t}))\n\n\t\tauthReq := storage.AuthRequest{\n\t\t\tID:          storage.NewID(),\n\t\t\tClientID:    \"client-1\",\n\t\t\tConnectorID: \"mock\",\n\t\t\tMaxAge:      -1,\n\t\t\tHMACKey:     storage.NewHMACKey(crypto.SHA256),\n\t\t\tExpiry:      now.Add(10 * time.Minute),\n\t\t}\n\t\trequire.NoError(t, s.storage.CreateAuthRequest(t.Context(), authReq))\n\n\t\tr := sessionCookieRequest(\"user-exp\", \"mock\", \"nonce-exp\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.False(t, ok)\n\t})\n\n\tt.Run(\"updates session activity\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = true\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\trequire.True(t, ok)\n\n\t\tsession, err := s.storage.GetAuthSession(ctx, \"user-1\", \"mock\")\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, s.now(), session.LastActivity)\n\t})\n}\n\n// setupSessionWithIdentity creates an AuthSession, UserIdentity, and AuthRequest in storage\n// for use in trySessionLogin tests. Returns the authReq.\nfunc setupSessionWithIdentity(t *testing.T, s *Server, now time.Time, lastLogin time.Time) storage.AuthRequest {\n\tt.Helper()\n\tctx := t.Context()\n\tnonce := \"test-nonce\"\n\n\tsession := storage.AuthSession{\n\t\tUserID:      \"user-1\",\n\t\tConnectorID: \"mock\",\n\t\tNonce:       nonce,\n\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\"client-1\": {\n\t\t\t\tActive:       true,\n\t\t\t\tExpiresAt:    now.Add(24 * time.Hour),\n\t\t\t\tLastActivity: now.Add(-1 * time.Minute),\n\t\t\t},\n\t\t},\n\t\tCreatedAt:    now.Add(-30 * time.Minute),\n\t\tLastActivity: now.Add(-1 * time.Minute),\n\t\tIPAddress:    \"127.0.0.1\",\n\t\tUserAgent:    \"test\",\n\t}\n\trequire.NoError(t, s.storage.CreateAuthSession(ctx, session))\n\n\tui := storage.UserIdentity{\n\t\tUserID:      \"user-1\",\n\t\tConnectorID: \"mock\",\n\t\tClaims: storage.Claims{\n\t\t\tUserID:   \"user-1\",\n\t\t\tUsername: \"testuser\",\n\t\t\tEmail:    \"test@example.com\",\n\t\t},\n\t\tConsents:  make(map[string][]string),\n\t\tCreatedAt: now.Add(-1 * time.Hour),\n\t\tLastLogin: lastLogin,\n\t}\n\trequire.NoError(t, s.storage.CreateUserIdentity(ctx, ui))\n\n\tauthReq := storage.AuthRequest{\n\t\tID:          storage.NewID(),\n\t\tClientID:    \"client-1\",\n\t\tConnectorID: \"mock\",\n\t\tScopes:      []string{\"openid\"},\n\t\tRedirectURI: \"http://localhost/callback\",\n\t\tMaxAge:      -1,\n\t\tHMACKey:     storage.NewHMACKey(crypto.SHA256),\n\t\tExpiry:      now.Add(10 * time.Minute),\n\t}\n\trequire.NoError(t, s.storage.CreateAuthRequest(ctx, authReq))\n\n\treturn authReq\n}\n\nfunc TestTrySessionLogin_MaxAge(t *testing.T) {\n\tctx := t.Context()\n\n\tt.Run(\"max_age not specified, session reused\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\n\t\tauthReq := setupSessionWithIdentity(t, s, now, now.Add(-2*time.Hour))\n\t\tauthReq.MaxAge = -1 // not specified\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user-1\", \"mock\", \"test-nonce\")})\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.True(t, ok, \"session should be reused when max_age is not specified\")\n\t})\n\n\tt.Run(\"max_age satisfied, session reused\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\n\t\t// User logged in 10 minutes ago, max_age=3600 (1 hour)\n\t\tauthReq := setupSessionWithIdentity(t, s, now, now.Add(-10*time.Minute))\n\t\tauthReq.MaxAge = 3600\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user-1\", \"mock\", \"test-nonce\")})\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.True(t, ok, \"session should be reused when max_age is satisfied\")\n\t})\n\n\tt.Run(\"max_age exceeded, force re-auth\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\n\t\t// User logged in 2 hours ago, max_age=3600 (1 hour)\n\t\tauthReq := setupSessionWithIdentity(t, s, now, now.Add(-2*time.Hour))\n\t\tauthReq.MaxAge = 3600\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user-1\", \"mock\", \"test-nonce\")})\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.False(t, ok, \"session should NOT be reused when max_age is exceeded\")\n\t})\n\n\tt.Run(\"max_age=0, always force re-auth\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\tnow := s.now()\n\n\t\t// User logged in 1 second ago, max_age=0\n\t\tauthReq := setupSessionWithIdentity(t, s, now, now.Add(-1*time.Second))\n\t\tauthReq.MaxAge = 0\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user-1\", \"mock\", \"test-nonce\")})\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\tassert.False(t, ok, \"max_age=0 should always force re-authentication\")\n\t})\n\n\tt.Run(\"auth_time is set from UserIdentity.LastLogin\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = false\n\t\tnow := s.now()\n\t\tlastLogin := now.Add(-10 * time.Minute)\n\n\t\tauthReq := setupSessionWithIdentity(t, s, now, lastLogin)\n\t\tauthReq.ForceApprovalPrompt = true // force approval so AuthRequest is not deleted\n\n\t\trequire.NoError(t, s.storage.UpdateAuthRequest(ctx, authReq.ID, func(a storage.AuthRequest) (storage.AuthRequest, error) {\n\t\t\ta.ForceApprovalPrompt = true\n\t\t\treturn a, nil\n\t\t}))\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tr.AddCookie(&http.Cookie{Name: \"dex_session\", Value: sessionCookieValue(\"user-1\", \"mock\", \"test-nonce\")})\n\t\tw := httptest.NewRecorder()\n\n\t\tredirectURL, ok := s.trySessionLogin(ctx, r, w, &authReq)\n\t\trequire.True(t, ok)\n\t\tassert.Contains(t, redirectURL, \"/approval\")\n\n\t\t// Verify AuthTime was set on the auth request.\n\t\tupdated, err := s.storage.GetAuthRequest(ctx, authReq.ID)\n\t\trequire.NoError(t, err)\n\t\tassert.Equal(t, lastLogin.Unix(), updated.AuthTime.Unix())\n\t})\n}\n\nfunc TestTrySessionLoginWithSession_IDTokenHint(t *testing.T) {\n\tctx := t.Context()\n\n\t// genSubject(\"user-1\", \"mock\") produces a deterministic subject string.\n\thintSubjectForUser1Mock, err := genSubject(\"user-1\", \"mock\")\n\trequire.NoError(t, err)\n\n\thintSubjectOther, err := genSubject(\"other-user\", \"mock\")\n\trequire.NoError(t, err)\n\n\tt.Run(\"hint matches session user - session login succeeds\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = true\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\n\t\tsession := s.getValidAuthSession(ctx, httptest.NewRecorder(), sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\"), &authReq)\n\t\trequire.NotNil(t, session)\n\n\t\t// Verify hint matches.\n\t\tassert.True(t, sessionMatchesHint(session, hintSubjectForUser1Mock))\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, session)\n\t\tassert.True(t, ok)\n\t})\n\n\tt.Run(\"hint does not match session user - session invalidated\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = true\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\n\t\tsession := s.getValidAuthSession(ctx, httptest.NewRecorder(), sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\"), &authReq)\n\t\trequire.NotNil(t, session)\n\n\t\t// Verify hint does NOT match.\n\t\tassert.False(t, sessionMatchesHint(session, hintSubjectOther))\n\n\t\t// Simulating the hint mismatch logic from handleConnectorLogin:\n\t\t// when hint doesn't match and prompt is not none, session is set to nil.\n\t\tvar nilSession *storage.AuthSession\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, nilSession)\n\t\tassert.False(t, ok, \"session login should fail when session is invalidated due to hint mismatch\")\n\t})\n\n\tt.Run(\"hint with no session - trySessionLoginWithSession returns false\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = true\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\n\t\tr := httptest.NewRequest(http.MethodGet, \"/\", nil)\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, nil)\n\t\tassert.False(t, ok)\n\t})\n\n\tt.Run(\"no hint - unchanged behavior\", func(t *testing.T) {\n\t\ts := newTestSessionServer(t)\n\t\ts.skipApproval = true\n\t\tauthReq := setupSessionLoginFixture(t, s)\n\n\t\tsession := s.getValidAuthSession(ctx, httptest.NewRecorder(), sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\"), &authReq)\n\t\trequire.NotNil(t, session)\n\n\t\tr := sessionCookieRequest(\"user-1\", \"mock\", \"test-nonce\")\n\t\tw := httptest.NewRecorder()\n\n\t\t_, ok := s.trySessionLoginWithSession(ctx, r, w, &authReq, session)\n\t\tassert.True(t, ok)\n\t})\n}\n\nfunc TestParseAuthRequest_PromptAndMaxAge(t *testing.T) {\n\tt.Run(\"prompt=consent sets ForceApprovalPrompt\", func(t *testing.T) {\n\t\tauthReq := storage.AuthRequest{\n\t\t\tPrompt:              \"consent\",\n\t\t\tForceApprovalPrompt: true,\n\t\t}\n\t\tassert.True(t, authReq.ForceApprovalPrompt)\n\t\tassert.Equal(t, \"consent\", authReq.Prompt)\n\t})\n\n\tt.Run(\"max_age default is -1\", func(t *testing.T) {\n\t\tauthReq := storage.AuthRequest{\n\t\t\tMaxAge: -1,\n\t\t}\n\t\tassert.Equal(t, -1, authReq.MaxAge)\n\t})\n}\n"
  },
  {
    "path": "server/signer/local.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// LocalConfig holds configuration for the local signer.\ntype LocalConfig struct {\n\t// KeysRotationPeriod defines the duration of time after which the signing keys will be rotated.\n\tKeysRotationPeriod string `json:\"keysRotationPeriod\"`\n}\n\n// Open creates a new local signer.\nfunc (c *LocalConfig) Open(_ context.Context, s storage.Storage, idTokenValidFor time.Duration, now func() time.Time, logger *slog.Logger) (Signer, error) {\n\trotateKeysAfter, err := time.ParseDuration(c.KeysRotationPeriod)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid config value %q for local signer rotation period: %v\", c.KeysRotationPeriod, err)\n\t}\n\n\tstrategy := defaultRotationStrategy(rotateKeysAfter, idTokenValidFor)\n\tr := &keyRotator{s, strategy, now, logger}\n\treturn &localSigner{\n\t\tstorage: s,\n\t\trotator: r,\n\t\tlogger:  logger,\n\t}, nil\n}\n\n// localSigner signs payloads using keys stored in the Dex storage.\n// It manages key rotation and storage using the existing keyRotator logic.\ntype localSigner struct {\n\tstorage storage.Storage\n\trotator *keyRotator\n\tlogger  *slog.Logger\n}\n\n// Start begins key rotation in a new goroutine, closing once the context is canceled.\n//\n// The method blocks until after the first attempt to rotate keys has completed. That way\n// healthy storages will return from this call with valid keys.\nfunc (l *localSigner) Start(ctx context.Context) {\n\t// Try to rotate immediately so properly configured storages will have keys.\n\tif err := l.rotator.rotate(); err != nil {\n\t\tif err == errAlreadyRotated {\n\t\t\tl.logger.Info(\"key rotation not needed\", \"err\", err)\n\t\t} else {\n\t\t\tl.logger.Error(\"failed to rotate keys\", \"err\", err)\n\t\t}\n\t}\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn\n\t\t\tcase <-time.After(time.Second * 30):\n\t\t\t\tif err := l.rotator.rotate(); err != nil {\n\t\t\t\t\tl.logger.Error(\"failed to rotate keys\", \"err\", err)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}()\n}\n\nfunc (l *localSigner) Sign(ctx context.Context, payload []byte) (string, error) {\n\tkeys, err := l.storage.GetKeys(ctx)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get keys: %v\", err)\n\t}\n\n\tsigningKey := keys.SigningKey\n\tif signingKey == nil {\n\t\treturn \"\", fmt.Errorf(\"no key to sign payload with\")\n\t}\n\tsigningAlg, err := signatureAlgorithm(signingKey)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn signPayload(signingKey, signingAlg, payload)\n}\n\nfunc (l *localSigner) ValidationKeys(ctx context.Context) ([]*jose.JSONWebKey, error) {\n\tkeys, err := l.storage.GetKeys(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get keys: %v\", err)\n\t}\n\n\tif keys.SigningKeyPub == nil {\n\t\treturn nil, fmt.Errorf(\"no public keys found\")\n\t}\n\n\tjwks := make([]*jose.JSONWebKey, len(keys.VerificationKeys)+1)\n\tjwks[0] = keys.SigningKeyPub\n\tfor i, verificationKey := range keys.VerificationKeys {\n\t\tjwks[i+1] = verificationKey.PublicKey\n\t}\n\treturn jwks, nil\n}\n\nfunc (l *localSigner) Algorithm(_ context.Context) (jose.SignatureAlgorithm, error) {\n\t// Local signer always uses RSA keys (see rotationStrategy.key).\n\t// TODO(nabokihms): add support for other key types and algorithms in the future.\n\treturn jose.RS256, nil\n}\n"
  },
  {
    "path": "server/signer/local_test.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/storage/memory\"\n)\n\nfunc newTestLocalSigner(t *testing.T) *localSigner {\n\tt.Helper()\n\n\tlogger := slog.New(slog.DiscardHandler)\n\ts := memory.New(logger)\n\tr := &keyRotator{\n\t\tStorage:  s,\n\t\tstrategy: defaultRotationStrategy(time.Hour, time.Hour),\n\t\tnow:      time.Now,\n\t\tlogger:   logger,\n\t}\n\n\treturn &localSigner{\n\t\tstorage: s,\n\t\trotator: r,\n\t\tlogger:  logger,\n\t}\n}\n\nfunc TestLocalSignerAlgorithm(t *testing.T) {\n\tls := newTestLocalSigner(t)\n\n\t// Algorithm should return RS256 even before keys are rotated (empty storage).\n\talg, err := ls.Algorithm(context.Background())\n\trequire.NoError(t, err)\n\tassert.Equal(t, jose.RS256, alg)\n}\n\nfunc TestLocalSignerSignAndValidate(t *testing.T) {\n\tls := newTestLocalSigner(t)\n\tctx := context.Background()\n\n\t// Rotate keys so we have a signing key.\n\trequire.NoError(t, ls.rotator.rotate())\n\n\tpayload := []byte(`{\"sub\":\"test-user\"}`)\n\tsigned, err := ls.Sign(ctx, payload)\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, signed)\n\n\t// Validation keys should be available.\n\tkeys, err := ls.ValidationKeys(ctx)\n\trequire.NoError(t, err)\n\tassert.NotEmpty(t, keys)\n}\n"
  },
  {
    "path": "server/signer/mock.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/hex\"\n\t\"io\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n)\n\n// MockConfig creates a mock signer with a static key for testing.\ntype MockConfig struct {\n\tKey *rsa.PrivateKey\n}\n\n// Open creates a new mock signer.\nfunc (c *MockConfig) Open(_ context.Context) (Signer, error) {\n\tif c.Key == nil {\n\t\t// Generate a new key if not provided\n\t\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tc.Key = key\n\t}\n\n\t// Generate a key ID\n\tb := make([]byte, 20)\n\tif _, err := io.ReadFull(rand.Reader, b); err != nil {\n\t\tpanic(err)\n\t}\n\tkeyID := hex.EncodeToString(b)\n\n\treturn &mockSigner{\n\t\tkey: &jose.JSONWebKey{\n\t\t\tKey:       c.Key,\n\t\t\tKeyID:     keyID,\n\t\t\tAlgorithm: \"RS256\",\n\t\t\tUse:       \"sig\",\n\t\t},\n\t\tpubKey: &jose.JSONWebKey{\n\t\t\tKey:       c.Key.Public(),\n\t\t\tKeyID:     keyID,\n\t\t\tAlgorithm: \"RS256\",\n\t\t\tUse:       \"sig\",\n\t\t},\n\t}, nil\n}\n\n// mockSigner is a simple signer that uses a static RSA key for testing.\ntype mockSigner struct {\n\tkey    *jose.JSONWebKey\n\tpubKey *jose.JSONWebKey\n}\n\nfunc (m *mockSigner) Sign(_ context.Context, payload []byte) (string, error) {\n\treturn signPayload(m.key, jose.RS256, payload)\n}\n\nfunc (m *mockSigner) ValidationKeys(_ context.Context) ([]*jose.JSONWebKey, error) {\n\treturn []*jose.JSONWebKey{m.pubKey}, nil\n}\n\nfunc (m *mockSigner) Algorithm(_ context.Context) (jose.SignatureAlgorithm, error) {\n\treturn jose.RS256, nil\n}\n\nfunc (m *mockSigner) Start(_ context.Context) {\n\t// Nothing to do for mock signer\n}\n\n// NewMockSigner creates a mock signer with the provided key for testing.\n// If key is nil, a new one will be generated.\nfunc NewMockSigner(key *rsa.PrivateKey) (Signer, error) {\n\treturn (&MockConfig{Key: key}).Open(context.Background())\n}\n"
  },
  {
    "path": "server/signer/rotation.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"log/slog\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\nvar errAlreadyRotated = errors.New(\"keys already rotated by another server instance\")\n\n// rotationStrategy describes a strategy for generating cryptographic keys, how\n// often to rotate them, and how long they can validate signatures after rotation.\ntype rotationStrategy struct {\n\t// Time between rotations.\n\trotationFrequency time.Duration\n\n\t// After being rotated how long should the key be kept around for validating\n\t// signatures?\n\tidTokenValidFor time.Duration\n\n\t// Keys are always RSA keys. Though cryptopasta recommends ECDSA keys, not every\n\t// client may support these (e.g. github.com/coreos/go-oidc/oidc).\n\tkey func() (*rsa.PrivateKey, error)\n}\n\n// defaultRotationStrategy returns a strategy which rotates keys every provided period,\n// holding onto the public parts for some specified amount of time.\nfunc defaultRotationStrategy(rotationFrequency, idTokenValidFor time.Duration) rotationStrategy {\n\treturn rotationStrategy{\n\t\trotationFrequency: rotationFrequency,\n\t\tidTokenValidFor:   idTokenValidFor,\n\t\tkey: func() (*rsa.PrivateKey, error) {\n\t\t\treturn rsa.GenerateKey(rand.Reader, 2048)\n\t\t},\n\t}\n}\n\ntype keyRotator struct {\n\tstorage.Storage\n\n\tstrategy rotationStrategy\n\tnow      func() time.Time\n\n\tlogger *slog.Logger\n}\n\nfunc (k keyRotator) rotate() error {\n\tkeys, err := k.GetKeys(context.Background())\n\tif err != nil && err != storage.ErrNotFound {\n\t\treturn fmt.Errorf(\"get keys: %v\", err)\n\t}\n\tif k.now().Before(keys.NextRotation) {\n\t\treturn nil\n\t}\n\tk.logger.Info(\"keys expired, rotating\")\n\n\t// Generate the key outside of a storage transaction.\n\tkey, err := k.strategy.key()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"generate key: %v\", err)\n\t}\n\tb := make([]byte, 20)\n\tif _, err := io.ReadFull(rand.Reader, b); err != nil {\n\t\tpanic(err)\n\t}\n\tkeyID := hex.EncodeToString(b)\n\tpriv := &jose.JSONWebKey{\n\t\tKey:       key,\n\t\tKeyID:     keyID,\n\t\tAlgorithm: \"RS256\",\n\t\tUse:       \"sig\",\n\t}\n\tpub := &jose.JSONWebKey{\n\t\tKey:       key.Public(),\n\t\tKeyID:     keyID,\n\t\tAlgorithm: \"RS256\",\n\t\tUse:       \"sig\",\n\t}\n\n\tvar nextRotation time.Time\n\terr = k.Storage.UpdateKeys(context.Background(), func(keys storage.Keys) (storage.Keys, error) {\n\t\ttNow := k.now()\n\n\t\t// if you are running multiple instances of dex, another instance\n\t\t// could have already rotated the keys.\n\t\tif tNow.Before(keys.NextRotation) {\n\t\t\treturn storage.Keys{}, errAlreadyRotated\n\t\t}\n\n\t\texpired := func(key storage.VerificationKey) bool {\n\t\t\treturn tNow.After(key.Expiry)\n\t\t}\n\n\t\t// Remove any verification keys that have expired.\n\t\ti := 0\n\t\tfor _, key := range keys.VerificationKeys {\n\t\t\tif !expired(key) {\n\t\t\t\tkeys.VerificationKeys[i] = key\n\t\t\t\ti++\n\t\t\t}\n\t\t}\n\t\tkeys.VerificationKeys = keys.VerificationKeys[:i]\n\n\t\tif keys.SigningKeyPub != nil {\n\t\t\t// Move current signing key to a verification only key, throwing\n\t\t\t// away the private part.\n\t\t\tverificationKey := storage.VerificationKey{\n\t\t\t\tPublicKey: keys.SigningKeyPub,\n\t\t\t\t// After demoting the signing key, keep the token around for at least\n\t\t\t\t// the amount of time an ID Token is valid for. This ensures the\n\t\t\t\t// verification key won't expire until all ID Tokens it's signed\n\t\t\t\t// expired as well.\n\t\t\t\tExpiry: tNow.Add(k.strategy.idTokenValidFor),\n\t\t\t}\n\t\t\tkeys.VerificationKeys = append(keys.VerificationKeys, verificationKey)\n\t\t}\n\n\t\tnextRotation = k.now().Add(k.strategy.rotationFrequency)\n\t\tkeys.SigningKey = priv\n\t\tkeys.SigningKeyPub = pub\n\t\tkeys.NextRotation = nextRotation\n\t\treturn keys, nil\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tk.logger.Info(\"keys rotated\", \"next_rotation\", nextRotation)\n\treturn nil\n}\n"
  },
  {
    "path": "server/signer/rotation_test.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/memory\"\n)\n\nfunc signingKeyID(t *testing.T, s storage.Storage) string {\n\tkeys, err := s.GetKeys(context.TODO())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\treturn keys.SigningKey.KeyID\n}\n\nfunc verificationKeyIDs(t *testing.T, s storage.Storage) (ids []string) {\n\tkeys, err := s.GetKeys(context.TODO())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tfor _, key := range keys.VerificationKeys {\n\t\tids = append(ids, key.PublicKey.KeyID)\n\t}\n\treturn ids\n}\n\n// slicesEq compare two string slices without modifying the ordering\n// of the slices.\nfunc slicesEq(s1, s2 []string) bool {\n\tif len(s1) != len(s2) {\n\t\treturn false\n\t}\n\n\tcp := func(s []string) []string {\n\t\tc := make([]string, len(s))\n\t\tcopy(c, s)\n\t\treturn c\n\t}\n\n\tcp1 := cp(s1)\n\tcp2 := cp(s2)\n\tsort.Strings(cp1)\n\tsort.Strings(cp2)\n\n\tfor i, el := range cp1 {\n\t\tif el != cp2[i] {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc TestKeyRotator(t *testing.T) {\n\tnow := time.Now()\n\n\tdelta := time.Millisecond\n\trotationFrequency := time.Second * 5\n\tvalidFor := time.Second * 21\n\n\t// Only the last 5 verification keys are expected to be kept around.\n\tmaxVerificationKeys := 5\n\n\tl := slog.New(slog.DiscardHandler)\n\n\tr := &keyRotator{\n\t\tStorage:  memory.New(l),\n\t\tstrategy: defaultRotationStrategy(rotationFrequency, validFor),\n\t\tnow:      func() time.Time { return now },\n\t\tlogger:   l,\n\t}\n\n\tvar expVerificationKeys []string\n\n\tfor i := 0; i < 10; i++ {\n\t\tnow = now.Add(rotationFrequency + delta)\n\t\tif err := r.rotate(); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tgot := verificationKeyIDs(t, r.Storage)\n\n\t\tif !slicesEq(expVerificationKeys, got) {\n\t\t\tt.Errorf(\"after %d rotation, expected verification keys %q, got %q\", i+1, expVerificationKeys, got)\n\t\t}\n\n\t\texpVerificationKeys = append(expVerificationKeys, signingKeyID(t, r.Storage))\n\t\tif n := len(expVerificationKeys); n > maxVerificationKeys {\n\t\t\texpVerificationKeys = expVerificationKeys[n-maxVerificationKeys:]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/signer/signer.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n)\n\n// Signer is an interface for signing payloads and retrieving validation keys.\ntype Signer interface {\n\t// Sign signs the provided payload.\n\tSign(ctx context.Context, payload []byte) (string, error)\n\t// ValidationKeys returns the current public keys used for signature validation.\n\tValidationKeys(ctx context.Context) ([]*jose.JSONWebKey, error)\n\t// Algorithm returns the signing algorithm used by this signer.\n\tAlgorithm(ctx context.Context) (jose.SignatureAlgorithm, error)\n\t// Start starts any background tasks required by the signer (e.g., key rotation).\n\tStart(ctx context.Context)\n}\n"
  },
  {
    "path": "server/signer/utils.go",
    "content": "package signer\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rsa\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n)\n\nfunc signatureAlgorithm(jwk *jose.JSONWebKey) (alg jose.SignatureAlgorithm, err error) {\n\tif jwk.Key == nil {\n\t\treturn alg, errors.New(\"no signing key\")\n\t}\n\tswitch key := jwk.Key.(type) {\n\tcase *rsa.PrivateKey:\n\t\t// Because OIDC mandates that we support RS256, we always return that\n\t\t// value. In the future, we might want to make this configurable on a\n\t\t// per client basis. For example allowing PS256 or ECDSA variants.\n\t\t//\n\t\t// See https://github.com/dexidp/dex/issues/692\n\t\treturn jose.RS256, nil\n\tcase *ecdsa.PrivateKey:\n\t\t// We don't actually support ECDSA keys yet, but they're tested for\n\t\t// in case we want to in the future.\n\t\t//\n\t\t// These values are prescribed depending on the ECDSA key type. We\n\t\t// can't return different values.\n\t\tswitch key.Params() {\n\t\tcase elliptic.P256().Params():\n\t\t\treturn jose.ES256, nil\n\t\tcase elliptic.P384().Params():\n\t\t\treturn jose.ES384, nil\n\t\tcase elliptic.P521().Params():\n\t\t\treturn jose.ES512, nil\n\t\tdefault:\n\t\t\treturn alg, errors.New(\"unsupported ecdsa curve\")\n\t\t}\n\tdefault:\n\t\treturn alg, fmt.Errorf(\"unsupported signing key type %T\", key)\n\t}\n}\n\nfunc signPayload(key *jose.JSONWebKey, alg jose.SignatureAlgorithm, payload []byte) (jws string, err error) {\n\tsigningKey := jose.SigningKey{Key: key, Algorithm: alg}\n\n\tsigner, err := jose.NewSigner(signingKey, &jose.SignerOptions{})\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"new signer: %v\", err)\n\t}\n\tsignature, err := signer.Sign(payload)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"signing payload: %v\", err)\n\t}\n\treturn signature.CompactSerialize()\n}\n"
  },
  {
    "path": "server/signer/vault.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"crypto\"\n\t\"crypto/ecdsa\"\n\t\"crypto/ed25519\"\n\t\"crypto/elliptic\"\n\t\"crypto/rsa\"\n\t\"crypto/sha256\"\n\t\"crypto/sha512\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"encoding/pem\"\n\t\"fmt\"\n\t\"hash\"\n\t\"os\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\tvault \"github.com/openbao/openbao/api/v2\"\n)\n\n// VaultConfig holds configuration for the Vault signer.\ntype VaultConfig struct {\n\tAddr    string `json:\"addr\"`\n\tToken   string `json:\"token\"`\n\tKeyName string `json:\"keyName\"`\n}\n\n// UnmarshalJSON unmarshals a VaultConfig and applies environment variables.\n// If Addr or Token are not provided in the config, they are read from VAULT_ADDR\n// and VAULT_TOKEN environment variables respectively.\nfunc (c *VaultConfig) UnmarshalJSON(data []byte) error {\n\ttype Alias VaultConfig\n\taux := &struct {\n\t\t*Alias\n\t}{\n\t\tAlias: (*Alias)(c),\n\t}\n\n\tif err := json.Unmarshal(data, &aux); err != nil {\n\t\treturn err\n\t}\n\n\t// Apply environment variables if config values are empty\n\tif c.Addr == \"\" {\n\t\tif addr := os.Getenv(\"VAULT_ADDR\"); addr != \"\" {\n\t\t\tc.Addr = addr\n\t\t}\n\t}\n\n\tif c.Token == \"\" {\n\t\tif token := os.Getenv(\"VAULT_TOKEN\"); token != \"\" {\n\t\t\tc.Token = token\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Open creates a new Vault signer.\nfunc (c *VaultConfig) Open(_ context.Context) (Signer, error) {\n\treturn newVaultSigner(*c)\n}\n\n// vaultSigner signs payloads using HashiCorp Vault's Transit backend.\ntype vaultSigner struct {\n\tclient  *vault.Client\n\tkeyName string\n}\n\n// newVaultSigner creates a new Vault signer that uses Transit backend for signing.\nfunc newVaultSigner(c VaultConfig) (*vaultSigner, error) {\n\tconfig := vault.DefaultConfig()\n\tconfig.Address = c.Addr\n\n\tclient, err := vault.NewClient(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create vault client: %v\", err)\n\t}\n\n\tif c.Token != \"\" {\n\t\tclient.SetToken(c.Token)\n\t}\n\n\treturn &vaultSigner{\n\t\tclient:  client,\n\t\tkeyName: c.KeyName,\n\t}, nil\n}\n\nfunc (v *vaultSigner) Start(_ context.Context) {\n\t// Vault signer does not need background rotation tasks\n}\n\nfunc (v *vaultSigner) Sign(ctx context.Context, payload []byte) (string, error) {\n\t// 1. Fetch keys to determine the key to use (latest version) and its ID.\n\tkeysMap, latestVersion, err := v.getTransitKeysMap(ctx)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get keys for signing context: %v\", err)\n\t}\n\n\t// Determine the key version and ID to use\n\t// We use the latest version by default\n\tsigningJWK, ok := keysMap[latestVersion]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"latest key version %d not found in public keys\", latestVersion)\n\t}\n\n\t// 2. Construct JWS Header and Payload first (Signing Input)\n\theader := map[string]interface{}{\n\t\t\"alg\": signingJWK.Algorithm,\n\t\t\"kid\": signingJWK.KeyID,\n\t}\n\n\theaderBytes, err := json.Marshal(header)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to marshal header: %v\", err)\n\t}\n\n\theaderB64 := base64.RawURLEncoding.EncodeToString(headerBytes)\n\tpayloadB64 := base64.RawURLEncoding.EncodeToString(payload)\n\n\t// The input to the signature is \"header.payload\"\n\tsigningInput := fmt.Sprintf(\"%s.%s\", headerB64, payloadB64)\n\n\t// 3. Sign the signingInput using Vault\n\tvar vaultInput string\n\tdata := map[string]interface{}{}\n\n\t// Determine Vault params based on JWS algorithm\n\tparams, err := getVaultParams(signingJWK.Algorithm)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Apply params to data map\n\tfor k, v := range params.extraParams {\n\t\tdata[k] = v\n\t}\n\n\t// Hash input if needed\n\tif params.hasher != nil {\n\t\tparams.hasher.Write([]byte(signingInput))\n\t\thash := params.hasher.Sum(nil)\n\t\tvaultInput = base64.StdEncoding.EncodeToString(hash)\n\t} else {\n\t\t// No pre-hashing (EdDSA)\n\t\tvaultInput = base64.StdEncoding.EncodeToString([]byte(signingInput))\n\t}\n\tdata[\"input\"] = vaultInput\n\n\tsignPath := fmt.Sprintf(\"transit/sign/%s\", v.keyName)\n\tsignSecret, err := v.client.Logical().WriteWithContext(ctx, signPath, data)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"vault sign: %v\", err)\n\t}\n\n\tsignatureString, ok := signSecret.Data[\"signature\"].(string)\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"vault response missing signature\")\n\t}\n\n\t// Parse vault signature: \"vault:v1:base64sig\"\n\tvar signatureB64 []byte\n\tif len(signatureString) > 8 && signatureString[:6] == \"vault:\" {\n\t\tparts := splitVaultSignature(signatureString)\n\t\tif len(parts) == 3 {\n\t\t\t// part 1 is \"vault\", part 2 is \"v1\", part 3 is signature\n\t\t\t// The signature is already base64 encoded, decoding it is not needed and\n\t\t\t// will make the code failing.\n\t\t\tsignatureB64 = []byte(parts[2])\n\t\t}\n\t} else {\n\t\treturn \"\", fmt.Errorf(\"unexpected signature format: %s\", signatureString)\n\t}\n\n\treturn fmt.Sprintf(\"%s.%s.%s\", headerB64, payloadB64, signatureB64), nil\n}\n\nfunc (v *vaultSigner) ValidationKeys(ctx context.Context) ([]*jose.JSONWebKey, error) {\n\tkeysMap, _, err := v.getTransitKeysMap(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tkeys := make([]*jose.JSONWebKey, 0, len(keysMap))\n\tfor _, k := range keysMap {\n\t\tkeys = append(keys, k)\n\t}\n\treturn keys, nil\n}\n\n// getTransitKeysMap returns a map of key_version -> JWK and the latest version number\nfunc (v *vaultSigner) getTransitKeysMap(ctx context.Context) (map[int64]*jose.JSONWebKey, int64, error) {\n\tpath := fmt.Sprintf(\"transit/keys/%s\", v.keyName)\n\tsecret, err := v.client.Logical().ReadWithContext(ctx, path)\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"failed to read key from vault: %v\", err)\n\t}\n\tif secret == nil {\n\t\treturn nil, 0, fmt.Errorf(\"key %q not found in vault\", v.keyName)\n\t}\n\n\tlatestVersion, ok := secret.Data[\"latest_version\"].(json.Number)\n\tif !ok {\n\t\t// Try float64 which is default for unmarshal interface{}\n\t\tif lv, ok := secret.Data[\"latest_version\"].(float64); ok {\n\t\t\tlatestVersion = json.Number(fmt.Sprintf(\"%d\", int(lv)))\n\t\t} else if lv, ok := secret.Data[\"latest_version\"].(int); ok {\n\t\t\tlatestVersion = json.Number(fmt.Sprintf(\"%d\", lv))\n\t\t}\n\t}\n\tlatestVerInt, err := latestVersion.Int64()\n\tif err != nil {\n\t\treturn nil, 0, fmt.Errorf(\"failed to get latest version: %v\", err)\n\t}\n\n\tkeysObj, ok := secret.Data[\"keys\"].(map[string]interface{})\n\tif !ok {\n\t\treturn nil, 0, fmt.Errorf(\"invalid response from vault\")\n\t}\n\n\tjwksMap := make(map[int64]*jose.JSONWebKey)\n\n\tfor verStr, data := range keysObj {\n\t\td, ok := data.(map[string]interface{})\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tvar ver int64\n\t\tfmt.Sscanf(verStr, \"%d\", &ver)\n\n\t\tpemStr, ok := d[\"public_key\"].(string)\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\n\t\tjwk, err := parsePEMToJWK(pemStr)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tjwksMap[ver] = jwk\n\t}\n\n\treturn jwksMap, latestVerInt, nil\n}\n\nfunc parsePEMToJWK(pemStr string) (*jose.JSONWebKey, error) {\n\tblock, _ := pem.Decode([]byte(pemStr))\n\tif block == nil {\n\t\t// OpenBao may return ED25519 keys as raw base64-encoded strings instead of PEM\n\t\t// Try to decode as raw base64 ED25519 key\n\t\tkeyBytes, err := base64.StdEncoding.DecodeString(pemStr)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to parse PEM block or base64: %v\", err)\n\t\t}\n\n\t\t// Check if it's a raw 32-byte ED25519 key\n\t\tvar ed25519Key ed25519.PublicKey\n\t\tif len(keyBytes) == 32 {\n\t\t\ted25519Key = ed25519.PublicKey(keyBytes)\n\t\t} else {\n\t\t\t// Try to parse as PKIX public key\n\t\t\tpub, err := x509.ParsePKIXPublicKey(keyBytes)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"failed to parse raw key: %v\", err)\n\t\t\t}\n\n\t\t\t// Create JWK for ED25519 key\n\t\t\tvar ok bool\n\t\t\ted25519Key, ok = pub.(ed25519.PublicKey)\n\t\t\tif !ok {\n\t\t\t\treturn nil, fmt.Errorf(\"expected ED25519 key, got %T\", pub)\n\t\t\t}\n\t\t}\n\n\t\tjwk := &jose.JSONWebKey{\n\t\t\tKey:       ed25519Key,\n\t\t\tAlgorithm: \"EdDSA\",\n\t\t\tUse:       \"sig\",\n\t\t}\n\n\t\tthumbprint, err := jwk.Thumbprint(crypto.SHA256)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tjwk.KeyID = base64.RawURLEncoding.EncodeToString(thumbprint)\n\n\t\treturn jwk, nil\n\t}\n\n\tpub, err := x509.ParsePKIXPublicKey(block.Bytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to parse public key: %v\", err)\n\t}\n\n\talg := \"\"\n\tswitch k := pub.(type) {\n\tcase *rsa.PublicKey:\n\t\talg = \"RS256\"\n\tcase *ecdsa.PublicKey:\n\t\tswitch k.Curve {\n\t\tcase elliptic.P256():\n\t\t\talg = \"ES256\"\n\t\tcase elliptic.P384():\n\t\t\talg = \"ES384\"\n\t\tcase elliptic.P521():\n\t\t\talg = \"ES512\"\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unsupported ECDSA curve\")\n\t\t}\n\tcase ed25519.PublicKey:\n\t\talg = \"EdDSA\"\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported key type %T\", pub)\n\t}\n\n\tjwk := &jose.JSONWebKey{\n\t\tKey:       pub,\n\t\tAlgorithm: alg,\n\t\tUse:       \"sig\",\n\t}\n\n\tthumbprint, err := jwk.Thumbprint(crypto.SHA256)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tjwk.KeyID = base64.RawURLEncoding.EncodeToString(thumbprint)\n\n\treturn jwk, nil\n}\n\nfunc splitVaultSignature(sig string) []string {\n\t// Basic split implementation\n\t// \"vault:v1:signature\"\n\tvar parts []string\n\tstart := 0\n\tfor i := 0; i < len(sig); i++ {\n\t\tif sig[i] == ':' {\n\t\t\tparts = append(parts, sig[start:i])\n\t\t\tstart = i + 1\n\t\t}\n\t}\n\tparts = append(parts, sig[start:])\n\treturn parts\n}\n\nfunc (v *vaultSigner) Algorithm(ctx context.Context) (jose.SignatureAlgorithm, error) {\n\tkeysMap, latestVersion, err := v.getTransitKeysMap(ctx)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get keys: %v\", err)\n\t}\n\n\tsigningJWK, ok := keysMap[latestVersion]\n\tif !ok {\n\t\treturn \"\", fmt.Errorf(\"latest key version %d not found\", latestVersion)\n\t}\n\treturn jose.SignatureAlgorithm(signingJWK.Algorithm), nil\n}\n\ntype vaultAlgoParams struct {\n\thasher      hash.Hash\n\textraParams map[string]interface{}\n}\n\nfunc getVaultParams(alg string) (vaultAlgoParams, error) {\n\tparams := vaultAlgoParams{\n\t\textraParams: map[string]interface{}{\n\t\t\t\"marshaling_algorithm\": \"jws\",\n\t\t\t\"signature_algorithm\":  \"pkcs1v15\",\n\t\t},\n\t}\n\n\tswitch alg {\n\tcase \"RS256\":\n\t\tparams.hasher = sha256.New()\n\t\tparams.extraParams[\"prehashed\"] = true\n\t\tparams.extraParams[\"hash_algorithm\"] = \"sha2-256\"\n\tcase \"ES256\":\n\t\tparams.hasher = sha256.New()\n\t\tparams.extraParams[\"prehashed\"] = true\n\t\tparams.extraParams[\"hash_algorithm\"] = \"sha2-256\"\n\tcase \"ES384\":\n\t\tparams.hasher = sha512.New384()\n\t\tparams.extraParams[\"prehashed\"] = true\n\t\tparams.extraParams[\"hash_algorithm\"] = \"sha2-384\"\n\tcase \"ES512\":\n\t\tparams.hasher = sha512.New()\n\t\tparams.extraParams[\"prehashed\"] = true\n\t\tparams.extraParams[\"hash_algorithm\"] = \"sha2-512\"\n\tcase \"EdDSA\":\n\t\t// No hashing\n\t\tparams.hasher = nil\n\tdefault:\n\t\treturn params, fmt.Errorf(\"unsupported signing algorithm: %s\", alg)\n\t}\n\treturn params, nil\n}\n"
  },
  {
    "path": "server/signer/vault_integration_test.go",
    "content": "package signer\n\nimport (\n\t\"context\"\n\t\"crypto/rand\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\tvault \"github.com/openbao/openbao/api/v2\"\n)\n\n// Conformance tests verify that Vault and OpenBao behave identically with the signer.\n// These tests use a single SDK (OpenBao API) that works with both systems.\n//\n// To run tests for a specific system, set the environment variables:\n//\n// For Vault:\n//   DEX_VAULT_ADDR=http://localhost:8200\n//   DEX_VAULT_TOKEN=root-token\n//   go test -v -run TestVaultSignerConformance\n//\n// For OpenBao:\n//   DEX_OPENBAO_ADDR=http://localhost:8210\n//   DEX_OPENBAO_TOKEN=root-token\n//   go test -v -run TestVaultSignerConformance\n//\n// To test both systems in parallel, set both sets of environment variables.\n\ntype conformanceTestConfig struct {\n\tname  string\n\taddr  string\n\ttoken string\n}\n\n// getTestConfigs returns list of test configs based on environment variables\nfunc getTestConfigs(t *testing.T) []conformanceTestConfig {\n\tvar configs []conformanceTestConfig\n\n\t// Check for Vault\n\tvaultAddr := os.Getenv(\"DEX_VAULT_ADDR\")\n\tvaultToken := os.Getenv(\"DEX_VAULT_TOKEN\")\n\tif vaultAddr != \"\" && vaultToken != \"\" {\n\t\tconfigs = append(configs, conformanceTestConfig{\n\t\t\tname:  \"Vault\",\n\t\t\taddr:  vaultAddr,\n\t\t\ttoken: vaultToken,\n\t\t})\n\t}\n\n\t// Check for OpenBao\n\topenbaoAddr := os.Getenv(\"DEX_OPENBAO_ADDR\")\n\topenbaoToken := os.Getenv(\"DEX_OPENBAO_TOKEN\")\n\tif openbaoAddr != \"\" && openbaoToken != \"\" {\n\t\tconfigs = append(configs, conformanceTestConfig{\n\t\t\tname:  \"OpenBao\",\n\t\t\taddr:  openbaoAddr,\n\t\t\ttoken: openbaoToken,\n\t\t})\n\t}\n\n\tif len(configs) == 0 {\n\t\tt.Skip(\"Skipping conformance tests. Set DEX_VAULT_TOKEN+DEX_VAULT_ADDR or DEX_OPENBAO_TOKEN+DEX_OPENBAO_ADDR to run.\")\n\t}\n\n\treturn configs\n}\n\n// TestVaultSignerConformance_SigningAndVerification tests that signing and verification work the same way\n// across Vault and OpenBao implementations.\nfunc TestVaultSignerConformance_SigningAndVerification(t *testing.T) {\n\tconfigs := getTestConfigs(t)\n\n\ttestCases := []struct {\n\t\tname    string\n\t\tkeyType string\n\t\talg     string\n\t}{\n\t\t{\n\t\t\tname:    \"RSA-2048\",\n\t\t\tkeyType: \"rsa-2048\",\n\t\t\talg:     \"RS256\",\n\t\t},\n\t\t{\n\t\t\tname:    \"ECDSA-P256\",\n\t\t\tkeyType: \"ecdsa-p256\",\n\t\t\talg:     \"ES256\",\n\t\t},\n\t\t{\n\t\t\tname:    \"ECDSA-P384\",\n\t\t\tkeyType: \"ecdsa-p384\",\n\t\t\talg:     \"ES384\",\n\t\t},\n\t\t{\n\t\t\tname:    \"ED25519\",\n\t\t\tkeyType: \"ed25519\",\n\t\t\talg:     \"EdDSA\",\n\t\t},\n\t}\n\n\tfor _, config := range configs {\n\t\tt.Run(config.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\n\t\t\t// Create client\n\t\t\tvaultConfig := vault.DefaultConfig()\n\t\t\tvaultConfig.Address = config.addr\n\t\t\tclient, err := vault.NewClient(vaultConfig)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t\t\t}\n\t\t\tclient.SetToken(config.token)\n\n\t\t\t// Enable transit engine\n\t\t\tif err := enableTransitEngine(client); err != nil {\n\t\t\t\tt.Fatalf(\"failed to enable transit engine: %v\", err)\n\t\t\t}\n\n\t\t\tfor _, tc := range testCases {\n\t\t\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\t\t\tkeyName := fmt.Sprintf(\"test-key-%s-%s-%d\", config.name, tc.keyType, time.Now().Unix())\n\n\t\t\t\t\t// Create key\n\t\t\t\t\tkeyData := map[string]interface{}{\n\t\t\t\t\t\t\"type\": tc.keyType,\n\t\t\t\t\t}\n\t\t\t\t\t_, err := client.Logical().WriteWithContext(ctx, fmt.Sprintf(\"transit/keys/%s\", keyName), keyData)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to create key: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tdefer cleanupTests(t, ctx, client, keyName)\n\n\t\t\t\t\t// Create signer\n\t\t\t\t\tsignerConfig := VaultConfig{\n\t\t\t\t\t\tAddr:    config.addr,\n\t\t\t\t\t\tToken:   config.token,\n\t\t\t\t\t\tKeyName: keyName,\n\t\t\t\t\t}\n\t\t\t\t\tsigner, err := newVaultSigner(signerConfig)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to create signer: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Test 1: Verify algorithm\n\t\t\t\t\talg, err := signer.Algorithm(ctx)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to get algorithm: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif string(alg) != tc.alg {\n\t\t\t\t\t\tt.Errorf(\"expected algorithm %s, got %s\", tc.alg, alg)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Test 2: Get validation keys\n\t\t\t\t\tkeys, err := signer.ValidationKeys(ctx)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to get validation keys: %v\", err)\n\t\t\t\t\t}\n\t\t\t\t\tif len(keys) == 0 {\n\t\t\t\t\t\tt.Fatal(\"expected at least one validation key\")\n\t\t\t\t\t}\n\t\t\t\t\tif keys[0].Algorithm != tc.alg {\n\t\t\t\t\t\tt.Errorf(\"expected key algorithm %s, got %s\", tc.alg, keys[0].Algorithm)\n\t\t\t\t\t}\n\t\t\t\t\tif keys[0].Use != \"sig\" {\n\t\t\t\t\t\tt.Errorf(\"expected key use 'sig', got %s\", keys[0].Use)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Test 3: Sign and verify JWT\n\t\t\t\t\tpayload := map[string]interface{}{\n\t\t\t\t\t\t\"iss\": \"https://dex.example.com\",\n\t\t\t\t\t\t\"sub\": \"user123\",\n\t\t\t\t\t\t\"aud\": \"client-app\",\n\t\t\t\t\t\t\"exp\": time.Now().Add(time.Hour).Unix(),\n\t\t\t\t\t\t\"iat\": time.Now().Unix(),\n\t\t\t\t\t}\n\t\t\t\t\tpayloadBytes, err := json.Marshal(payload)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to marshal payload: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tjwtString, err := signer.Sign(ctx, payloadBytes)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to sign payload: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\t// Verify JWT signature\n\t\t\t\t\tjws, err := jose.ParseSigned(jwtString, []jose.SignatureAlgorithm{jose.SignatureAlgorithm(tc.alg)})\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to parse signed JWT: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tverifiedPayload, err := jws.Verify(keys[0])\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to verify JWT signature: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tvar decodedPayload map[string]interface{}\n\t\t\t\t\tif err := json.Unmarshal(verifiedPayload, &decodedPayload); err != nil {\n\t\t\t\t\t\tt.Fatalf(\"failed to unmarshal verified payload: %v\", err)\n\t\t\t\t\t}\n\n\t\t\t\t\tif decodedPayload[\"sub\"] != payload[\"sub\"] {\n\t\t\t\t\t\tt.Errorf(\"payload mismatch: expected sub=%s, got %s\", payload[\"sub\"], decodedPayload[\"sub\"])\n\t\t\t\t\t}\n\n\t\t\t\t\t// Test 4: Multiple signatures with same key\n\t\t\t\t\tfor i := 0; i < 3; i++ {\n\t\t\t\t\t\trandomPayload := make([]byte, 32)\n\t\t\t\t\t\t_, err := rand.Read(randomPayload)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"failed to generate random payload: %v\", err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tpayloadData := map[string]interface{}{\n\t\t\t\t\t\t\t\"data\": base64.StdEncoding.EncodeToString(randomPayload),\n\t\t\t\t\t\t\t\"iat\":  time.Now().Unix(),\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpayloadBytes, err := json.Marshal(payloadData)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"failed to marshal payload: %v\", err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tjwtString, err := signer.Sign(ctx, payloadBytes)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"sign attempt %d failed: %v\", i+1, err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tjws, err := jose.ParseSigned(jwtString, []jose.SignatureAlgorithm{jose.SignatureAlgorithm(tc.alg)})\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"parse attempt %d failed: %v\", i+1, err)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t_, err = jws.Verify(keys[0])\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tt.Fatalf(\"verify attempt %d failed: %v\", i+1, err)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestVaultSignerConformance_KeyRotation tests that key rotation works identically\n// across Vault and OpenBao implementations.\nfunc TestVaultSignerConformance_KeyRotation(t *testing.T) {\n\tconfigs := getTestConfigs(t)\n\n\tfor _, config := range configs {\n\t\tt.Run(config.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\n\t\t\t// Create client\n\t\t\tvaultConfig := vault.DefaultConfig()\n\t\t\tvaultConfig.Address = config.addr\n\t\t\tclient, err := vault.NewClient(vaultConfig)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t\t\t}\n\t\t\tclient.SetToken(config.token)\n\n\t\t\t// Enable transit engine\n\t\t\tif err := enableTransitEngine(client); err != nil {\n\t\t\t\tt.Fatalf(\"failed to enable transit engine: %v\", err)\n\t\t\t}\n\n\t\t\tkeyName := fmt.Sprintf(\"test-rotation-key-%s-%d\", config.name, time.Now().Unix())\n\n\t\t\t// Create initial key\n\t\t\tkeyData := map[string]interface{}{\n\t\t\t\t\"type\": \"ecdsa-p256\",\n\t\t\t}\n\t\t\t_, err = client.Logical().WriteWithContext(ctx, fmt.Sprintf(\"transit/keys/%s\", keyName), keyData)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create key: %v\", err)\n\t\t\t}\n\n\t\t\tdefer cleanupTests(t, ctx, client, keyName)\n\n\t\t\t// Create signer\n\t\t\tsignerConfig := VaultConfig{\n\t\t\t\tAddr:    config.addr,\n\t\t\t\tToken:   config.token,\n\t\t\t\tKeyName: keyName,\n\t\t\t}\n\t\t\tsigner, err := newVaultSigner(signerConfig)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create signer: %v\", err)\n\t\t\t}\n\n\t\t\t// Sign with initial key version\n\t\t\tpayload1 := map[string]interface{}{\"version\": \"v1\", \"iat\": time.Now().Unix()}\n\t\t\tpayload1Bytes, err := json.Marshal(payload1)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to marshal payload: %v\", err)\n\t\t\t}\n\n\t\t\tjwt1, err := signer.Sign(ctx, payload1Bytes)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to sign with v1: %v\", err)\n\t\t\t}\n\n\t\t\t// Get keys before rotation\n\t\t\tkeysBefore, err := signer.ValidationKeys(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to get keys before rotation: %v\", err)\n\t\t\t}\n\t\t\tif len(keysBefore) != 1 {\n\t\t\t\tt.Errorf(\"expected 1 key before rotation, got %d\", len(keysBefore))\n\t\t\t}\n\n\t\t\t// Rotate key\n\t\t\t_, err = client.Logical().WriteWithContext(ctx, fmt.Sprintf(\"transit/keys/%s/rotate\", keyName), nil)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to rotate key: %v\", err)\n\t\t\t}\n\n\t\t\t// Sign with new key version\n\t\t\tpayload2 := map[string]interface{}{\"version\": \"v2\", \"iat\": time.Now().Unix()}\n\t\t\tpayload2Bytes, err := json.Marshal(payload2)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to marshal payload: %v\", err)\n\t\t\t}\n\n\t\t\tjwt2, err := signer.Sign(ctx, payload2Bytes)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to sign with v2: %v\", err)\n\t\t\t}\n\n\t\t\t// Get keys after rotation\n\t\t\tkeysAfter, err := signer.ValidationKeys(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to get keys after rotation: %v\", err)\n\t\t\t}\n\t\t\tif len(keysAfter) != 2 {\n\t\t\t\tt.Errorf(\"expected 2 keys after rotation, got %d\", len(keysAfter))\n\t\t\t}\n\n\t\t\t// Verify both JWTs can be validated with the current keyset\n\t\t\tjws1, err := jose.ParseSigned(jwt1, []jose.SignatureAlgorithm{jose.ES256})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to parse jwt1: %v\", err)\n\t\t\t}\n\n\t\t\tjws2, err := jose.ParseSigned(jwt2, []jose.SignatureAlgorithm{jose.ES256})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to parse jwt2: %v\", err)\n\t\t\t}\n\n\t\t\t// Find matching keys and verify\n\t\t\tverified1 := false\n\t\t\tverified2 := false\n\n\t\t\tfor _, key := range keysAfter {\n\t\t\t\tif _, err := jws1.Verify(key); err == nil {\n\t\t\t\t\tverified1 = true\n\t\t\t\t}\n\t\t\t\tif _, err := jws2.Verify(key); err == nil {\n\t\t\t\t\tverified2 = true\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif !verified1 {\n\t\t\t\tt.Error(\"failed to verify JWT signed with version 1\")\n\t\t\t}\n\t\t\tif !verified2 {\n\t\t\t\tt.Error(\"failed to verify JWT signed with version 2\")\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestVaultSignerConformance_PublicKeyDiscovery tests that public key discovery works identically\n// across Vault and OpenBao implementations.\nfunc TestVaultSignerConformance_PublicKeyDiscovery(t *testing.T) {\n\tconfigs := getTestConfigs(t)\n\n\tfor _, config := range configs {\n\t\tt.Run(config.name, func(t *testing.T) {\n\t\t\tctx := context.Background()\n\n\t\t\t// Create client\n\t\t\tvaultConfig := vault.DefaultConfig()\n\t\t\tvaultConfig.Address = config.addr\n\t\t\tclient, err := vault.NewClient(vaultConfig)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create client: %v\", err)\n\t\t\t}\n\t\t\tclient.SetToken(config.token)\n\n\t\t\t// Enable transit engine\n\t\t\tif err := enableTransitEngine(client); err != nil {\n\t\t\t\tt.Fatalf(\"failed to enable transit engine: %v\", err)\n\t\t\t}\n\n\t\t\tkeyName := fmt.Sprintf(\"test-discovery-key-%s-%d\", config.name, time.Now().Unix())\n\n\t\t\t// Create key\n\t\t\tkeyData := map[string]interface{}{\n\t\t\t\t\"type\": \"rsa-2048\",\n\t\t\t}\n\t\t\t_, err = client.Logical().WriteWithContext(ctx, fmt.Sprintf(\"transit/keys/%s\", keyName), keyData)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create key: %v\", err)\n\t\t\t}\n\n\t\t\tdefer cleanupTests(t, ctx, client, keyName)\n\n\t\t\t// Create signer\n\t\t\tsignerConfig := VaultConfig{\n\t\t\t\tAddr:    config.addr,\n\t\t\t\tToken:   config.token,\n\t\t\t\tKeyName: keyName,\n\t\t\t}\n\t\t\tsigner, err := newVaultSigner(signerConfig)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to create signer: %v\", err)\n\t\t\t}\n\n\t\t\t// Get public keys (simulating JWKS endpoint)\n\t\t\tkeys, err := signer.ValidationKeys(ctx)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to get validation keys: %v\", err)\n\t\t\t}\n\n\t\t\t// Verify keys have required JWKS fields\n\t\t\tfor i, key := range keys {\n\t\t\t\tif key.KeyID == \"\" {\n\t\t\t\t\tt.Errorf(\"key %d missing KeyID\", i)\n\t\t\t\t}\n\t\t\t\tif key.Algorithm == \"\" {\n\t\t\t\t\tt.Errorf(\"key %d missing Algorithm\", i)\n\t\t\t\t}\n\t\t\t\tif key.Use != \"sig\" {\n\t\t\t\t\tt.Errorf(\"key %d has wrong Use field: expected 'sig', got '%s'\", i, key.Use)\n\t\t\t\t}\n\t\t\t\tif key.Key == nil {\n\t\t\t\t\tt.Errorf(\"key %d missing public key\", i)\n\t\t\t\t}\n\n\t\t\t\t// Verify key can be marshaled to JWKS format\n\t\t\t\tjwksData, err := json.Marshal(key)\n\t\t\t\tif err != nil {\n\t\t\t\t\tt.Errorf(\"key %d cannot be marshaled to JSON: %v\", i, err)\n\t\t\t\t}\n\n\t\t\t\tvar jwksCheck map[string]interface{}\n\t\t\t\tif err := json.Unmarshal(jwksData, &jwksCheck); err != nil {\n\t\t\t\t\tt.Errorf(\"key %d JWKS data is invalid: %v\", i, err)\n\t\t\t\t}\n\n\t\t\t\t// Check for standard JWKS fields\n\t\t\t\trequiredFields := []string{\"kty\", \"use\", \"kid\", \"alg\"}\n\t\t\t\tfor _, field := range requiredFields {\n\t\t\t\t\tif _, ok := jwksCheck[field]; !ok {\n\t\t\t\t\t\tt.Errorf(\"key %d missing required JWKS field: %s\", i, field)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sign a JWT\n\t\t\tpayload := map[string]interface{}{\n\t\t\t\t\"iss\": \"https://dex.example.com\",\n\t\t\t\t\"sub\": \"test-user\",\n\t\t\t\t\"aud\": \"test-client\",\n\t\t\t\t\"exp\": time.Now().Add(time.Hour).Unix(),\n\t\t\t\t\"iat\": time.Now().Unix(),\n\t\t\t}\n\t\t\tpayloadBytes, err := json.Marshal(payload)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to marshal payload: %v\", err)\n\t\t\t}\n\n\t\t\tjwtString, err := signer.Sign(ctx, payloadBytes)\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to sign JWT: %v\", err)\n\t\t\t}\n\n\t\t\t// Parse JWT and verify it has correct kid in header\n\t\t\tjws, err := jose.ParseSigned(jwtString, []jose.SignatureAlgorithm{jose.RS256})\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"failed to parse JWT: %v\", err)\n\t\t\t}\n\n\t\t\tif len(jws.Signatures) == 0 {\n\t\t\t\tt.Fatal(\"JWT has no signatures\")\n\t\t\t}\n\n\t\t\tkid := jws.Signatures[0].Header.KeyID\n\t\t\tif kid == \"\" {\n\t\t\t\tt.Error(\"JWT header missing kid\")\n\t\t\t}\n\n\t\t\t// Verify kid matches one of the public keys\n\t\t\tkidFound := false\n\t\t\tfor _, key := range keys {\n\t\t\t\tif key.KeyID == kid {\n\t\t\t\t\tkidFound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !kidFound {\n\t\t\t\tt.Errorf(\"JWT kid '%s' not found in public keys\", kid)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// enableTransitEngine enables the transit secrets engine if not already enabled.\nfunc enableTransitEngine(client *vault.Client) error {\n\t// Check if already enabled\n\tmounts, err := client.Sys().ListMounts()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to list mounts: %v\", err)\n\t}\n\n\tif _, exists := mounts[\"transit/\"]; exists {\n\t\treturn nil\n\t}\n\n\t// Enable transit engine\n\tmountInput := &vault.MountInput{\n\t\tType: \"transit\",\n\t}\n\tif err := client.Sys().Mount(\"transit\", mountInput); err != nil {\n\t\treturn fmt.Errorf(\"failed to mount transit: %v\", err)\n\t}\n\n\treturn nil\n}\n\nfunc cleanupTests(t *testing.T, ctx context.Context, client *vault.Client, keyName string) {\n\tupdateData := map[string]interface{}{\n\t\t\"deletion_allowed\": true,\n\t}\n\t_, err := client.Logical().WriteWithContext(ctx, fmt.Sprintf(\"transit/keys/%s/config\", keyName), updateData)\n\tif err != nil {\n\t\tt.Logf(\"failed to update key config: %v\", err)\n\t}\n\t_, err = client.Logical().DeleteWithContext(ctx, fmt.Sprintf(\"transit/keys/%s\", keyName))\n\tif err != nil {\n\t\tt.Logf(\"failed to delete key: %v\", err)\n\t}\n}\n"
  },
  {
    "path": "server/signer/vault_test.go",
    "content": "package signer\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestVaultConfigUnmarshalJSON_WithEnvVars(t *testing.T) {\n\t// Save original environment variables\n\toriginalAddr := os.Getenv(\"VAULT_ADDR\")\n\toriginalToken := os.Getenv(\"VAULT_TOKEN\")\n\tdefer func() {\n\t\tos.Setenv(\"VAULT_ADDR\", originalAddr)\n\t\tos.Setenv(\"VAULT_TOKEN\", originalToken)\n\t}()\n\n\t// Set environment variables\n\tos.Setenv(\"VAULT_ADDR\", \"http://vault.example.com:8200\")\n\tos.Setenv(\"VAULT_TOKEN\", \"s.xxxxxxxxxxxxxxxx\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tjson    string\n\t\twant    VaultConfig\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"empty config uses env vars\",\n\t\t\tjson: `{\"keyName\": \"signing-key\"}`,\n\t\t\twant: VaultConfig{\n\t\t\t\tAddr:    \"http://vault.example.com:8200\",\n\t\t\t\tToken:   \"s.xxxxxxxxxxxxxxxx\",\n\t\t\t\tKeyName: \"signing-key\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"config values override env vars\",\n\t\t\tjson: `{\"addr\": \"http://custom.vault.com:8200\", \"token\": \"s.custom\", \"keyName\": \"signing-key\"}`,\n\t\t\twant: VaultConfig{\n\t\t\t\tAddr:    \"http://custom.vault.com:8200\",\n\t\t\t\tToken:   \"s.custom\",\n\t\t\t\tKeyName: \"signing-key\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"partial config uses env vars for missing values\",\n\t\t\tjson: `{\"addr\": \"http://custom.vault.com:8200\", \"keyName\": \"signing-key\"}`,\n\t\t\twant: VaultConfig{\n\t\t\t\tAddr:    \"http://custom.vault.com:8200\",\n\t\t\t\tToken:   \"s.xxxxxxxxxxxxxxxx\",\n\t\t\t\tKeyName: \"signing-key\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"empty token in config uses env var\",\n\t\t\tjson: `{\"addr\": \"http://custom.vault.com:8200\", \"token\": \"\", \"keyName\": \"signing-key\"}`,\n\t\t\twant: VaultConfig{\n\t\t\t\tAddr:    \"http://custom.vault.com:8200\",\n\t\t\t\tToken:   \"s.xxxxxxxxxxxxxxxx\",\n\t\t\t\tKeyName: \"signing-key\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar got VaultConfig\n\t\t\terr := json.Unmarshal([]byte(tt.json), &got)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UnmarshalJSON() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got.Addr != tt.want.Addr {\n\t\t\t\tt.Errorf(\"Addr: got %q, want %q\", got.Addr, tt.want.Addr)\n\t\t\t}\n\t\t\tif got.Token != tt.want.Token {\n\t\t\t\tt.Errorf(\"Token: got %q, want %q\", got.Token, tt.want.Token)\n\t\t\t}\n\t\t\tif got.KeyName != tt.want.KeyName {\n\t\t\t\tt.Errorf(\"KeyName: got %q, want %q\", got.KeyName, tt.want.KeyName)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVaultConfigUnmarshalJSON_WithoutEnvVars(t *testing.T) {\n\t// Save original environment variables\n\toriginalAddr := os.Getenv(\"VAULT_ADDR\")\n\toriginalToken := os.Getenv(\"VAULT_TOKEN\")\n\tdefer func() {\n\t\tos.Setenv(\"VAULT_ADDR\", originalAddr)\n\t\tos.Setenv(\"VAULT_TOKEN\", originalToken)\n\t}()\n\n\t// Unset environment variables\n\tos.Unsetenv(\"VAULT_ADDR\")\n\tos.Unsetenv(\"VAULT_TOKEN\")\n\n\ttests := []struct {\n\t\tname    string\n\t\tjson    string\n\t\twant    VaultConfig\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"config values used when env vars not set\",\n\t\t\tjson: `{\"addr\": \"http://vault.example.com:8200\", \"token\": \"s.xxxxxxxxxxxxxxxx\", \"keyName\": \"signing-key\"}`,\n\t\t\twant: VaultConfig{\n\t\t\t\tAddr:    \"http://vault.example.com:8200\",\n\t\t\t\tToken:   \"s.xxxxxxxxxxxxxxxx\",\n\t\t\t\tKeyName: \"signing-key\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"empty config when env vars not set\",\n\t\t\tjson: `{\"keyName\": \"signing-key\"}`,\n\t\t\twant: VaultConfig{\n\t\t\t\tAddr:    \"\",\n\t\t\t\tToken:   \"\",\n\t\t\t\tKeyName: \"signing-key\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\tname: \"only keyName required in config\",\n\t\t\tjson: `{\"keyName\": \"my-key\"}`,\n\t\t\twant: VaultConfig{\n\t\t\t\tAddr:    \"\",\n\t\t\t\tToken:   \"\",\n\t\t\t\tKeyName: \"my-key\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar got VaultConfig\n\t\t\terr := json.Unmarshal([]byte(tt.json), &got)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UnmarshalJSON() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif got.Addr != tt.want.Addr {\n\t\t\t\tt.Errorf(\"Addr: got %q, want %q\", got.Addr, tt.want.Addr)\n\t\t\t}\n\t\t\tif got.Token != tt.want.Token {\n\t\t\t\tt.Errorf(\"Token: got %q, want %q\", got.Token, tt.want.Token)\n\t\t\t}\n\t\t\tif got.KeyName != tt.want.KeyName {\n\t\t\t\tt.Errorf(\"KeyName: got %q, want %q\", got.KeyName, tt.want.KeyName)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestVaultConfigUnmarshalJSON_InvalidJSON(t *testing.T) {\n\ttests := []struct {\n\t\tname    string\n\t\tjson    string\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname:    \"invalid json\",\n\t\t\tjson:    `{invalid json}`,\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname:    \"empty json\",\n\t\t\tjson:    `{}`,\n\t\t\twantErr: false,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tvar got VaultConfig\n\t\t\terr := json.Unmarshal([]byte(tt.json), &got)\n\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"UnmarshalJSON() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "server/templates.go",
    "content": "package server\n\nimport (\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/fs\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/sprig/v3\"\n)\n\nconst (\n\ttmplApproval      = \"approval.html\"\n\ttmplLogin         = \"login.html\"\n\ttmplPassword      = \"password.html\"\n\ttmplOOB           = \"oob.html\"\n\ttmplError         = \"error.html\"\n\ttmplDevice        = \"device.html\"\n\ttmplDeviceSuccess = \"device_success.html\"\n\ttmplTOTPVerify    = \"totp_verify.html\"\n)\n\nvar requiredTmpls = []string{\n\ttmplApproval,\n\ttmplLogin,\n\ttmplPassword,\n\ttmplOOB,\n\ttmplError,\n\ttmplDevice,\n\ttmplDeviceSuccess,\n}\n\ntype templates struct {\n\tloginTmpl         *template.Template\n\tapprovalTmpl      *template.Template\n\tpasswordTmpl      *template.Template\n\toobTmpl           *template.Template\n\terrorTmpl         *template.Template\n\tdeviceTmpl        *template.Template\n\tdeviceSuccessTmpl *template.Template\n\ttotpVerifyTmpl    *template.Template\n}\n\ntype webConfig struct {\n\twebFS     fs.FS\n\tlogoURL   string\n\tissuer    string\n\ttheme     string\n\tissuerURL string\n\textra     map[string]string\n}\n\nfunc getFuncMap(c webConfig) (template.FuncMap, error) {\n\tfuncs := sprig.FuncMap()\n\n\tissuerURL, err := url.Parse(c.issuerURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"error parsing issuerURL: %v\", err)\n\t}\n\n\tadditionalFuncs := map[string]interface{}{\n\t\t\"extra\":  func(k string) string { return c.extra[k] },\n\t\t\"issuer\": func() string { return c.issuer },\n\t\t\"logo\":   func() string { return c.logoURL },\n\t\t\"url\": func(reqPath, assetPath string) string {\n\t\t\treturn relativeURL(issuerURL.Path, reqPath, assetPath)\n\t\t},\n\t}\n\n\tfor k, v := range additionalFuncs {\n\t\tfuncs[k] = v\n\t}\n\n\treturn funcs, nil\n}\n\n// loadWebConfig returns static assets, theme assets, and templates used by the frontend by\n// reading the dir specified in the webConfig. If directory is not specified it will\n// use the file system specified by webFS.\n//\n// The directory layout is expected to be:\n//\n//\t( web directory )\n//\t|- static\n//\t|- themes\n//\t|  |- (theme name)\n//\t|- templates\nfunc loadWebConfig(c webConfig) (http.Handler, http.Handler, http.HandlerFunc, *templates, error) {\n\t// fallback to the default theme if the legacy theme name is provided\n\tif c.theme == \"coreos\" || c.theme == \"tectonic\" {\n\t\tc.theme = \"\"\n\t}\n\tif c.theme == \"\" {\n\t\tc.theme = \"light\"\n\t}\n\tif c.issuer == \"\" {\n\t\tc.issuer = \"dex\"\n\t}\n\tif c.logoURL == \"\" {\n\t\tc.logoURL = \"theme/logo.png\"\n\t}\n\n\tstaticFiles, err := fs.Sub(c.webFS, \"static\")\n\tif err != nil {\n\t\treturn nil, nil, nil, nil, fmt.Errorf(\"read static dir: %v\", err)\n\t}\n\tthemeFiles, err := fs.Sub(c.webFS, path.Join(\"themes\", c.theme))\n\tif err != nil {\n\t\treturn nil, nil, nil, nil, fmt.Errorf(\"read themes dir: %v\", err)\n\t}\n\trobotsContent, err := fs.ReadFile(c.webFS, \"robots.txt\")\n\tif err != nil {\n\t\treturn nil, nil, nil, nil, fmt.Errorf(\"read robots.txt dir: %v\", err)\n\t}\n\n\tstatic := http.FileServer(http.FS(staticFiles))\n\ttheme := http.FileServer(http.FS(themeFiles))\n\trobots := func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, string(robotsContent)) }\n\n\ttemplates, err := loadTemplates(c, \"templates\")\n\n\treturn static, theme, robots, templates, err\n}\n\n// loadTemplates parses the expected templates from the provided directory.\nfunc loadTemplates(c webConfig, templatesDir string) (*templates, error) {\n\tfiles, err := fs.ReadDir(c.webFS, templatesDir)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"read dir: %v\", err)\n\t}\n\n\tfilenames := []string{}\n\tfor _, file := range files {\n\t\tif file.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tfilenames = append(filenames, path.Join(templatesDir, file.Name()))\n\t}\n\tif len(filenames) == 0 {\n\t\treturn nil, fmt.Errorf(\"no files in template dir %q\", templatesDir)\n\t}\n\n\tfuncs, err := getFuncMap(c)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttmpls, err := template.New(\"\").Funcs(funcs).ParseFS(c.webFS, filenames...)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"parse files: %v\", err)\n\t}\n\tmissingTmpls := []string{}\n\tfor _, tmplName := range requiredTmpls {\n\t\tif tmpls.Lookup(tmplName) == nil {\n\t\t\tmissingTmpls = append(missingTmpls, tmplName)\n\t\t}\n\t}\n\tif len(missingTmpls) > 0 {\n\t\treturn nil, fmt.Errorf(\"missing template(s): %s\", missingTmpls)\n\t}\n\treturn &templates{\n\t\tloginTmpl:         tmpls.Lookup(tmplLogin),\n\t\tapprovalTmpl:      tmpls.Lookup(tmplApproval),\n\t\tpasswordTmpl:      tmpls.Lookup(tmplPassword),\n\t\toobTmpl:           tmpls.Lookup(tmplOOB),\n\t\terrorTmpl:         tmpls.Lookup(tmplError),\n\t\tdeviceTmpl:        tmpls.Lookup(tmplDevice),\n\t\tdeviceSuccessTmpl: tmpls.Lookup(tmplDeviceSuccess),\n\t\ttotpVerifyTmpl:    tmpls.Lookup(tmplTOTPVerify),\n\t}, nil\n}\n\n// relativeURL returns the URL of the asset relative to the URL of the request path.\n// The serverPath is consulted to trim any prefix due in case it is not listening\n// to the root path.\n//\n// Algorithm:\n// 1. Remove common prefix of serverPath and reqPath\n// 2. Remove common prefix of assetPath and reqPath\n// 3. For each part of reqPath remaining(minus one), go up one level (..)\n// 4. For each part of assetPath remaining, append it to result\n//\n// eg\n// server listens at localhost/dex so serverPath is dex\n// reqPath is /dex/auth\n// assetPath is static/main.css\n// relativeURL(\"/dex\", \"/dex/auth\", \"static/main.css\") = \"../static/main.css\"\nfunc relativeURL(serverPath, reqPath, assetPath string) string {\n\tif u, err := url.ParseRequestURI(assetPath); err == nil && u.Scheme != \"\" {\n\t\t// assetPath points to the external URL, no changes needed\n\t\treturn assetPath\n\t}\n\n\tsplitPath := func(p string) []string {\n\t\tres := []string{}\n\t\tparts := strings.Split(path.Clean(p), \"/\")\n\t\tfor _, part := range parts {\n\t\t\tif part != \"\" {\n\t\t\t\tres = append(res, part)\n\t\t\t}\n\t\t}\n\t\treturn res\n\t}\n\n\tstripCommonParts := func(s1, s2 []string) ([]string, []string) {\n\t\tmin := len(s1)\n\t\tif len(s2) < min {\n\t\t\tmin = len(s2)\n\t\t}\n\n\t\tsplitIndex := min\n\t\tfor i := 0; i < min; i++ {\n\t\t\tif s1[i] != s2[i] {\n\t\t\t\tsplitIndex = i\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\treturn s1[splitIndex:], s2[splitIndex:]\n\t}\n\n\tserver, req, asset := splitPath(serverPath), splitPath(reqPath), splitPath(assetPath)\n\n\t// Remove common prefix of request path with server path\n\t_, req = stripCommonParts(server, req)\n\n\t// Remove common prefix of request path with asset path\n\tasset, req = stripCommonParts(asset, req)\n\n\t// For each part of the request remaining (minus one) -> go up one level (..)\n\t// For each part of the asset remaining               -> append it\n\tvar relativeURL string\n\tfor i := 0; i < len(req)-1; i++ {\n\t\trelativeURL = path.Join(\"..\", relativeURL)\n\t}\n\trelativeURL = path.Join(relativeURL, path.Join(asset...))\n\n\treturn relativeURL\n}\n\nvar scopeDescriptions = map[string]string{\n\t\"offline_access\": \"Have offline access\",\n\t\"profile\":        \"View basic profile information\",\n\t\"email\":          \"View your email address\",\n\t// 'groups' is not a standard OIDC scope, and Dex only returns groups only if the upstream provider does too.\n\t// This warning is added for convenience to show that the user may expose some sensitive data to the application.\n\t\"groups\": \"View your groups\",\n}\n\ntype connectorInfo struct {\n\tID   string\n\tName string\n\tURL  template.URL\n\tType string\n}\n\ntype byName []connectorInfo\n\nfunc (n byName) Len() int           { return len(n) }\nfunc (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name }\nfunc (n byName) Swap(i, j int)      { n[i], n[j] = n[j], n[i] }\n\nfunc (t *templates) device(r *http.Request, w http.ResponseWriter, postURL string, userCode string, lastWasInvalid bool) error {\n\tif lastWasInvalid {\n\t\tw.WriteHeader(http.StatusBadRequest)\n\t}\n\tdata := struct {\n\t\tPostURL  string\n\t\tUserCode string\n\t\tInvalid  bool\n\t\tReqPath  string\n\t}{postURL, userCode, lastWasInvalid, r.URL.Path}\n\treturn renderTemplate(w, t.deviceTmpl, data)\n}\n\nfunc (t *templates) deviceSuccess(r *http.Request, w http.ResponseWriter, clientName string) error {\n\tdata := struct {\n\t\tClientName string\n\t\tReqPath    string\n\t}{clientName, r.URL.Path}\n\treturn renderTemplate(w, t.deviceSuccessTmpl, data)\n}\n\nfunc (t *templates) login(r *http.Request, w http.ResponseWriter, connectors []connectorInfo) error {\n\tsort.Sort(byName(connectors))\n\tdata := struct {\n\t\tConnectors []connectorInfo\n\t\tReqPath    string\n\t}{connectors, r.URL.Path}\n\treturn renderTemplate(w, t.loginTmpl, data)\n}\n\nfunc (t *templates) password(r *http.Request, w http.ResponseWriter, postURL, lastUsername, usernamePrompt string, lastWasInvalid bool, backLink string, rememberMe *bool) error {\n\tif lastWasInvalid {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t}\n\tdata := struct {\n\t\tPostURL           string\n\t\tBackLink          string\n\t\tUsername          string\n\t\tUsernamePrompt    string\n\t\tInvalid           bool\n\t\tReqPath           string\n\t\tShowRememberMe    bool\n\t\tRememberMeChecked bool\n\t}{\n\t\tPostURL:        postURL,\n\t\tBackLink:       backLink,\n\t\tUsername:       lastUsername,\n\t\tUsernamePrompt: usernamePrompt,\n\t\tInvalid:        lastWasInvalid,\n\t\tReqPath:        r.URL.Path,\n\t\tShowRememberMe: rememberMe != nil,\n\t}\n\tif rememberMe != nil {\n\t\tdata.RememberMeChecked = *rememberMe\n\t}\n\treturn renderTemplate(w, t.passwordTmpl, data)\n}\n\nfunc (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID, username, clientName string, scopes []string) error {\n\taccesses := []string{}\n\tfor _, scope := range scopes {\n\t\taccess, ok := scopeDescriptions[scope]\n\t\tif ok {\n\t\t\taccesses = append(accesses, access)\n\t\t}\n\t}\n\tsort.Strings(accesses)\n\tdata := struct {\n\t\tUser      string\n\t\tClient    string\n\t\tAuthReqID string\n\t\tScopes    []string\n\t\tReqPath   string\n\t}{username, clientName, authReqID, accesses, r.URL.Path}\n\treturn renderTemplate(w, t.approvalTmpl, data)\n}\n\nfunc (t *templates) totpVerify(r *http.Request, w http.ResponseWriter, postURL, issuer, connector, qrCode string, lastWasInvalid bool) error {\n\tif lastWasInvalid {\n\t\tw.WriteHeader(http.StatusUnauthorized)\n\t}\n\tdata := struct {\n\t\tPostURL   string\n\t\tInvalid   bool\n\t\tIssuer    string\n\t\tConnector string\n\t\tQRCode    string\n\t\tReqPath   string\n\t}{postURL, lastWasInvalid, issuer, connector, qrCode, r.URL.Path}\n\treturn renderTemplate(w, t.totpVerifyTmpl, data)\n}\n\nfunc (t *templates) oob(r *http.Request, w http.ResponseWriter, code string) error {\n\tdata := struct {\n\t\tCode    string\n\t\tReqPath string\n\t}{code, r.URL.Path}\n\treturn renderTemplate(w, t.oobTmpl, data)\n}\n\nfunc (t *templates) err(r *http.Request, w http.ResponseWriter, errCode int, errMsg string) error {\n\tw.WriteHeader(errCode)\n\tdata := struct {\n\t\tErrType string\n\t\tErrMsg  string\n\t\tReqPath string\n\t}{http.StatusText(errCode), errMsg, r.URL.Path}\n\tif err := t.errorTmpl.Execute(w, data); err != nil {\n\t\treturn fmt.Errorf(\"rendering template %s failed: %s\", t.errorTmpl.Name(), err)\n\t}\n\treturn nil\n}\n\n// small io.Writer utility to determine if executing the template wrote to the underlying response writer.\ntype writeRecorder struct {\n\twrote bool\n\tw     io.Writer\n}\n\nfunc (w *writeRecorder) Write(p []byte) (n int, err error) {\n\tw.wrote = true\n\treturn w.w.Write(p)\n}\n\nfunc renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) error {\n\twr := &writeRecorder{w: w}\n\tif err := tmpl.Execute(wr, data); err != nil {\n\t\tif !wr.wrote {\n\t\t\t// TODO(ericchiang): replace with better internal server error.\n\t\t\thttp.Error(w, \"Internal server error\", http.StatusInternalServerError)\n\t\t}\n\t\treturn fmt.Errorf(\"rendering template %s failed: %s\", tmpl.Name(), err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "server/templates_test.go",
    "content": "package server\n\nimport \"testing\"\n\nfunc TestRelativeURL(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tserverPath string\n\t\treqPath    string\n\t\tassetPath  string\n\t\texpected   string\n\t}{\n\t\t{\n\t\t\tname:       \"server-root-req-one-level-asset-two-level\",\n\t\t\tserverPath: \"/\",\n\t\t\treqPath:    \"/auth\",\n\t\t\tassetPath:  \"/theme/main.css\",\n\t\t\texpected:   \"theme/main.css\",\n\t\t},\n\t\t{\n\t\t\tname:       \"server-one-level-req-one-level-asset-two-level\",\n\t\t\tserverPath: \"/dex\",\n\t\t\treqPath:    \"/dex/auth\",\n\t\t\tassetPath:  \"/theme/main.css\",\n\t\t\texpected:   \"theme/main.css\",\n\t\t},\n\t\t{\n\t\t\tname:       \"server-root-req-two-level-asset-three-level\",\n\t\t\tserverPath: \"/dex\",\n\t\t\treqPath:    \"/dex/auth/connector\",\n\t\t\tassetPath:  \"assets/css/main.css\",\n\t\t\texpected:   \"../assets/css/main.css\",\n\t\t},\n\t\t{\n\t\t\tname:       \"external-url\",\n\t\t\tserverPath: \"/dex\",\n\t\t\treqPath:    \"/dex/auth/connector\",\n\t\t\tassetPath:  \"https://kubernetes.io/images/favicon.png\",\n\t\t\texpected:   \"https://kubernetes.io/images/favicon.png\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tactual := relativeURL(test.serverPath, test.reqPath, test.assetPath)\n\t\t\tif actual != test.expected {\n\t\t\t\tt.Fatalf(\"Got '%s'. Expected '%s'\", actual, test.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "storage/conformance/conformance.go",
    "content": "// Package conformance provides conformance tests for storage implementations.\npackage conformance\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"sort\"\n\t\"testing\"\n\t\"time\"\n\n\tjose \"github.com/go-jose/go-jose/v4\"\n\t\"github.com/kylelemons/godebug/pretty\"\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/bcrypt\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// ensure that values being tested on never expire.\nvar neverExpire = time.Now().UTC().Add(time.Hour * 24 * 365 * 100)\n\n// defaultAuthTime is a non-zero time used as AuthTime default in tests.\n// MySQL rejects Go's zero time (0001-01-01), so all test fixtures must use a real value.\nvar defaultAuthTime = time.Now().UTC()\n\ntype subTest struct {\n\tname string\n\trun  func(t *testing.T, s storage.Storage)\n}\n\nfunc runTests(t *testing.T, newStorage func(t *testing.T) storage.Storage, tests []subTest) {\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\ts := newStorage(t)\n\t\t\ttest.run(t, s)\n\t\t\ts.Close()\n\t\t})\n\t}\n}\n\n// RunTests runs a set of conformance tests against a storage. newStorage should\n// return an initialized but empty storage. The storage will be closed at the\n// end of each test run.\nfunc RunTests(t *testing.T, newStorage func(t *testing.T) storage.Storage) {\n\trunTests(t, newStorage, []subTest{\n\t\t{\"AuthCodeCRUD\", testAuthCodeCRUD},\n\t\t{\"AuthRequestCRUD\", testAuthRequestCRUD},\n\t\t{\"ClientCRUD\", testClientCRUD},\n\t\t{\"RefreshTokenCRUD\", testRefreshTokenCRUD},\n\t\t{\"PasswordCRUD\", testPasswordCRUD},\n\t\t{\"KeysCRUD\", testKeysCRUD},\n\t\t{\"OfflineSessionCRUD\", testOfflineSessionCRUD},\n\t\t{\"ConnectorCRUD\", testConnectorCRUD},\n\t\t{\"GarbageCollection\", testGC},\n\t\t{\"TimezoneSupport\", testTimezones},\n\t\t{\"DeviceRequestCRUD\", testDeviceRequestCRUD},\n\t\t{\"DeviceTokenCRUD\", testDeviceTokenCRUD},\n\t\t{\"UserIdentityCRUD\", testUserIdentityCRUD},\n\t\t{\"AuthSessionCRUD\", testAuthSessionCRUD},\n\t})\n}\n\nfunc mustLoadJWK(b string) *jose.JSONWebKey {\n\tvar jwt jose.JSONWebKey\n\tif err := jwt.UnmarshalJSON([]byte(b)); err != nil {\n\t\tpanic(err)\n\t}\n\treturn &jwt\n}\n\nfunc mustBeErrNotFound(t *testing.T, kind string, err error) {\n\tswitch {\n\tcase err == nil:\n\t\tt.Errorf(\"deleting nonexistent %s should return an error\", kind)\n\tcase err != storage.ErrNotFound:\n\t\tt.Errorf(\"deleting %s expected storage.ErrNotFound, got %v\", kind, err)\n\t}\n}\n\nfunc mustBeErrAlreadyExists(t *testing.T, kind string, err error) {\n\tswitch {\n\tcase err == nil:\n\t\tt.Errorf(\"attempting to create an existing %s should return an error\", kind)\n\tcase err != storage.ErrAlreadyExists:\n\t\tt.Errorf(\"creating an existing %s expected storage.ErrAlreadyExists, got %v\", kind, err)\n\t}\n}\n\nfunc testAuthRequestCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tcodeChallenge := storage.PKCE{\n\t\tCodeChallenge:       \"code_challenge_test\",\n\t\tCodeChallengeMethod: \"plain\",\n\t}\n\n\ta1 := storage.AuthRequest{\n\t\tID:                  storage.NewID(),\n\t\tClientID:            \"client1\",\n\t\tResponseTypes:       []string{\"code\"},\n\t\tScopes:              []string{\"openid\", \"email\"},\n\t\tRedirectURI:         \"https://localhost:80/callback\",\n\t\tNonce:               \"foo\",\n\t\tState:               \"bar\",\n\t\tForceApprovalPrompt: true,\n\t\tLoggedIn:            true,\n\t\tExpiry:              neverExpire,\n\t\tAuthTime:            defaultAuthTime,\n\t\tConnectorID:         \"ldap\",\n\t\tConnectorData:       []byte(`{\"some\":\"data\"}`),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tPKCE:    codeChallenge,\n\t\tHMACKey: []byte(\"hmac_key\"),\n\t}\n\n\tidentity := storage.Claims{Email: \"foobar\"}\n\n\tif err := s.CreateAuthRequest(ctx, a1); err != nil {\n\t\tt.Fatalf(\"failed creating auth request: %v\", err)\n\t}\n\n\t// Attempt to create same AuthRequest twice.\n\terr := s.CreateAuthRequest(ctx, a1)\n\tmustBeErrAlreadyExists(t, \"auth request\", err)\n\n\ta2 := storage.AuthRequest{\n\t\tID:                  storage.NewID(),\n\t\tClientID:            \"client2\",\n\t\tResponseTypes:       []string{\"code\"},\n\t\tScopes:              []string{\"openid\", \"email\"},\n\t\tRedirectURI:         \"https://localhost:80/callback\",\n\t\tNonce:               \"bar\",\n\t\tState:               \"foo\",\n\t\tForceApprovalPrompt: true,\n\t\tLoggedIn:            true,\n\t\tExpiry:              neverExpire,\n\t\tAuthTime:            defaultAuthTime,\n\t\tConnectorID:         \"ldap\",\n\t\tConnectorData:       []byte(`{\"some\":\"data\"}`),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"2\",\n\t\t\tUsername:      \"john\",\n\t\t\tEmail:         \"john.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\"},\n\t\t},\n\t\tHMACKey: []byte(\"hmac_key\"),\n\t}\n\n\tif err := s.CreateAuthRequest(ctx, a2); err != nil {\n\t\tt.Fatalf(\"failed creating auth request: %v\", err)\n\t}\n\n\tif err := s.UpdateAuthRequest(ctx, a1.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) {\n\t\told.Claims = identity\n\t\told.ConnectorID = \"connID\"\n\t\treturn old, nil\n\t}); err != nil {\n\t\tt.Fatalf(\"failed to update auth request: %v\", err)\n\t}\n\n\tgot, err := s.GetAuthRequest(ctx, a1.ID)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get auth req: %v\", err)\n\t}\n\tif !reflect.DeepEqual(got.Claims, identity) {\n\t\tt.Fatalf(\"update failed, wanted identity=%#v got %#v\", identity, got.Claims)\n\t}\n\n\tif !reflect.DeepEqual(got.PKCE, codeChallenge) {\n\t\tt.Fatalf(\"storage does not support PKCE, wanted challenge=%#v got %#v\", codeChallenge, got.PKCE)\n\t}\n\n\tif err := s.DeleteAuthRequest(ctx, a1.ID); err != nil {\n\t\tt.Fatalf(\"failed to delete auth request: %v\", err)\n\t}\n\n\tif err := s.DeleteAuthRequest(ctx, a2.ID); err != nil {\n\t\tt.Fatalf(\"failed to delete auth request: %v\", err)\n\t}\n\n\t_, err = s.GetAuthRequest(ctx, a1.ID)\n\tmustBeErrNotFound(t, \"auth request\", err)\n}\n\nfunc testAuthCodeCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\ta1 := storage.AuthCode{\n\t\tID:            storage.NewID(),\n\t\tClientID:      \"client1\",\n\t\tRedirectURI:   \"https://localhost:80/callback\",\n\t\tNonce:         \"foobar\",\n\t\tScopes:        []string{\"openid\", \"email\"},\n\t\tExpiry:        neverExpire,\n\t\tAuthTime:      defaultAuthTime,\n\t\tConnectorID:   \"ldap\",\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       \"12345\",\n\t\t\tCodeChallengeMethod: \"Whatever\",\n\t\t},\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t}\n\n\tif err := s.CreateAuthCode(ctx, a1); err != nil {\n\t\tt.Fatalf(\"failed creating auth code: %v\", err)\n\t}\n\n\ta2 := storage.AuthCode{\n\t\tID:            storage.NewID(),\n\t\tClientID:      \"client2\",\n\t\tRedirectURI:   \"https://localhost:80/callback\",\n\t\tNonce:         \"foobar\",\n\t\tScopes:        []string{\"openid\", \"email\"},\n\t\tExpiry:        neverExpire,\n\t\tAuthTime:      defaultAuthTime,\n\t\tConnectorID:   \"ldap\",\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"2\",\n\t\t\tUsername:      \"john\",\n\t\t\tEmail:         \"john.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\"},\n\t\t},\n\t}\n\n\t// Attempt to create same AuthCode twice.\n\terr := s.CreateAuthCode(ctx, a1)\n\tmustBeErrAlreadyExists(t, \"auth code\", err)\n\n\tif err := s.CreateAuthCode(ctx, a2); err != nil {\n\t\tt.Fatalf(\"failed creating auth code: %v\", err)\n\t}\n\n\tgot, err := s.GetAuthCode(ctx, a1.ID)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get auth code: %v\", err)\n\t}\n\tif a1.Expiry.Unix() != got.Expiry.Unix() {\n\t\tt.Errorf(\"auth code expiry did not match want=%s vs got=%s\", a1.Expiry, got.Expiry)\n\t}\n\tgot.Expiry = a1.Expiry     // time fields do not compare well\n\tgot.AuthTime = a1.AuthTime // time fields do not compare well\n\tif diff := pretty.Compare(a1, got); diff != \"\" {\n\t\tt.Errorf(\"auth code retrieved from storage did not match: %s\", diff)\n\t}\n\n\tif err := s.DeleteAuthCode(ctx, a1.ID); err != nil {\n\t\tt.Fatalf(\"delete auth code: %v\", err)\n\t}\n\n\tif err := s.DeleteAuthCode(ctx, a2.ID); err != nil {\n\t\tt.Fatalf(\"delete auth code: %v\", err)\n\t}\n\n\t_, err = s.GetAuthCode(ctx, a1.ID)\n\tmustBeErrNotFound(t, \"auth code\", err)\n}\n\nfunc testClientCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tid1 := storage.NewID()\n\tc1 := storage.Client{\n\t\tID:                id1,\n\t\tSecret:            \"foobar\",\n\t\tRedirectURIs:      []string{\"foo://bar.com/\", \"https://auth.example.com\"},\n\t\tName:              \"dex client\",\n\t\tLogoURL:           \"https://goo.gl/JIyzIC\",\n\t\tAllowedConnectors: []string{\"github\", \"google\"},\n\t}\n\terr := s.DeleteClient(ctx, id1)\n\tmustBeErrNotFound(t, \"client\", err)\n\n\tif err := s.CreateClient(ctx, c1); err != nil {\n\t\tt.Fatalf(\"create client: %v\", err)\n\t}\n\n\t// Attempt to create same Client twice.\n\terr = s.CreateClient(ctx, c1)\n\tmustBeErrAlreadyExists(t, \"client\", err)\n\n\tid2 := storage.NewID()\n\tc2 := storage.Client{\n\t\tID:           id2,\n\t\tSecret:       \"barfoo\",\n\t\tRedirectURIs: []string{\"foo://bar.com/\", \"https://auth.example.com\"},\n\t\tName:         \"dex client\",\n\t\tLogoURL:      \"https://goo.gl/JIyzIC\",\n\t}\n\n\tif err := s.CreateClient(ctx, c2); err != nil {\n\t\tt.Fatalf(\"create client: %v\", err)\n\t}\n\n\tgetAndCompare := func(_ string, want storage.Client) {\n\t\tgc, err := s.GetClient(ctx, id1)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"get client: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tif diff := pretty.Compare(want, gc); diff != \"\" {\n\t\t\tt.Errorf(\"client retrieved from storage did not match: %s\", diff)\n\t\t}\n\t}\n\n\tgetAndCompare(id1, c1)\n\n\tnewSecret := \"barfoo\"\n\terr = s.UpdateClient(ctx, id1, func(old storage.Client) (storage.Client, error) {\n\t\told.Secret = newSecret\n\t\treturn old, nil\n\t})\n\tif err != nil {\n\t\tt.Errorf(\"update client: %v\", err)\n\t}\n\tc1.Secret = newSecret\n\tgetAndCompare(id1, c1)\n\n\tif err := s.DeleteClient(ctx, id1); err != nil {\n\t\tt.Fatalf(\"delete client: %v\", err)\n\t}\n\n\tif err := s.DeleteClient(ctx, id2); err != nil {\n\t\tt.Fatalf(\"delete client: %v\", err)\n\t}\n\n\t_, err = s.GetClient(ctx, id1)\n\tmustBeErrNotFound(t, \"client\", err)\n}\n\nfunc testRefreshTokenCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tid := storage.NewID()\n\trefresh := storage.RefreshToken{\n\t\tID:            id,\n\t\tToken:         \"bar\",\n\t\tObsoleteToken: \"\",\n\t\tNonce:         \"foo\",\n\t\tClientID:      \"client_id\",\n\t\tConnectorID:   \"client_secret\",\n\t\tScopes:        []string{\"openid\", \"email\", \"profile\"},\n\t\tCreatedAt:     time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:      time.Now().UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t}\n\tif err := s.CreateRefresh(ctx, refresh); err != nil {\n\t\tt.Fatalf(\"create refresh token: %v\", err)\n\t}\n\n\t// Attempt to create same Refresh Token twice.\n\terr := s.CreateRefresh(ctx, refresh)\n\tmustBeErrAlreadyExists(t, \"refresh token\", err)\n\n\tgetAndCompare := func(id string, want storage.RefreshToken) {\n\t\tgr, err := s.GetRefresh(ctx, id)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"get refresh: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif diff := pretty.Compare(gr.CreatedAt.UnixNano(), gr.CreatedAt.UnixNano()); diff != \"\" {\n\t\t\tt.Errorf(\"refresh token created timestamp retrieved from storage did not match: %s\", diff)\n\t\t}\n\n\t\tif diff := pretty.Compare(gr.LastUsed.UnixNano(), gr.LastUsed.UnixNano()); diff != \"\" {\n\t\t\tt.Errorf(\"refresh token last used timestamp retrieved from storage did not match: %s\", diff)\n\t\t}\n\n\t\tgr.CreatedAt = time.Time{}\n\t\tgr.LastUsed = time.Time{}\n\t\twant.CreatedAt = time.Time{}\n\t\twant.LastUsed = time.Time{}\n\n\t\tif diff := pretty.Compare(want, gr); diff != \"\" {\n\t\t\tt.Errorf(\"refresh token retrieved from storage did not match: %s\", diff)\n\t\t}\n\t}\n\n\tgetAndCompare(id, refresh)\n\n\tid2 := storage.NewID()\n\trefresh2 := storage.RefreshToken{\n\t\tID:            id2,\n\t\tToken:         \"bar_2\",\n\t\tObsoleteToken: refresh.Token,\n\t\tNonce:         \"foo_2\",\n\t\tClientID:      \"client_id_2\",\n\t\tConnectorID:   \"client_secret\",\n\t\tScopes:        []string{\"openid\", \"email\", \"profile\"},\n\t\tCreatedAt:     time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:      time.Now().UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"2\",\n\t\t\tUsername:      \"john\",\n\t\t\tEmail:         \"john.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t}\n\n\tif err := s.CreateRefresh(ctx, refresh2); err != nil {\n\t\tt.Fatalf(\"create second refresh token: %v\", err)\n\t}\n\n\tgetAndCompare(id2, refresh2)\n\n\tupdatedAt := time.Now().UTC().Round(time.Millisecond)\n\n\tupdater := func(r storage.RefreshToken) (storage.RefreshToken, error) {\n\t\tr.Token = \"spam\"\n\t\tr.LastUsed = updatedAt\n\t\treturn r, nil\n\t}\n\tif err := s.UpdateRefreshToken(ctx, id, updater); err != nil {\n\t\tt.Errorf(\"failed to update refresh token: %v\", err)\n\t}\n\trefresh.Token = \"spam\"\n\trefresh.LastUsed = updatedAt\n\tgetAndCompare(id, refresh)\n\n\t// Ensure that updating the first token doesn't impact the second. Issue #847.\n\tgetAndCompare(id2, refresh2)\n\n\tif err := s.DeleteRefresh(ctx, id); err != nil {\n\t\tt.Fatalf(\"failed to delete refresh request: %v\", err)\n\t}\n\n\tif err := s.DeleteRefresh(ctx, id2); err != nil {\n\t\tt.Fatalf(\"failed to delete refresh request: %v\", err)\n\t}\n\n\t_, err = s.GetRefresh(ctx, id)\n\tmustBeErrNotFound(t, \"refresh token\", err)\n}\n\ntype byEmail []storage.Password\n\nfunc (n byEmail) Len() int           { return len(n) }\nfunc (n byEmail) Less(i, j int) bool { return n[i].Email < n[j].Email }\nfunc (n byEmail) Swap(i, j int)      { n[i], n[j] = n[j], n[i] }\n\nfunc boolPtr(v bool) *bool {\n\treturn &v\n}\n\nfunc testPasswordCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\t// Use bcrypt.MinCost to keep the tests short.\n\tpasswordHash1, err := bcrypt.GenerateFromPassword([]byte(\"secret\"), bcrypt.MinCost)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpassword1 := storage.Password{\n\t\tEmail:             \"jane@example.com\",\n\t\tHash:              passwordHash1,\n\t\tUsername:          \"jane\",\n\t\tName:              \"Jane Doe\",\n\t\tPreferredUsername: \"jane-public\",\n\t\tEmailVerified:     boolPtr(true),\n\t\tUserID:            \"foobar\",\n\t\tGroups:            []string{\"team-a\", \"team-a/admins\"},\n\t}\n\tif err := s.CreatePassword(ctx, password1); err != nil {\n\t\tt.Fatalf(\"create password token: %v\", err)\n\t}\n\n\t// Attempt to create same Password twice.\n\terr = s.CreatePassword(ctx, password1)\n\tmustBeErrAlreadyExists(t, \"password\", err)\n\n\tpasswordHash2, err := bcrypt.GenerateFromPassword([]byte(\"password\"), bcrypt.MinCost)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpassword2 := storage.Password{\n\t\tEmail:             \"john@example.com\",\n\t\tHash:              passwordHash2,\n\t\tUsername:          \"john\",\n\t\tName:              \"John Smith\",\n\t\tPreferredUsername: \"john-public\",\n\t\tEmailVerified:     boolPtr(false),\n\t\tUserID:            \"barfoo\",\n\t\tGroups:            []string{\"team-b\"},\n\t}\n\tif err := s.CreatePassword(ctx, password2); err != nil {\n\t\tt.Fatalf(\"create password token: %v\", err)\n\t}\n\n\tgetAndCompare := func(id string, want storage.Password) {\n\t\tgr, err := s.GetPassword(ctx, id)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"get password %q: %v\", id, err)\n\t\t\treturn\n\t\t}\n\t\tif diff := pretty.Compare(want, gr); diff != \"\" {\n\t\t\tt.Errorf(\"password retrieved from storage did not match: %s\", diff)\n\t\t}\n\t}\n\n\tgetAndCompare(\"jane@example.com\", password1)\n\tgetAndCompare(\"JANE@example.com\", password1) // Emails should be case insensitive\n\n\tif err := s.UpdatePassword(ctx, password1.Email, func(old storage.Password) (storage.Password, error) {\n\t\told.Username = \"jane doe\"\n\t\treturn old, nil\n\t}); err != nil {\n\t\tt.Fatalf(\"failed to update auth request: %v\", err)\n\t}\n\n\tpassword1.Username = \"jane doe\"\n\tgetAndCompare(\"jane@example.com\", password1)\n\n\tvar passwordList []storage.Password\n\tpasswordList = append(passwordList, password1, password2)\n\n\tlistAndCompare := func(want []storage.Password) {\n\t\tpasswords, err := s.ListPasswords(ctx)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"list password: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tsort.Sort(byEmail(want))\n\t\tsort.Sort(byEmail(passwords))\n\t\tif diff := pretty.Compare(want, passwords); diff != \"\" {\n\t\t\tt.Errorf(\"password list retrieved from storage did not match: %s\", diff)\n\t\t}\n\t}\n\n\tlistAndCompare(passwordList)\n\n\tif err := s.DeletePassword(ctx, password1.Email); err != nil {\n\t\tt.Fatalf(\"failed to delete password: %v\", err)\n\t}\n\n\tif err := s.DeletePassword(ctx, password2.Email); err != nil {\n\t\tt.Fatalf(\"failed to delete password: %v\", err)\n\t}\n\n\t_, err = s.GetPassword(ctx, password1.Email)\n\tmustBeErrNotFound(t, \"password\", err)\n}\n\nfunc testOfflineSessionCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tuserID1 := storage.NewID()\n\tsession1 := storage.OfflineSessions{\n\t\tUserID:        userID1,\n\t\tConnID:        \"Conn1\",\n\t\tRefresh:       make(map[string]*storage.RefreshTokenRef),\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t}\n\n\t// Creating an OfflineSession with an empty Refresh list to ensure that\n\t// an empty map is translated as expected by the storage.\n\tif err := s.CreateOfflineSessions(ctx, session1); err != nil {\n\t\tt.Fatalf(\"create offline session with UserID = %s: %v\", session1.UserID, err)\n\t}\n\n\t// Attempt to create same OfflineSession twice.\n\terr := s.CreateOfflineSessions(ctx, session1)\n\tmustBeErrAlreadyExists(t, \"offline session\", err)\n\n\tuserID2 := storage.NewID()\n\tsession2 := storage.OfflineSessions{\n\t\tUserID:        userID2,\n\t\tConnID:        \"Conn2\",\n\t\tRefresh:       make(map[string]*storage.RefreshTokenRef),\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t}\n\n\tif err := s.CreateOfflineSessions(ctx, session2); err != nil {\n\t\tt.Fatalf(\"create offline session with UserID = %s: %v\", session2.UserID, err)\n\t}\n\n\tgetAndCompare := func(userID string, connID string, want storage.OfflineSessions) {\n\t\tgr, err := s.GetOfflineSessions(ctx, userID, connID)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"get offline session: %v\", err)\n\t\t\treturn\n\t\t}\n\t\tif diff := pretty.Compare(want, gr); diff != \"\" {\n\t\t\tt.Errorf(\"offline session retrieved from storage did not match: %s\", diff)\n\t\t}\n\t}\n\n\tgetAndCompare(userID1, \"Conn1\", session1)\n\n\tid := storage.NewID()\n\ttokenRef := storage.RefreshTokenRef{\n\t\tID:        id,\n\t\tClientID:  \"client_id\",\n\t\tCreatedAt: time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:  time.Now().UTC().Round(time.Millisecond),\n\t}\n\tsession1.Refresh[tokenRef.ClientID] = &tokenRef\n\n\tif err := s.UpdateOfflineSessions(ctx, session1.UserID, session1.ConnID, func(old storage.OfflineSessions) (storage.OfflineSessions, error) {\n\t\told.Refresh[tokenRef.ClientID] = &tokenRef\n\t\treturn old, nil\n\t}); err != nil {\n\t\tt.Fatalf(\"failed to update offline session: %v\", err)\n\t}\n\n\tgetAndCompare(userID1, \"Conn1\", session1)\n\n\tif err := s.DeleteOfflineSessions(ctx, session1.UserID, session1.ConnID); err != nil {\n\t\tt.Fatalf(\"failed to delete offline session: %v\", err)\n\t}\n\n\tif err := s.DeleteOfflineSessions(ctx, session2.UserID, session2.ConnID); err != nil {\n\t\tt.Fatalf(\"failed to delete offline session: %v\", err)\n\t}\n\n\t_, err = s.GetOfflineSessions(ctx, session1.UserID, session1.ConnID)\n\tmustBeErrNotFound(t, \"offline session\", err)\n}\n\nfunc testConnectorCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tid1 := storage.NewID()\n\tconfig1 := []byte(`{\"issuer\": \"https://accounts.google.com\"}`)\n\tc1 := storage.Connector{\n\t\tID:         id1,\n\t\tType:       \"Default\",\n\t\tName:       \"Default\",\n\t\tConfig:     config1,\n\t\tGrantTypes: []string{\"authorization_code\", \"refresh_token\"},\n\t}\n\n\tif err := s.CreateConnector(ctx, c1); err != nil {\n\t\tt.Fatalf(\"create connector with ID = %s: %v\", c1.ID, err)\n\t}\n\n\t// Attempt to create same Connector twice.\n\terr := s.CreateConnector(ctx, c1)\n\tmustBeErrAlreadyExists(t, \"connector\", err)\n\n\tid2 := storage.NewID()\n\tconfig2 := []byte(`{\"redirectURI\": \"http://127.0.0.1:5556/dex/callback\"}`)\n\tc2 := storage.Connector{\n\t\tID:     id2,\n\t\tType:   \"Mock\",\n\t\tName:   \"Mock\",\n\t\tConfig: config2,\n\t}\n\n\tif err := s.CreateConnector(ctx, c2); err != nil {\n\t\tt.Fatalf(\"create connector with ID = %s: %v\", c2.ID, err)\n\t}\n\n\tgetAndCompare := func(id string, want storage.Connector) {\n\t\tgr, err := s.GetConnector(ctx, id)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"get connector: %v\", err)\n\t\t\treturn\n\t\t}\n\t\t// ignore resource version comparison\n\t\tgr.ResourceVersion = \"\"\n\t\tif diff := pretty.Compare(want, gr); diff != \"\" {\n\t\t\tt.Errorf(\"connector retrieved from storage did not match: %s\", diff)\n\t\t}\n\t}\n\n\tgetAndCompare(id1, c1)\n\n\tif err := s.UpdateConnector(ctx, c1.ID, func(old storage.Connector) (storage.Connector, error) {\n\t\told.Type = \"oidc\"\n\t\told.GrantTypes = []string{\"urn:ietf:params:oauth:grant-type:token-exchange\"}\n\t\treturn old, nil\n\t}); err != nil {\n\t\tt.Fatalf(\"failed to update Connector: %v\", err)\n\t}\n\n\tc1.Type = \"oidc\"\n\tc1.GrantTypes = []string{\"urn:ietf:params:oauth:grant-type:token-exchange\"}\n\tgetAndCompare(id1, c1)\n\n\tconnectorList := []storage.Connector{c1, c2}\n\tlistAndCompare := func(want []storage.Connector) {\n\t\tconnectors, err := s.ListConnectors(ctx)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"list connectors: %v\", err)\n\t\t\treturn\n\t\t}\n\t\t// ignore resource version comparison\n\t\tfor i := range connectors {\n\t\t\tconnectors[i].ResourceVersion = \"\"\n\t\t}\n\t\tsort.Slice(connectors, func(i, j int) bool {\n\t\t\treturn connectors[i].Name < connectors[j].Name\n\t\t})\n\t\tif diff := pretty.Compare(want, connectors); diff != \"\" {\n\t\t\tt.Errorf(\"connector list retrieved from storage did not match: %s\", diff)\n\t\t}\n\t}\n\tlistAndCompare(connectorList)\n\n\tif err := s.DeleteConnector(ctx, c1.ID); err != nil {\n\t\tt.Fatalf(\"failed to delete connector: %v\", err)\n\t}\n\n\tif err := s.DeleteConnector(ctx, c2.ID); err != nil {\n\t\tt.Fatalf(\"failed to delete connector: %v\", err)\n\t}\n\n\t_, err = s.GetConnector(ctx, c1.ID)\n\tmustBeErrNotFound(t, \"connector\", err)\n}\n\nfunc testKeysCRUD(t *testing.T, s storage.Storage) {\n\tctx := context.TODO()\n\n\tupdateAndCompare := func(k storage.Keys) {\n\t\terr := s.UpdateKeys(ctx, func(oldKeys storage.Keys) (storage.Keys, error) {\n\t\t\treturn k, nil\n\t\t})\n\t\tif err != nil {\n\t\t\tt.Errorf(\"failed to update keys: %v\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif got, err := s.GetKeys(ctx); err != nil {\n\t\t\tt.Errorf(\"failed to get keys: %v\", err)\n\t\t} else {\n\t\t\tgot.NextRotation = got.NextRotation.UTC()\n\t\t\tif diff := pretty.Compare(k, got); diff != \"\" {\n\t\t\t\tt.Errorf(\"got keys did not equal expected: %s\", diff)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Postgres isn't as accurate with nano seconds as we'd like\n\tn := time.Now().UTC().Round(time.Second)\n\n\tkeys1 := storage.Keys{\n\t\tSigningKey:    jsonWebKeys[0].Private,\n\t\tSigningKeyPub: jsonWebKeys[0].Public,\n\t\tNextRotation:  n,\n\t}\n\n\tkeys2 := storage.Keys{\n\t\tSigningKey:    jsonWebKeys[2].Private,\n\t\tSigningKeyPub: jsonWebKeys[2].Public,\n\t\tNextRotation:  n.Add(time.Hour),\n\t\tVerificationKeys: []storage.VerificationKey{\n\t\t\t{\n\t\t\t\tPublicKey: jsonWebKeys[0].Public,\n\t\t\t\tExpiry:    n.Add(time.Hour),\n\t\t\t},\n\t\t\t{\n\t\t\t\tPublicKey: jsonWebKeys[1].Public,\n\t\t\t\tExpiry:    n.Add(time.Hour * 2),\n\t\t\t},\n\t\t},\n\t}\n\n\tupdateAndCompare(keys1)\n\tupdateAndCompare(keys2)\n}\n\nfunc testGC(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\test, err := time.LoadLocation(\"America/New_York\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tpst, err := time.LoadLocation(\"America/Los_Angeles\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\texpiry := time.Now().In(est)\n\tc := storage.AuthCode{\n\t\tID:            storage.NewID(),\n\t\tClientID:      \"foobar\",\n\t\tRedirectURI:   \"https://localhost:80/callback\",\n\t\tNonce:         \"foobar\",\n\t\tScopes:        []string{\"openid\", \"email\"},\n\t\tExpiry:        expiry,\n\t\tAuthTime:      defaultAuthTime,\n\t\tConnectorID:   \"ldap\",\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t}\n\n\tif err := s.CreateAuthCode(ctx, c); err != nil {\n\t\tt.Fatalf(\"failed creating auth code: %v\", err)\n\t}\n\n\tfor _, tz := range []*time.Location{time.UTC, est, pst} {\n\t\tresult, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t\t} else if result.AuthCodes != 0 || result.AuthRequests != 0 {\n\t\t\tt.Errorf(\"expected no garbage collection results, got %#v\", result)\n\t\t}\n\t\tif _, err := s.GetAuthCode(ctx, c.ID); err != nil {\n\t\t\tt.Errorf(\"expected to be able to get auth code after GC: %v\", err)\n\t\t}\n\t}\n\n\tif r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil {\n\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t} else if r.AuthCodes != 1 {\n\t\tt.Errorf(\"expected to garbage collect 1 objects, got %d\", r.AuthCodes)\n\t}\n\n\tif _, err := s.GetAuthCode(ctx, c.ID); err == nil {\n\t\tt.Errorf(\"expected auth code to be GC'd\")\n\t} else if err != storage.ErrNotFound {\n\t\tt.Errorf(\"expected storage.ErrNotFound, got %v\", err)\n\t}\n\n\ta := storage.AuthRequest{\n\t\tID:                  storage.NewID(),\n\t\tClientID:            \"foobar\",\n\t\tResponseTypes:       []string{\"code\"},\n\t\tScopes:              []string{\"openid\", \"email\"},\n\t\tRedirectURI:         \"https://localhost:80/callback\",\n\t\tNonce:               \"foo\",\n\t\tState:               \"bar\",\n\t\tForceApprovalPrompt: true,\n\t\tLoggedIn:            true,\n\t\tExpiry:              expiry,\n\t\tAuthTime:            defaultAuthTime,\n\t\tConnectorID:         \"ldap\",\n\t\tConnectorData:       []byte(`{\"some\":\"data\"}`),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tHMACKey: []byte(\"hmac_key\"),\n\t}\n\n\tif err := s.CreateAuthRequest(ctx, a); err != nil {\n\t\tt.Fatalf(\"failed creating auth request: %v\", err)\n\t}\n\n\tfor _, tz := range []*time.Location{time.UTC, est, pst} {\n\t\tresult, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t\t} else if result.AuthCodes != 0 || result.AuthRequests != 0 {\n\t\t\tt.Errorf(\"expected no garbage collection results, got %#v\", result)\n\t\t}\n\t\tif _, err := s.GetAuthRequest(ctx, a.ID); err != nil {\n\t\t\tt.Errorf(\"expected to be able to get auth request after GC: %v\", err)\n\t\t}\n\t}\n\n\tif r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil {\n\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t} else if r.AuthRequests != 1 {\n\t\tt.Errorf(\"expected to garbage collect 1 objects, got %d\", r.AuthRequests)\n\t}\n\n\tif _, err := s.GetAuthRequest(ctx, a.ID); err == nil {\n\t\tt.Errorf(\"expected auth request to be GC'd\")\n\t} else if err != storage.ErrNotFound {\n\t\tt.Errorf(\"expected storage.ErrNotFound, got %v\", err)\n\t}\n\n\td := storage.DeviceRequest{\n\t\tUserCode:     storage.NewUserCode(),\n\t\tDeviceCode:   storage.NewID(),\n\t\tClientID:     \"client1\",\n\t\tClientSecret: \"secret1\",\n\t\tScopes:       []string{\"openid\", \"email\"},\n\t\tExpiry:       expiry,\n\t}\n\n\tif err := s.CreateDeviceRequest(ctx, d); err != nil {\n\t\tt.Fatalf(\"failed creating device request: %v\", err)\n\t}\n\n\tfor _, tz := range []*time.Location{time.UTC, est, pst} {\n\t\tresult, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t\t} else if result.DeviceRequests != 0 {\n\t\t\tt.Errorf(\"expected no device garbage collection results, got %#v\", result)\n\t\t}\n\t\tif _, err := s.GetDeviceRequest(ctx, d.UserCode); err != nil {\n\t\t\tt.Errorf(\"expected to be able to get auth request after GC: %v\", err)\n\t\t}\n\t}\n\tif r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil {\n\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t} else if r.DeviceRequests != 1 {\n\t\tt.Errorf(\"expected to garbage collect 1 device request, got %d\", r.DeviceRequests)\n\t}\n\n\tif _, err := s.GetDeviceRequest(ctx, d.UserCode); err == nil {\n\t\tt.Errorf(\"expected device request to be GC'd\")\n\t} else if err != storage.ErrNotFound {\n\t\tt.Errorf(\"expected storage.ErrNotFound, got %v\", err)\n\t}\n\n\tdt := storage.DeviceToken{\n\t\tDeviceCode:          storage.NewID(),\n\t\tStatus:              \"pending\",\n\t\tToken:               \"foo\",\n\t\tExpiry:              expiry,\n\t\tLastRequestTime:     time.Now(),\n\t\tPollIntervalSeconds: 0,\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       \"challenge\",\n\t\t\tCodeChallengeMethod: \"S256\",\n\t\t},\n\t}\n\n\tif err := s.CreateDeviceToken(ctx, dt); err != nil {\n\t\tt.Fatalf(\"failed creating device token: %v\", err)\n\t}\n\n\tfor _, tz := range []*time.Location{time.UTC, est, pst} {\n\t\tresult, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t\t} else if result.DeviceTokens != 0 {\n\t\t\tt.Errorf(\"expected no device token garbage collection results, got %#v\", result)\n\t\t}\n\t\tif _, err := s.GetDeviceToken(ctx, dt.DeviceCode); err != nil {\n\t\t\tt.Errorf(\"expected to be able to get device token after GC: %v\", err)\n\t\t}\n\t}\n\tif r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil {\n\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t} else if r.DeviceTokens != 1 {\n\t\tt.Errorf(\"expected to garbage collect 1 device token, got %d\", r.DeviceTokens)\n\t}\n\n\tif _, err := s.GetDeviceToken(ctx, dt.DeviceCode); err == nil {\n\t\tt.Errorf(\"expected device token to be GC'd\")\n\t} else if err != storage.ErrNotFound {\n\t\tt.Errorf(\"expected storage.ErrNotFound, got %v\", err)\n\t}\n\n\t// Test auth session GC.\n\tauthSession := storage.AuthSession{\n\t\tUserID:      \"gc-user\",\n\t\tConnectorID: \"gc-conn\",\n\t\tNonce:       storage.NewID(),\n\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\"client1\": {Active: true, ExpiresAt: expiry.Add(time.Hour), LastActivity: expiry},\n\t\t},\n\t\tCreatedAt:      expiry.Add(-time.Hour),\n\t\tLastActivity:   expiry.Add(-time.Hour),\n\t\tAbsoluteExpiry: expiry,\n\t\tIdleExpiry:     expiry,\n\t}\n\n\tif err := s.CreateAuthSession(ctx, authSession); err != nil {\n\t\tt.Fatalf(\"failed creating auth session: %v\", err)\n\t}\n\n\t// GC before expiry should not delete.\n\tfor _, tz := range []*time.Location{time.UTC, est, pst} {\n\t\tresult, err := s.GarbageCollect(ctx, expiry.Add(-time.Hour).In(tz))\n\t\tif err != nil {\n\t\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t\t} else if result.AuthSessions != 0 {\n\t\t\tt.Errorf(\"expected no auth session garbage collection results, got %#v\", result)\n\t\t}\n\t\tif _, err := s.GetAuthSession(ctx, authSession.UserID, authSession.ConnectorID); err != nil {\n\t\t\tt.Errorf(\"expected to be able to get auth session after GC: %v\", err)\n\t\t}\n\t}\n\n\t// GC after expiry should delete.\n\tif r, err := s.GarbageCollect(ctx, expiry.Add(time.Hour)); err != nil {\n\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t} else if r.AuthSessions != 1 {\n\t\tt.Errorf(\"expected to garbage collect 1 auth session, got %d\", r.AuthSessions)\n\t}\n\n\tif _, err := s.GetAuthSession(ctx, authSession.UserID, authSession.ConnectorID); err == nil {\n\t\tt.Errorf(\"expected auth session to be GC'd\")\n\t} else if err != storage.ErrNotFound {\n\t\tt.Errorf(\"expected storage.ErrNotFound, got %v\", err)\n\t}\n\n\t// Test auth session GC: absolute expired, idle still valid.\n\tabsExpiredSession := storage.AuthSession{\n\t\tUserID:      \"gc-abs-expired\",\n\t\tConnectorID: \"gc-conn\",\n\t\tNonce:       storage.NewID(),\n\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\"client1\": {Active: true, ExpiresAt: expiry.Add(time.Hour), LastActivity: expiry},\n\t\t},\n\t\tCreatedAt:      expiry.Add(-25 * time.Hour),\n\t\tLastActivity:   expiry.Add(-time.Minute),\n\t\tAbsoluteExpiry: expiry.Add(-time.Hour), // expired\n\t\tIdleExpiry:     expiry.Add(time.Hour),  // still valid\n\t}\n\tif err := s.CreateAuthSession(ctx, absExpiredSession); err != nil {\n\t\tt.Fatalf(\"failed creating abs-expired auth session: %v\", err)\n\t}\n\tif r, err := s.GarbageCollect(ctx, expiry); err != nil {\n\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t} else if r.AuthSessions != 1 {\n\t\tt.Errorf(\"expected to garbage collect 1 auth session (absolute expired), got %d\", r.AuthSessions)\n\t}\n\tif _, err := s.GetAuthSession(ctx, absExpiredSession.UserID, absExpiredSession.ConnectorID); err == nil {\n\t\tt.Errorf(\"expected abs-expired auth session to be GC'd\")\n\t}\n\n\t// Test auth session GC: absolute still valid, idle expired.\n\tidleExpiredSession := storage.AuthSession{\n\t\tUserID:      \"gc-idle-expired\",\n\t\tConnectorID: \"gc-conn\",\n\t\tNonce:       storage.NewID(),\n\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\"client1\": {Active: true, ExpiresAt: expiry.Add(time.Hour), LastActivity: expiry},\n\t\t},\n\t\tCreatedAt:      expiry.Add(-time.Hour),\n\t\tLastActivity:   expiry.Add(-2 * time.Hour),\n\t\tAbsoluteExpiry: expiry.Add(23 * time.Hour), // still valid\n\t\tIdleExpiry:     expiry.Add(-time.Hour),     // expired\n\t}\n\tif err := s.CreateAuthSession(ctx, idleExpiredSession); err != nil {\n\t\tt.Fatalf(\"failed creating idle-expired auth session: %v\", err)\n\t}\n\tif r, err := s.GarbageCollect(ctx, expiry); err != nil {\n\t\tt.Errorf(\"garbage collection failed: %v\", err)\n\t} else if r.AuthSessions != 1 {\n\t\tt.Errorf(\"expected to garbage collect 1 auth session (idle expired), got %d\", r.AuthSessions)\n\t}\n\tif _, err := s.GetAuthSession(ctx, idleExpiredSession.UserID, idleExpiredSession.ConnectorID); err == nil {\n\t\tt.Errorf(\"expected idle-expired auth session to be GC'd\")\n\t}\n}\n\n// testTimezones tests that backends either fully support timezones or\n// do the correct standardization.\nfunc testTimezones(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\test, err := time.LoadLocation(\"America/New_York\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\t// Create an expiry with timezone info. Only expect backends to be\n\t// accurate to the millisecond\n\texpiry := time.Now().In(est).Round(time.Millisecond)\n\n\tc := storage.AuthCode{\n\t\tID:            storage.NewID(),\n\t\tClientID:      \"foobar\",\n\t\tRedirectURI:   \"https://localhost:80/callback\",\n\t\tNonce:         \"foobar\",\n\t\tScopes:        []string{\"openid\", \"email\"},\n\t\tExpiry:        expiry,\n\t\tAuthTime:      defaultAuthTime,\n\t\tConnectorID:   \"ldap\",\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t}\n\tif err := s.CreateAuthCode(ctx, c); err != nil {\n\t\tt.Fatalf(\"failed creating auth code: %v\", err)\n\t}\n\tgot, err := s.GetAuthCode(ctx, c.ID)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get auth code: %v\", err)\n\t}\n\n\t// Ensure that if the resulting time is converted to the same\n\t// timezone, it's the same value. We DO NOT expect timezones\n\t// to be preserved.\n\tgotTime := got.Expiry.In(est)\n\twantTime := expiry\n\tif !gotTime.Equal(wantTime) {\n\t\tt.Fatalf(\"expected expiry %v got %v\", wantTime, gotTime)\n\t}\n}\n\nfunc testDeviceRequestCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\td1 := storage.DeviceRequest{\n\t\tUserCode:     storage.NewUserCode(),\n\t\tDeviceCode:   storage.NewID(),\n\t\tClientID:     \"client1\",\n\t\tClientSecret: \"secret1\",\n\t\tScopes:       []string{\"openid\", \"email\"},\n\t\tExpiry:       neverExpire.Round(time.Second),\n\t}\n\n\tif err := s.CreateDeviceRequest(ctx, d1); err != nil {\n\t\tt.Fatalf(\"failed creating device request: %v\", err)\n\t}\n\n\t// Attempt to create same DeviceRequest twice.\n\terr := s.CreateDeviceRequest(ctx, d1)\n\tmustBeErrAlreadyExists(t, \"device request\", err)\n\n\tgot, err := s.GetDeviceRequest(ctx, d1.UserCode)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get device request: %v\", err)\n\t}\n\n\trequire.Equal(t, d1, got)\n\n\t// No manual deletes for device requests, will be handled by garbage collection routines\n\t// see testGC\n}\n\nfunc testDeviceTokenCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tcodeChallenge := storage.PKCE{\n\t\tCodeChallenge:       \"code_challenge_test\",\n\t\tCodeChallengeMethod: \"plain\",\n\t}\n\n\t// Create a Token\n\td1 := storage.DeviceToken{\n\t\tDeviceCode:          storage.NewID(),\n\t\tStatus:              \"pending\",\n\t\tToken:               storage.NewID(),\n\t\tExpiry:              neverExpire,\n\t\tLastRequestTime:     time.Now(),\n\t\tPollIntervalSeconds: 0,\n\t\tPKCE:                codeChallenge,\n\t}\n\n\tif err := s.CreateDeviceToken(ctx, d1); err != nil {\n\t\tt.Fatalf(\"failed creating device token: %v\", err)\n\t}\n\n\t// Attempt to create same Device Token twice.\n\terr := s.CreateDeviceToken(ctx, d1)\n\tmustBeErrAlreadyExists(t, \"device token\", err)\n\n\t// Update the device token, simulate a redemption\n\tif err := s.UpdateDeviceToken(ctx, d1.DeviceCode, func(old storage.DeviceToken) (storage.DeviceToken, error) {\n\t\told.Token = \"token data\"\n\t\told.Status = \"complete\"\n\t\treturn old, nil\n\t}); err != nil {\n\t\tt.Fatalf(\"failed to update device token: %v\", err)\n\t}\n\n\t// Retrieve the device token\n\tgot, err := s.GetDeviceToken(ctx, d1.DeviceCode)\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get device token: %v\", err)\n\t}\n\n\t// Validate expected result set\n\tif got.Status != \"complete\" {\n\t\tt.Fatalf(\"update failed, wanted token status=%v got %v\", \"complete\", got.Status)\n\t}\n\tif got.Token != \"token data\" {\n\t\tt.Fatalf(\"update failed, wanted token %v got %v\", \"token data\", got.Token)\n\t}\n\tif !reflect.DeepEqual(got.PKCE, codeChallenge) {\n\t\tt.Fatalf(\"storage does not support PKCE, wanted challenge=%#v got %#v\", codeChallenge, got.PKCE)\n\t}\n}\n\nfunc testUserIdentityCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\n\tnow := time.Now().UTC().Round(time.Millisecond)\n\n\tu1 := storage.UserIdentity{\n\t\tUserID:      \"user1\",\n\t\tConnectorID: \"conn1\",\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"user1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConsents:     make(map[string][]string),\n\t\tCreatedAt:    now,\n\t\tLastLogin:    now,\n\t\tBlockedUntil: time.Unix(0, 0).UTC(),\n\t}\n\n\t// Create with empty Consents map.\n\tif err := s.CreateUserIdentity(ctx, u1); err != nil {\n\t\tt.Fatalf(\"create user identity: %v\", err)\n\t}\n\n\t// Duplicate create should return ErrAlreadyExists.\n\terr := s.CreateUserIdentity(ctx, u1)\n\tmustBeErrAlreadyExists(t, \"user identity\", err)\n\n\t// Get and compare.\n\tgot, err := s.GetUserIdentity(ctx, u1.UserID, u1.ConnectorID)\n\tif err != nil {\n\t\tt.Fatalf(\"get user identity: %v\", err)\n\t}\n\n\tgot.CreatedAt = got.CreatedAt.UTC().Round(time.Millisecond)\n\tgot.LastLogin = got.LastLogin.UTC().Round(time.Millisecond)\n\tgot.BlockedUntil = got.BlockedUntil.UTC().Round(time.Millisecond)\n\tu1.BlockedUntil = u1.BlockedUntil.UTC().Round(time.Millisecond)\n\tif diff := pretty.Compare(u1, got); diff != \"\" {\n\t\tt.Errorf(\"user identity retrieved from storage did not match: %s\", diff)\n\t}\n\n\t// Update: add consent entry.\n\tif err := s.UpdateUserIdentity(ctx, u1.UserID, u1.ConnectorID, func(old storage.UserIdentity) (storage.UserIdentity, error) {\n\t\told.Consents[\"client1\"] = []string{\"openid\", \"email\"}\n\t\treturn old, nil\n\t}); err != nil {\n\t\tt.Fatalf(\"update user identity: %v\", err)\n\t}\n\n\t// Get and verify updated consents.\n\tgot, err = s.GetUserIdentity(ctx, u1.UserID, u1.ConnectorID)\n\tif err != nil {\n\t\tt.Fatalf(\"get user identity after update: %v\", err)\n\t}\n\twantConsents := map[string][]string{\"client1\": {\"openid\", \"email\"}}\n\tif diff := pretty.Compare(wantConsents, got.Consents); diff != \"\" {\n\t\tt.Errorf(\"user identity consents did not match after update: %s\", diff)\n\t}\n\n\t// List and verify.\n\tidentities, err := s.ListUserIdentities(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"list user identities: %v\", err)\n\t}\n\tif len(identities) != 1 {\n\t\tt.Fatalf(\"expected 1 user identity, got %d\", len(identities))\n\t}\n\n\t// Delete.\n\tif err := s.DeleteUserIdentity(ctx, u1.UserID, u1.ConnectorID); err != nil {\n\t\tt.Fatalf(\"delete user identity: %v\", err)\n\t}\n\n\t// Get deleted should return ErrNotFound.\n\t_, err = s.GetUserIdentity(ctx, u1.UserID, u1.ConnectorID)\n\tmustBeErrNotFound(t, \"user identity\", err)\n}\n\nfunc testAuthSessionCRUD(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\n\tnow := time.Now().UTC().Round(time.Millisecond)\n\n\tsession := storage.AuthSession{\n\t\tUserID:      \"user1\",\n\t\tConnectorID: \"conn1\",\n\t\tNonce:       storage.NewID(),\n\t\tClientStates: map[string]*storage.ClientAuthState{\n\t\t\t\"client1\": {\n\t\t\t\tActive:            true,\n\t\t\t\tExpiresAt:         now.Add(24 * time.Hour),\n\t\t\t\tLastActivity:      now,\n\t\t\t\tLastTokenIssuedAt: now,\n\t\t\t},\n\t\t},\n\t\tCreatedAt:      now,\n\t\tLastActivity:   now,\n\t\tIPAddress:      \"192.168.1.1\",\n\t\tUserAgent:      \"TestBrowser/1.0\",\n\t\tAbsoluteExpiry: now.Add(24 * time.Hour),\n\t\tIdleExpiry:     now.Add(1 * time.Hour),\n\t}\n\n\t// Create.\n\tif err := s.CreateAuthSession(ctx, session); err != nil {\n\t\tt.Fatalf(\"create auth session: %v\", err)\n\t}\n\n\t// Duplicate create should return ErrAlreadyExists.\n\terr := s.CreateAuthSession(ctx, session)\n\tmustBeErrAlreadyExists(t, \"auth session\", err)\n\n\t// Get and compare.\n\tgot, err := s.GetAuthSession(ctx, session.UserID, session.ConnectorID)\n\tif err != nil {\n\t\tt.Fatalf(\"get auth session: %v\", err)\n\t}\n\n\tgot.CreatedAt = got.CreatedAt.UTC().Round(time.Millisecond)\n\tgot.LastActivity = got.LastActivity.UTC().Round(time.Millisecond)\n\tgot.AbsoluteExpiry = got.AbsoluteExpiry.UTC().Round(time.Millisecond)\n\tgot.IdleExpiry = got.IdleExpiry.UTC().Round(time.Millisecond)\n\tfor _, cs := range got.ClientStates {\n\t\tcs.ExpiresAt = cs.ExpiresAt.UTC().Round(time.Millisecond)\n\t\tcs.LastActivity = cs.LastActivity.UTC().Round(time.Millisecond)\n\t\tcs.LastTokenIssuedAt = cs.LastTokenIssuedAt.UTC().Round(time.Millisecond)\n\t}\n\tif diff := pretty.Compare(session, got); diff != \"\" {\n\t\tt.Errorf(\"auth session retrieved from storage did not match: %s\", diff)\n\t}\n\n\t// Update: add a new client state.\n\tnewNow := now.Add(time.Minute)\n\tif err := s.UpdateAuthSession(ctx, session.UserID, session.ConnectorID, func(old storage.AuthSession) (storage.AuthSession, error) {\n\t\told.ClientStates[\"client2\"] = &storage.ClientAuthState{\n\t\t\tActive:       true,\n\t\t\tExpiresAt:    newNow.Add(24 * time.Hour),\n\t\t\tLastActivity: newNow,\n\t\t}\n\t\told.LastActivity = newNow\n\t\treturn old, nil\n\t}); err != nil {\n\t\tt.Fatalf(\"update auth session: %v\", err)\n\t}\n\n\t// Get and verify update.\n\tgot, err = s.GetAuthSession(ctx, session.UserID, session.ConnectorID)\n\tif err != nil {\n\t\tt.Fatalf(\"get auth session after update: %v\", err)\n\t}\n\tif len(got.ClientStates) != 2 {\n\t\tt.Fatalf(\"expected 2 client states, got %d\", len(got.ClientStates))\n\t}\n\tif got.ClientStates[\"client2\"] == nil {\n\t\tt.Fatal(\"expected client2 state to exist\")\n\t}\n\n\t// List and verify.\n\tsessions, err := s.ListAuthSessions(ctx)\n\tif err != nil {\n\t\tt.Fatalf(\"list auth sessions: %v\", err)\n\t}\n\tif len(sessions) != 1 {\n\t\tt.Fatalf(\"expected 1 auth session, got %d\", len(sessions))\n\t}\n\n\t// Delete.\n\tif err := s.DeleteAuthSession(ctx, session.UserID, session.ConnectorID); err != nil {\n\t\tt.Fatalf(\"delete auth session: %v\", err)\n\t}\n\n\t// Get deleted should return ErrNotFound.\n\t_, err = s.GetAuthSession(ctx, session.UserID, session.ConnectorID)\n\tmustBeErrNotFound(t, \"auth session\", err)\n}\n"
  },
  {
    "path": "storage/conformance/gen_jwks.go",
    "content": "//go:build ignore\n// +build ignore\n\n// This file is used to generate static JWKs for tests.\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"go/format\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"text/template\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n)\n\nfunc newUUID() string {\n\tu := make([]byte, 16)\n\tif _, err := io.ReadFull(rand.Reader, u); err != nil {\n\t\tpanic(err)\n\t}\n\n\tu[8] = (u[8] | 0x80) & 0xBF\n\tu[6] = (u[6] | 0x40) & 0x4F\n\n\treturn hex.EncodeToString(u)\n}\n\nvar tmpl = template.Must(template.New(\"jwks.go\").Parse(`\n// This file was generated by gen_jwks.go\n\npackage conformance\n\nimport jose \"github.com/go-jose/go-jose/v4\"\n\ntype keyPair struct {\n\tPublic  *jose.JSONWebKey\n\tPrivate *jose.JSONWebKey\n}\n\n// keys are generated beforehand so we don't have to generate RSA keys for every test.\nvar jsonWebKeys = []keyPair{\n\t{{ range $i, $pair := .Keys }}\n\t{\n\t\tPublic:  mustLoadJWK({{ $pair.Public }}),\n\t\tPrivate: mustLoadJWK({{ $pair.Private }}),\n\t},\n\t{{ end }}\n}\n`[1:])) // Remove the first newline.\n\ntype keyPair struct {\n\tPublic  string\n\tPrivate string\n}\n\nfunc main() {\n\tvar tmplData struct {\n\t\tKeys []keyPair\n\t}\n\tfor i := 0; i < 5; i++ {\n\t\t// TODO(ericchiang): Test with ECDSA keys.\n\t\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"gen rsa key: %v\", err)\n\t\t}\n\t\tpriv := jose.JSONWebKey{\n\t\t\tKey:       key,\n\t\t\tKeyID:     newUUID(),\n\t\t\tAlgorithm: \"RS256\",\n\t\t\tUse:       \"sig\",\n\t\t}\n\t\tpub := jose.JSONWebKey{\n\t\t\tKey:       key.Public(),\n\t\t\tKeyID:     newUUID(),\n\t\t\tAlgorithm: \"RS256\",\n\t\t\tUse:       \"sig\",\n\t\t}\n\n\t\tprivBytes, err := json.MarshalIndent(priv, \"\\t\\t\", \"\\t\")\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"marshal priv: %v\", err)\n\t\t}\n\t\tpubBytes, err := json.MarshalIndent(pub, \"\\t\\t\", \"\\t\")\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"marshal pub: %v\", err)\n\t\t}\n\t\ttmplData.Keys = append(tmplData.Keys, keyPair{\n\t\t\tPrivate: \"`\" + string(privBytes) + \"`\",\n\t\t\tPublic:  \"`\" + string(pubBytes) + \"`\",\n\t\t})\n\t}\n\tbuff := new(bytes.Buffer)\n\tif err := tmpl.Execute(buff, tmplData); err != nil {\n\t\tlog.Fatalf(\"execute tmpl: %v\", err)\n\t}\n\n\tout, err := format.Source(buff.Bytes())\n\tif err != nil {\n\t\tlog.Fatalf(\"gofmt failed: %v\", err)\n\t}\n\tif err := os.WriteFile(\"jwks.go\", out, 0o644); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "storage/conformance/jwks.go",
    "content": "// This file was generaged by gen_jwks.go\n\npackage conformance\n\nimport \"github.com/go-jose/go-jose/v4\"\n\ntype keyPair struct {\n\tPublic  *jose.JSONWebKey\n\tPrivate *jose.JSONWebKey\n}\n\n// keys are generated beforehand so we don't have to generate RSA keys for every test.\nvar jsonWebKeys = []keyPair{\n\t{\n\t\tPublic: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"8145b5b9243c41459a8fdaa12acbd371\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"34ls8E4onyEU_JKcxl8BMu2N6hK_D6aG2tOuCHJ_ka4rom8NmdJGdOQPC_fvKhcAxWeDktdAPislTT76Q4iMCC7DbM1aQhgRMaecKHBagc5ue2kSPM3oZPLqe6X-CxdxGTfXAvFIZM9JZTbQeJPcXFdn28iZ086xWPMdQKY5QTRKtoHQSN6EAQuuiuZsXrAC3lBZmE4tda6NoeYLb0UayGqiiFmtoIFJQ4NecI-EECT-mcjkPGWG0Ll5dCIUhGDl8sQSUrmBuaTDpPEzLGo-UtM3ay7AN0gOVN0mLIk2oyroXcVOA626LYNLVU0mz9PDpdkhWBeUfLL6i4HjUS3RaQ\",\n\t\t\t\"e\": \"AQAB\"\n\t\t}`),\n\t\tPrivate: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"f547defc90b34ec08caeb8b294591216\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"34ls8E4onyEU_JKcxl8BMu2N6hK_D6aG2tOuCHJ_ka4rom8NmdJGdOQPC_fvKhcAxWeDktdAPislTT76Q4iMCC7DbM1aQhgRMaecKHBagc5ue2kSPM3oZPLqe6X-CxdxGTfXAvFIZM9JZTbQeJPcXFdn28iZ086xWPMdQKY5QTRKtoHQSN6EAQuuiuZsXrAC3lBZmE4tda6NoeYLb0UayGqiiFmtoIFJQ4NecI-EECT-mcjkPGWG0Ll5dCIUhGDl8sQSUrmBuaTDpPEzLGo-UtM3ay7AN0gOVN0mLIk2oyroXcVOA626LYNLVU0mz9PDpdkhWBeUfLL6i4HjUS3RaQ\",\n\t\t\t\"e\": \"AQAB\",\n\t\t\t\"d\": \"3rABHsQ-I4jJZ3SHSfeLMjkFj5JtVCIJZiZK0Y9_Fpn0TjVjz0Fzfy9S7hFo6P1Rf1bH9JkLHuPMnU-H8Y8uMVikxtcse3uOZXEcWAzVnUsRNVBPItPeF_MHNXb_xfzsZrsCL6Q_Am6eJ36b4AMtG7DXflQxKphWhM5s7eKqVxDrkhaDPnALLRFjCvUZ_myQQ3Upn7gMgAbvfIY1fn9rXW_4CfxbxhcPJW5IOcu6bPvpQlfuFkXjF-gGCiNf5kv6Db0lpDOKX5l5T-KFGQ0dIOdasm8vL2GxCKZf55rKRCt0a28fwwH2p94ja-1qtPTc34V8F26LyVRgQgD3e-0aoQ\",\n\t\t\t\"p\": \"_WoAr3sgL5yfaqBL38yqx4hqSPZGdR6xTS64rhgZaVg14_W6xYmlPI7PmVBRW45Fk4tXhXjv9oMZH9HGrH2v4yqXLEq0gJr4VAPvRaN6p_kb_eCfLHCbNCYBAPNVUdFpOTvOmh7m0zYPrku7DZDnZQEN_A9hYcufjy0em-lV6Tc\",\n\t\t\t\"q\": \"4dFfwyYQmns1xwVEPABxpazk6nAluS-7yYSAc9A8D25nqm0mNWdPJvmpJS02xSDjIGfe0FtMr1XlPm3XHdUlIu2Z9Ex-J-kcs3lfs2UKmleQqJRXK4MahAEIV3vp0zG47hAJyzE3Oh4sVLFr3ZK9_-SenolCFv5eIikWa3Xg6l8\"\n\t\t}`),\n\t},\n\n\t{\n\t\tPublic: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"3a9365e41b114ec1b9288b214196e158\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"t3TrxLN5_z-x5X9kebkoPnoYnGAPqAXOVCGBTxcAqev_P8t6SyyeeITDiePhCctYp5dO-WHRkB7_BkUeHZOgoyCBarDkDifQSG7MCtlYDm0yiSij_0vqzJQx-6zlXb5ypwO0P1sAXrO_nO87u69w5yaKf0yEJMpSjU8BDKQ__nskZP2QJJsYwOeAI9aAM2oP8r7Im8KzLy9-mnFSqypxBnL24hFNzKOS_GyHs0tPLjVY7JNDtDOkwPQIQFzsdZSY88n6uYvV-MGu3O-Y3-xLwUqMlJOXFskhmp1AOUnb4JgQ9wEaZ7088PY3Ak0eZkrg2FQ3XRHSWhUCOb2xL5iTvw\",\n\t\t\t\"e\": \"AQAB\"\n\t\t}`),\n\t\tPrivate: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"c79418aaf8ee439bb2b0e28672d71584\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"t3TrxLN5_z-x5X9kebkoPnoYnGAPqAXOVCGBTxcAqev_P8t6SyyeeITDiePhCctYp5dO-WHRkB7_BkUeHZOgoyCBarDkDifQSG7MCtlYDm0yiSij_0vqzJQx-6zlXb5ypwO0P1sAXrO_nO87u69w5yaKf0yEJMpSjU8BDKQ__nskZP2QJJsYwOeAI9aAM2oP8r7Im8KzLy9-mnFSqypxBnL24hFNzKOS_GyHs0tPLjVY7JNDtDOkwPQIQFzsdZSY88n6uYvV-MGu3O-Y3-xLwUqMlJOXFskhmp1AOUnb4JgQ9wEaZ7088PY3Ak0eZkrg2FQ3XRHSWhUCOb2xL5iTvw\",\n\t\t\t\"e\": \"AQAB\",\n\t\t\t\"d\": \"T7-y0dIXQV8l7RbAza0wkmAvHKMhiy_i7m2WMZRVRIiDb-77HXyq8sb73ZBC_if4RPogaYYdPCJNSCN5oO_Qz7jMqV119bVW9HW9myW6AqNzaW5SRCNzUTVGuRoCpwqn-nRAwZ3EfmZy8DyK4d61HLaDVC0l8HxHAIiMcztfWjbfD2LjwWF2hF5VRG2-haDfT6Kwtz0zEXblvYxyPqVyKOFtuWDlzX8iP8_ryWaChpR-jTmwtm7663wcu4M9teMkdgubCIqkz0LLtd-97ZUM2ti70WO7AEqE6p1evnjfYt4HZpQlsn0psrgGLvX2oCIvmPQMfTjzmtsEC51F5CU-yQ\",\n\t\t\t\"p\": \"4xi5OdCP9n1ivD3CuMhcaoMrwkC1yVdYnJwaNXjIyuSUT0i_QmuRpViydpZsfiYEoNNczL_PwxlDNdl2ccbelBuoEDbrvAfz0G0-YVYuLJoEKQs_OjenIn_6AZlmn7zSQ0LjoZ1tTjOaKuueB2b8RVtF2pbZ_o1ApyWd3q6QjyU\",\n\t\t\t\"q\": \"zs5SF-jdzP9xThPTEmAa2yh6SI48KuwVwWXGjOQZThXVEfwo-iZNevPjg3b6gwY9fKi71-J75c1ng0QrgdDuRIackHFpSLaWgcIpN31-uyZl5X-uxpBZON1HeiYT8J2JhgbA9ZJ0_SUq3j4YSrFEGKSpBi741mqwS9CZ6NSN5BM\"\n\t\t}`),\n\t},\n\n\t{\n\t\tPublic: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"4c267fc23c7b44d6973a1722b7201849\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"yeZexEF1gOXd71iz9jRQR3EhgM2-o3mVO4O1fJYYQTh5APfrrbMhOGLvgK06vytREiY9_1awL7YfEnZzQynq9WTZpkwlAhYujHYf1RbGPeoXJS2cXKThfIhbeITEyhfepqzwU_f-RhvaLS3bydDi7F74oTO9njtLkGV2qNHH3B2uTFBy2G8VmDeHNQrUa868LQ9omrmWFkLnoZOoVPiLZD-5aZXOKJ0In5sg9B1EX1oaF-xejCTBX_8EJvvvKXH-GUZnHc3g3Rf3k4iXCJi8VMyjA8we3fgP8jp2P3Ofv6VOKG3vh8j5lI3ys_rctc2fu6CaNWNNZs9wbjpDVPuc0w\",\n\t\t\t\"e\": \"AQAB\"\n\t\t}`),\n\t\tPrivate: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"eec6ee158cb34d699be4baff419da383\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"yeZexEF1gOXd71iz9jRQR3EhgM2-o3mVO4O1fJYYQTh5APfrrbMhOGLvgK06vytREiY9_1awL7YfEnZzQynq9WTZpkwlAhYujHYf1RbGPeoXJS2cXKThfIhbeITEyhfepqzwU_f-RhvaLS3bydDi7F74oTO9njtLkGV2qNHH3B2uTFBy2G8VmDeHNQrUa868LQ9omrmWFkLnoZOoVPiLZD-5aZXOKJ0In5sg9B1EX1oaF-xejCTBX_8EJvvvKXH-GUZnHc3g3Rf3k4iXCJi8VMyjA8we3fgP8jp2P3Ofv6VOKG3vh8j5lI3ys_rctc2fu6CaNWNNZs9wbjpDVPuc0w\",\n\t\t\t\"e\": \"AQAB\",\n\t\t\t\"d\": \"IOFck5eZfElzMFSA0lrIrCnXa_OV1WeqjwuvFcAX6R86TZcSkbI3echa-ti7VYDHbi4-MIQ8oziErOwPb2O3OQmYjIWgDUvxfryKCJjx5glmhY59BXVwp2hJhUISDlt-ziQh63ratS46BNuQDLjxC8-XrCESA1_iuXxcq7emVclRKN2DpGehf2bZyjcZy-OEwvL1jLsvoY2jmY_2JOT4nFLqoelg5vENj69p8IR9Bpdzp0urngLZJ4-HqFGyfx3tEo4ZUF1M5xnoycBc5LMZjmElK66rjBRWPq9UwZwfqaeQh6wEA9siYw1V9yrNRUkq3Q6BErbXNDKBV36bRIiaIQ\",\n\t\t\t\"p\": \"_YirCr3Sfs9FkEFFMNsTZ2Wv8e5napONPtg1WUYOxG36k65EkPtlmZLWmiwmBk6592oND_S5WvbW4BbX5lRbEvNiRy9coVPst6lOOnLe69GJoI_GxoRyu_94qIS-VNPSQkyw4gfA1M-lMdfKpaTMv7fvVolvmDs5xN_fmXpl06M\",\n\t\t\t\"q\": \"y90gdyUcYzDX1u3-fCINzXbDcr80QEO3bjuG8p7feaYY2MP51t6j6MisNsQqcGKY7xFhpc-z8_cEIg1HJ3FSly-yejPj8RGavPX6NVGVHDNGwxxnm_i3kf-4MuDxwRSSHMlgVNAXuoH-3iicz-bNTVYM-5bYucZMvZHC6Ur2JRE\"\n\t\t}`),\n\t},\n\n\t{\n\t\tPublic: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"e10385c5384046f395fc6d9027db2f35\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"299cgJgPiu9CK8hGgQw3j8e-Y_u4-Tm6WXKOFHdjCUPV5EAWMOa34cQNt75KN8pxlIcnujnU6TpH4OPRCw1gA44rrk_uczIEULsTnt6UFuMtUY2r-2UW2BWg5rEHyLcNX_QCA80T9DVSxsWeN8S23YcVk9fVputIRU7ee7auOx3b6K3pkoQJBVUk-_ndaqwlX-JU2CQG52CH91CrDzN0WGUPrhMZOdL7ybv94l5ztBrnjaQupkt0FxTA1_m_tXTvxIgzzegaqXrJ1mJM-z2TxPUJUc_04JaGilPUkxU780jk_03d46Op-pdElgbZ52C9JT9b8nRnA-vHq4e2whY8Yw\",\n\t\t\t\"e\": \"AQAB\"\n\t\t}`),\n\t\tPrivate: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"8165cc507cd1492394be64575dfa8261\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"299cgJgPiu9CK8hGgQw3j8e-Y_u4-Tm6WXKOFHdjCUPV5EAWMOa34cQNt75KN8pxlIcnujnU6TpH4OPRCw1gA44rrk_uczIEULsTnt6UFuMtUY2r-2UW2BWg5rEHyLcNX_QCA80T9DVSxsWeN8S23YcVk9fVputIRU7ee7auOx3b6K3pkoQJBVUk-_ndaqwlX-JU2CQG52CH91CrDzN0WGUPrhMZOdL7ybv94l5ztBrnjaQupkt0FxTA1_m_tXTvxIgzzegaqXrJ1mJM-z2TxPUJUc_04JaGilPUkxU780jk_03d46Op-pdElgbZ52C9JT9b8nRnA-vHq4e2whY8Yw\",\n\t\t\t\"e\": \"AQAB\",\n\t\t\t\"d\": \"xh587o6WKr2uZV8gUHXettroLpWKtl-TD7hOWBi_j4ClgfdRR50NggwzxCZeH-l18LzcSkyEEefnDriZC5lws6NurrHtjbU6-Dep1VSAIiNwGXVLy8nqDKlog5ZvCigPkC-BhUVMPpexz9QP3faORAzNn5szNCX7yB_qD5WrZy20AUEoWtGPgxGW6xf5Lgu6zg2uQEEB1Z0hKjHV9seIiuQooMrSzpS1D7BLSTHOvM2Y2lXvQQokc3uQXnyT_soHPjHl00bcuJLJaRCmyHRTol7uh9MNe67eMy7pHYmmlwOvTDfW6meKCgoEXd1wKIrS9VRY7WP36ZRpJH6qv8vceQ\",\n\t\t\t\"p\": \"7sWEsknUaSlAJ-bGhsuFr_j15zupV9O-DLnLobASm4Z7Ylt1HhtPN1NCVzYFTCtltPBE_CXGaAPqw3wiERK3tgYSLV8yk57sU1H28Zsq65A1B-vdlO69-F_6djiGegYKTOO4CXt0VYB4hJ6Trwx_BNJmrAD_Ykjqsp5sR0gOrqU\",\n\t\t\t\"q\": \"67y_hzbi81IH2DxmHTQOfHgcLYe-TnrEQLGLQtfx8J0J_REf_fLBDL-pt_jy6WIvTAb-LgUIcieiXfhni1nPUw0f_I1SDNv02EYvP0vkfyQdJBR6sLi4jv0mpqyQxvGif9B4eM9Qjngm2Jclj3-el-RkMZOUyf3zGTNGLI3MmGc\"\n\t\t}`),\n\t},\n\n\t{\n\t\tPublic: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"941861b40500430da0d09ec213e00832\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"ub3SiNK-uIvSrUTyIPm1cITzuqPX_CIa6nZTDTP1tJ6PP_KufYz2eGLj9jppWLo_J7XQfKfIAKvET8Mq4HEcLQpNRN90KNyGML17JJtSgYJeLuB38BnalVUxpnycPKeGgoNJMu6t8tKYOtOfxtqTA6x8MnqMeify1cvEc5Tr4QmKjcLLHKcr1yMR7kG48i586bLdchtIBYeB298WXbQaKrgsEjZA0E1exfMnYHyvN12lMBxwhOJtcFu3mngZ7vTh179UKsP3yD8IdO5ITe_RIOmnUKuynW3PdkRUzCK5gS-xuqueGqEzJVIKBv0Hfom3eyDW5DjxpIZxlqkGhGyeNw\",\n\t\t\t\"e\": \"AQAB\"\n\t\t}`),\n\t\tPrivate: mustLoadJWK(`{\n\t\t\t\"use\": \"sig\",\n\t\t\t\"kty\": \"RSA\",\n\t\t\t\"kid\": \"c4c09817da9a42ae8d850aaba7b7cd82\",\n\t\t\t\"alg\": \"RS256\",\n\t\t\t\"n\": \"ub3SiNK-uIvSrUTyIPm1cITzuqPX_CIa6nZTDTP1tJ6PP_KufYz2eGLj9jppWLo_J7XQfKfIAKvET8Mq4HEcLQpNRN90KNyGML17JJtSgYJeLuB38BnalVUxpnycPKeGgoNJMu6t8tKYOtOfxtqTA6x8MnqMeify1cvEc5Tr4QmKjcLLHKcr1yMR7kG48i586bLdchtIBYeB298WXbQaKrgsEjZA0E1exfMnYHyvN12lMBxwhOJtcFu3mngZ7vTh179UKsP3yD8IdO5ITe_RIOmnUKuynW3PdkRUzCK5gS-xuqueGqEzJVIKBv0Hfom3eyDW5DjxpIZxlqkGhGyeNw\",\n\t\t\t\"e\": \"AQAB\",\n\t\t\t\"d\": \"29bQWSEWm1bjBDGWY3EqTwMNdtp1yPaU5O0nX3kgV6dT5VxXKkKtdc-WANkh1uKZ3WZUXTY4gpLKx504Im2965FF4z6XPcXFDes21R0BikfDMbh8PLJdBGLRYTwbr66YheDdwmq9d6nKg9X2RmZtmuuMFDL4EZ02zdVfr22TwcSCghC2gnV6CpHHeEatJBWbK1yE6cHqCeY9UTc_QnXmbZ0TYsQi4qCV1HqTJKZDtkzqZMPvMB5EP_my_SCxcfcIzt6qqujmuXCFiS658Up-Z4W5s0RINLoPmePG8zJVFBmWrQ8xiykCeL8z9XSvXoEo6ZJJC-KSjI6s-KsCfQqZ\",\n\t\t\t\"p\": \"8LzUJM2YgP7zG618rrFTav3gB2t1yMwFJy9d3J-pOkVFUq-4-74qEZz6H2RTUw7Ae5XEYdVIbRRQInpo0qO2MfLW8vtRexUNFFt1pBiVykq-KdkWcwPETyRD-huEEqswBhg33lFTUrY7BXRukbfNmVY7YfdagIJ5LZU0I-nGMqs\",\n\t\t\t\"q\": \"xYRoIFTTiXitKBFo0vvHAadqVV8gJq8bCxJZ4lFMpADlU-S8Me7aPmkhPmCDaw-ii940S46bTp9ueh6EJCttmG3cJm8r4YzjK-H1dnqeF_3dpq2pimVFlFILBKWojUHHWC4n0d1IVwdf8-xnDSiUzl9roFZV5IPy4mW1HMTZ4qU\"\n\t\t}`),\n\t},\n}\n"
  },
  {
    "path": "storage/conformance/transactions.go",
    "content": "package conformance\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"golang.org/x/crypto/bcrypt\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// RunTransactionTests runs a test suite aimed a verifying the transaction\n// guarantees of the storage interface. Atomic updates, deletes, etc. The\n// storage returned by newStorage will be closed at the end of each test run.\n//\n// This call is separate from RunTests because some storage perform extremely\n// poorly under deadlocks, such as SQLite3, while others may be working towards\n// conformance.\nfunc RunTransactionTests(t *testing.T, newStorage func(t *testing.T) storage.Storage) {\n\trunTests(t, newStorage, []subTest{\n\t\t{\"AuthRequestConcurrentUpdate\", testAuthRequestConcurrentUpdate},\n\t\t{\"ClientConcurrentUpdate\", testClientConcurrentUpdate},\n\t\t{\"PasswordConcurrentUpdate\", testPasswordConcurrentUpdate},\n\t\t{\"KeysConcurrentUpdate\", testKeysConcurrentUpdate},\n\t})\n}\n\n// RunConcurrencyTests runs tests that verify storage implementations handle\n// high-contention parallel updates correctly. Unlike RunTransactionTests,\n// these tests use real goroutine-based parallelism rather than nested calls,\n// and are safe to run on all storage backends (including those with non-reentrant locks).\nfunc RunConcurrencyTests(t *testing.T, newStorage func(t *testing.T) storage.Storage) {\n\trunTests(t, newStorage, []subTest{\n\t\t{\"RefreshTokenParallelUpdate\", testRefreshTokenParallelUpdate},\n\t})\n}\n\nfunc testClientConcurrentUpdate(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\tc := storage.Client{\n\t\tID:           storage.NewID(),\n\t\tSecret:       \"foobar\",\n\t\tRedirectURIs: []string{\"foo://bar.com/\", \"https://auth.example.com\"},\n\t\tName:         \"dex client\",\n\t\tLogoURL:      \"https://goo.gl/JIyzIC\",\n\t}\n\n\tif err := s.CreateClient(ctx, c); err != nil {\n\t\tt.Fatalf(\"create client: %v\", err)\n\t}\n\n\tvar err1, err2 error\n\n\terr1 = s.UpdateClient(ctx, c.ID, func(old storage.Client) (storage.Client, error) {\n\t\told.Secret = \"new secret 1\"\n\t\terr2 = s.UpdateClient(ctx, c.ID, func(old storage.Client) (storage.Client, error) {\n\t\t\told.Secret = \"new secret 2\"\n\t\t\treturn old, nil\n\t\t})\n\t\treturn old, nil\n\t})\n\n\tif (err1 == nil) == (err2 == nil) {\n\t\tt.Errorf(\"update client:\\nupdate1: %v\\nupdate2: %v\\n\", err1, err2)\n\t}\n}\n\nfunc testAuthRequestConcurrentUpdate(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\ta := storage.AuthRequest{\n\t\tID:                  storage.NewID(),\n\t\tClientID:            \"foobar\",\n\t\tResponseTypes:       []string{\"code\"},\n\t\tScopes:              []string{\"openid\", \"email\"},\n\t\tRedirectURI:         \"https://localhost:80/callback\",\n\t\tNonce:               \"foo\",\n\t\tState:               \"bar\",\n\t\tForceApprovalPrompt: true,\n\t\tLoggedIn:            true,\n\t\tExpiry:              neverExpire,\n\t\tAuthTime:            defaultAuthTime,\n\t\tConnectorID:         \"ldap\",\n\t\tConnectorData:       []byte(`{\"some\":\"data\"}`),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tHMACKey: []byte(\"hmac_key\"),\n\t}\n\n\tif err := s.CreateAuthRequest(ctx, a); err != nil {\n\t\tt.Fatalf(\"failed creating auth request: %v\", err)\n\t}\n\n\tvar err1, err2 error\n\n\terr1 = s.UpdateAuthRequest(ctx, a.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) {\n\t\told.State = \"state 1\"\n\t\terr2 = s.UpdateAuthRequest(ctx, a.ID, func(old storage.AuthRequest) (storage.AuthRequest, error) {\n\t\t\told.State = \"state 2\"\n\t\t\treturn old, nil\n\t\t})\n\t\treturn old, nil\n\t})\n\n\tif (err1 == nil) == (err2 == nil) {\n\t\tt.Errorf(\"update auth request:\\nupdate1: %v\\nupdate2: %v\\n\", err1, err2)\n\t}\n}\n\nfunc testPasswordConcurrentUpdate(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\t// Use bcrypt.MinCost to keep the tests short.\n\tpasswordHash, err := bcrypt.GenerateFromPassword([]byte(\"secret\"), bcrypt.MinCost)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tpassword := storage.Password{\n\t\tEmail:             \"jane@example.com\",\n\t\tHash:              passwordHash,\n\t\tUsername:          \"jane\",\n\t\tName:              \"Jane Doe\",\n\t\tPreferredUsername: \"jane-public\",\n\t\tEmailVerified:     boolPtr(true),\n\t\tUserID:            \"foobar\",\n\t\tGroups:            []string{\"team-a\"},\n\t}\n\tif err := s.CreatePassword(ctx, password); err != nil {\n\t\tt.Fatalf(\"create password token: %v\", err)\n\t}\n\n\tvar err1, err2 error\n\n\terr1 = s.UpdatePassword(ctx, password.Email, func(old storage.Password) (storage.Password, error) {\n\t\told.Username = \"user 1\"\n\t\terr2 = s.UpdatePassword(ctx, password.Email, func(old storage.Password) (storage.Password, error) {\n\t\t\told.Username = \"user 2\"\n\t\t\treturn old, nil\n\t\t})\n\t\treturn old, nil\n\t})\n\n\tif (err1 == nil) == (err2 == nil) {\n\t\tt.Errorf(\"update password: concurrent updates both returned no error\")\n\t}\n}\n\nfunc testKeysConcurrentUpdate(t *testing.T, s storage.Storage) {\n\t// Test twice. Once for a create, once for an update.\n\tfor i := 0; i < 2; i++ {\n\t\tn := time.Now().UTC().Round(time.Second)\n\t\tkeys1 := storage.Keys{\n\t\t\tSigningKey:    jsonWebKeys[i].Private,\n\t\t\tSigningKeyPub: jsonWebKeys[i].Public,\n\t\t\tNextRotation:  n,\n\t\t}\n\n\t\tkeys2 := storage.Keys{\n\t\t\tSigningKey:    jsonWebKeys[2].Private,\n\t\t\tSigningKeyPub: jsonWebKeys[2].Public,\n\t\t\tNextRotation:  n.Add(time.Hour),\n\t\t\tVerificationKeys: []storage.VerificationKey{\n\t\t\t\t{\n\t\t\t\t\tPublicKey: jsonWebKeys[0].Public,\n\t\t\t\t\tExpiry:    n.Add(time.Hour),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tPublicKey: jsonWebKeys[1].Public,\n\t\t\t\t\tExpiry:    n.Add(time.Hour * 2),\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tvar err1, err2 error\n\n\t\tctx := context.TODO()\n\t\terr1 = s.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) {\n\t\t\terr2 = s.UpdateKeys(ctx, func(old storage.Keys) (storage.Keys, error) {\n\t\t\t\treturn keys1, nil\n\t\t\t})\n\t\t\treturn keys2, nil\n\t\t})\n\n\t\tif (err1 == nil) == (err2 == nil) {\n\t\t\tt.Errorf(\"update keys: concurrent updates both returned no error\")\n\t\t}\n\t}\n}\n\n// testRefreshTokenParallelUpdate tests that many parallel updates to the same\n// refresh token are serialized correctly by the storage and no updates are lost.\n//\n// Each goroutine atomically increments a counter stored in the Token field.\n// After all goroutines finish, the counter must equal the number of successful updates.\n// A mismatch indicates lost updates due to broken atomicity.\nfunc testRefreshTokenParallelUpdate(t *testing.T, s storage.Storage) {\n\tctx := t.Context()\n\n\tid := storage.NewID()\n\trefresh := storage.RefreshToken{\n\t\tID:          id,\n\t\tToken:       \"0\",\n\t\tNonce:       \"foo\",\n\t\tClientID:    \"client_id\",\n\t\tConnectorID: \"connector_id\",\n\t\tScopes:      []string{\"openid\"},\n\t\tCreatedAt:   time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:    time.Now().UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:   \"1\",\n\t\t\tUsername: \"jane\",\n\t\t\tEmail:    \"jane@example.com\",\n\t\t},\n\t}\n\n\trequire.NoError(t, s.CreateRefresh(ctx, refresh))\n\n\tconst numWorkers = 100\n\n\ttype updateResult struct {\n\t\terr      error\n\t\tnewToken string // token value written by this worker's updater\n\t}\n\n\tvar wg sync.WaitGroup\n\tresults := make([]updateResult, numWorkers)\n\n\tfor i := range numWorkers {\n\t\twg.Add(1)\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tresults[i].err = s.UpdateRefreshToken(ctx, id, func(old storage.RefreshToken) (storage.RefreshToken, error) {\n\t\t\t\tcounter, _ := strconv.Atoi(old.Token)\n\t\t\t\told.Token = strconv.Itoa(counter + 1)\n\t\t\t\tresults[i].newToken = old.Token\n\t\t\t\treturn old, nil\n\t\t\t})\n\t\t}()\n\t}\n\n\twg.Wait()\n\n\terrCounts := map[string]int{}\n\tvar successes int\n\twrittenTokens := map[string]int{}\n\tfor _, r := range results {\n\t\tif r.err == nil {\n\t\t\tsuccesses++\n\t\t\twrittenTokens[r.newToken]++\n\t\t} else {\n\t\t\terrCounts[r.err.Error()]++\n\t\t}\n\t}\n\n\tfor msg, count := range errCounts {\n\t\tt.Logf(\"error (x%d): %s\", count, msg)\n\t}\n\n\tstored, err := s.GetRefresh(ctx, id)\n\trequire.NoError(t, err)\n\n\tcounter, err := strconv.Atoi(stored.Token)\n\trequire.NoError(t, err)\n\n\tt.Logf(\"parallel refresh token updates: %d/%d succeeded, final counter: %d\", successes, numWorkers, counter)\n\n\tif successes < numWorkers {\n\t\tt.Errorf(\"not all updates succeeded: %d/%d (some failed under contention)\", successes, numWorkers)\n\t}\n\n\tif counter != successes {\n\t\tt.Errorf(\"lost updates detected: %d successful updates but counter is %d\", successes, counter)\n\t}\n\n\t// Each successful updater must have seen a unique counter value.\n\t// Duplicates would mean two updaters read the same state — a sign of broken atomicity.\n\tfor token, count := range writtenTokens {\n\t\tif count > 1 {\n\t\t\tt.Errorf(\"token %q was written by %d updaters — concurrent updaters saw the same state\", token, count)\n\t\t}\n\t}\n\n\t// Successful updaters must have produced a contiguous sequence 1..N.\n\t// A gap would mean an updater saw stale state even though the write succeeded.\n\tfor i := 1; i <= successes; i++ {\n\t\tif writtenTokens[strconv.Itoa(i)] != 1 {\n\t\t\tt.Errorf(\"expected token %q to be written exactly once, got %d\", strconv.Itoa(i), writtenTokens[strconv.Itoa(i)])\n\t\t}\n\t}\n\n\t// The token stored in the database must match the highest value written.\n\t// This confirms that the last successful update is the one persisted.\n\tif stored.Token != strconv.Itoa(successes) {\n\t\tt.Errorf(\"stored token %q does not match expected final value %q\", stored.Token, strconv.Itoa(successes))\n\t}\n}\n"
  },
  {
    "path": "storage/doc.go",
    "content": "// Package storage defines the storage interface and types used by the server.\npackage storage\n"
  },
  {
    "path": "storage/ent/client/authcode.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateAuthCode saves provided auth code into the database.\nfunc (d *Database) CreateAuthCode(ctx context.Context, code storage.AuthCode) error {\n\t_, err := d.client.AuthCode.Create().\n\t\tSetID(code.ID).\n\t\tSetClientID(code.ClientID).\n\t\tSetScopes(code.Scopes).\n\t\tSetRedirectURI(code.RedirectURI).\n\t\tSetNonce(code.Nonce).\n\t\tSetClaimsUserID(code.Claims.UserID).\n\t\tSetClaimsEmail(code.Claims.Email).\n\t\tSetClaimsEmailVerified(code.Claims.EmailVerified).\n\t\tSetClaimsUsername(code.Claims.Username).\n\t\tSetClaimsPreferredUsername(code.Claims.PreferredUsername).\n\t\tSetClaimsGroups(code.Claims.Groups).\n\t\tSetCodeChallenge(code.PKCE.CodeChallenge).\n\t\tSetCodeChallengeMethod(code.PKCE.CodeChallengeMethod).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetExpiry(code.Expiry.UTC()).\n\t\tSetConnectorID(code.ConnectorID).\n\t\tSetConnectorData(code.ConnectorData).\n\t\tSetAuthTime(code.AuthTime).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create auth code: %w\", err)\n\t}\n\treturn nil\n}\n\n// GetAuthCode extracts an auth code from the database by id.\nfunc (d *Database) GetAuthCode(ctx context.Context, id string) (storage.AuthCode, error) {\n\tauthCode, err := d.client.AuthCode.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.AuthCode{}, convertDBError(\"get auth code: %w\", err)\n\t}\n\treturn toStorageAuthCode(authCode), nil\n}\n\n// DeleteAuthCode deletes an auth code from the database by id.\nfunc (d *Database) DeleteAuthCode(ctx context.Context, id string) error {\n\terr := d.client.AuthCode.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete auth code: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/authrequest.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateAuthRequest saves provided auth request into the database.\nfunc (d *Database) CreateAuthRequest(ctx context.Context, authRequest storage.AuthRequest) error {\n\t_, err := d.client.AuthRequest.Create().\n\t\tSetID(authRequest.ID).\n\t\tSetClientID(authRequest.ClientID).\n\t\tSetScopes(authRequest.Scopes).\n\t\tSetResponseTypes(authRequest.ResponseTypes).\n\t\tSetRedirectURI(authRequest.RedirectURI).\n\t\tSetState(authRequest.State).\n\t\tSetNonce(authRequest.Nonce).\n\t\tSetForceApprovalPrompt(authRequest.ForceApprovalPrompt).\n\t\tSetLoggedIn(authRequest.LoggedIn).\n\t\tSetClaimsUserID(authRequest.Claims.UserID).\n\t\tSetClaimsEmail(authRequest.Claims.Email).\n\t\tSetClaimsEmailVerified(authRequest.Claims.EmailVerified).\n\t\tSetClaimsUsername(authRequest.Claims.Username).\n\t\tSetClaimsPreferredUsername(authRequest.Claims.PreferredUsername).\n\t\tSetClaimsGroups(authRequest.Claims.Groups).\n\t\tSetCodeChallenge(authRequest.PKCE.CodeChallenge).\n\t\tSetCodeChallengeMethod(authRequest.PKCE.CodeChallengeMethod).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetExpiry(authRequest.Expiry.UTC()).\n\t\tSetConnectorID(authRequest.ConnectorID).\n\t\tSetConnectorData(authRequest.ConnectorData).\n\t\tSetHmacKey(authRequest.HMACKey).\n\t\tSetMfaValidated(authRequest.MFAValidated).\n\t\tSetPrompt(authRequest.Prompt).\n\t\tSetMaxAge(authRequest.MaxAge).\n\t\tSetAuthTime(authRequest.AuthTime).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create auth request: %w\", err)\n\t}\n\treturn nil\n}\n\n// GetAuthRequest extracts an auth request from the database by id.\nfunc (d *Database) GetAuthRequest(ctx context.Context, id string) (storage.AuthRequest, error) {\n\tauthRequest, err := d.client.AuthRequest.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.AuthRequest{}, convertDBError(\"get auth request: %w\", err)\n\t}\n\treturn toStorageAuthRequest(authRequest), nil\n}\n\n// DeleteAuthRequest deletes an auth request from the database by id.\nfunc (d *Database) DeleteAuthRequest(ctx context.Context, id string) error {\n\terr := d.client.AuthRequest.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete auth request: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdateAuthRequest changes an auth request by id using an updater function and saves it to the database.\nfunc (d *Database) UpdateAuthRequest(ctx context.Context, id string, updater func(old storage.AuthRequest) (storage.AuthRequest, error)) error {\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"update auth request tx: %w\", err)\n\t}\n\n\tauthRequest, err := tx.AuthRequest.Get(context.TODO(), id)\n\tif err != nil {\n\t\treturn rollback(tx, \"update auth request database: %w\", err)\n\t}\n\n\tnewAuthRequest, err := updater(toStorageAuthRequest(authRequest))\n\tif err != nil {\n\t\treturn rollback(tx, \"update auth request updating: %w\", err)\n\t}\n\n\t_, err = tx.AuthRequest.UpdateOneID(newAuthRequest.ID).\n\t\tSetClientID(newAuthRequest.ClientID).\n\t\tSetScopes(newAuthRequest.Scopes).\n\t\tSetResponseTypes(newAuthRequest.ResponseTypes).\n\t\tSetRedirectURI(newAuthRequest.RedirectURI).\n\t\tSetState(newAuthRequest.State).\n\t\tSetNonce(newAuthRequest.Nonce).\n\t\tSetForceApprovalPrompt(newAuthRequest.ForceApprovalPrompt).\n\t\tSetLoggedIn(newAuthRequest.LoggedIn).\n\t\tSetClaimsUserID(newAuthRequest.Claims.UserID).\n\t\tSetClaimsEmail(newAuthRequest.Claims.Email).\n\t\tSetClaimsEmailVerified(newAuthRequest.Claims.EmailVerified).\n\t\tSetClaimsUsername(newAuthRequest.Claims.Username).\n\t\tSetClaimsPreferredUsername(newAuthRequest.Claims.PreferredUsername).\n\t\tSetClaimsGroups(newAuthRequest.Claims.Groups).\n\t\tSetCodeChallenge(newAuthRequest.PKCE.CodeChallenge).\n\t\tSetCodeChallengeMethod(newAuthRequest.PKCE.CodeChallengeMethod).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetExpiry(newAuthRequest.Expiry.UTC()).\n\t\tSetConnectorID(newAuthRequest.ConnectorID).\n\t\tSetConnectorData(newAuthRequest.ConnectorData).\n\t\tSetHmacKey(newAuthRequest.HMACKey).\n\t\tSetMfaValidated(newAuthRequest.MFAValidated).\n\t\tSetPrompt(newAuthRequest.Prompt).\n\t\tSetMaxAge(newAuthRequest.MaxAge).\n\t\tSetAuthTime(newAuthRequest.AuthTime).\n\t\tSave(context.TODO())\n\tif err != nil {\n\t\treturn rollback(tx, \"update auth request uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update auth request commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/authsession.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateAuthSession saves provided auth session into the database.\nfunc (d *Database) CreateAuthSession(ctx context.Context, session storage.AuthSession) error {\n\tif session.ClientStates == nil {\n\t\tsession.ClientStates = make(map[string]*storage.ClientAuthState)\n\t}\n\tencodedStates, err := json.Marshal(session.ClientStates)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"encode client states auth session: %w\", err)\n\t}\n\n\tid := compositeKeyID(session.UserID, session.ConnectorID, d.hasher)\n\t_, err = d.client.AuthSession.Create().\n\t\tSetID(id).\n\t\tSetUserID(session.UserID).\n\t\tSetConnectorID(session.ConnectorID).\n\t\tSetNonce(session.Nonce).\n\t\tSetClientStates(encodedStates).\n\t\tSetCreatedAt(session.CreatedAt).\n\t\tSetLastActivity(session.LastActivity).\n\t\tSetIPAddress(session.IPAddress).\n\t\tSetUserAgent(session.UserAgent).\n\t\tSetAbsoluteExpiry(session.AbsoluteExpiry.UTC()).\n\t\tSetIdleExpiry(session.IdleExpiry.UTC()).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create auth session: %w\", err)\n\t}\n\treturn nil\n}\n\n// GetAuthSession extracts an auth session from the database by user ID and connector ID.\nfunc (d *Database) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) {\n\tid := compositeKeyID(userID, connectorID, d.hasher)\n\tauthSession, err := d.client.AuthSession.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.AuthSession{}, convertDBError(\"get auth session: %w\", err)\n\t}\n\treturn toStorageAuthSession(authSession), nil\n}\n\n// ListAuthSessions extracts all auth sessions from the database.\nfunc (d *Database) ListAuthSessions(ctx context.Context) ([]storage.AuthSession, error) {\n\tauthSessions, err := d.client.AuthSession.Query().All(ctx)\n\tif err != nil {\n\t\treturn nil, convertDBError(\"list auth sessions: %w\", err)\n\t}\n\n\tstorageAuthSessions := make([]storage.AuthSession, 0, len(authSessions))\n\tfor _, s := range authSessions {\n\t\tstorageAuthSessions = append(storageAuthSessions, toStorageAuthSession(s))\n\t}\n\treturn storageAuthSessions, nil\n}\n\n// DeleteAuthSession deletes an auth session from the database by user ID and connector ID.\nfunc (d *Database) DeleteAuthSession(ctx context.Context, userID, connectorID string) error {\n\tid := compositeKeyID(userID, connectorID, d.hasher)\n\terr := d.client.AuthSession.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete auth session: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdateAuthSession changes an auth session using an updater function.\nfunc (d *Database) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) error {\n\tid := compositeKeyID(userID, connectorID, d.hasher)\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update auth session tx: %w\", err)\n\t}\n\n\tauthSession, err := tx.AuthSession.Get(ctx, id)\n\tif err != nil {\n\t\treturn rollback(tx, \"update auth session database: %w\", err)\n\t}\n\n\tnewSession, err := updater(toStorageAuthSession(authSession))\n\tif err != nil {\n\t\treturn rollback(tx, \"update auth session updating: %w\", err)\n\t}\n\n\tif newSession.ClientStates == nil {\n\t\tnewSession.ClientStates = make(map[string]*storage.ClientAuthState)\n\t}\n\n\tencodedStates, err := json.Marshal(newSession.ClientStates)\n\tif err != nil {\n\t\treturn rollback(tx, \"encode client states auth session: %w\", err)\n\t}\n\n\t_, err = tx.AuthSession.UpdateOneID(id).\n\t\tSetClientStates(encodedStates).\n\t\tSetLastActivity(newSession.LastActivity).\n\t\tSetIPAddress(newSession.IPAddress).\n\t\tSetUserAgent(newSession.UserAgent).\n\t\tSetAbsoluteExpiry(newSession.AbsoluteExpiry.UTC()).\n\t\tSetIdleExpiry(newSession.IdleExpiry.UTC()).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update auth session updating: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update auth session commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/client.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateClient saves provided oauth2 client settings into the database.\nfunc (d *Database) CreateClient(ctx context.Context, client storage.Client) error {\n\t_, err := d.client.OAuth2Client.Create().\n\t\tSetID(client.ID).\n\t\tSetName(client.Name).\n\t\tSetSecret(client.Secret).\n\t\tSetPublic(client.Public).\n\t\tSetLogoURL(client.LogoURL).\n\t\tSetRedirectUris(client.RedirectURIs).\n\t\tSetTrustedPeers(client.TrustedPeers).\n\t\tSetAllowedConnectors(client.AllowedConnectors).\n\t\tSetMfaChain(client.MFAChain).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create oauth2 client: %w\", err)\n\t}\n\treturn nil\n}\n\n// ListClients extracts an array of oauth2 clients from the database.\nfunc (d *Database) ListClients(ctx context.Context) ([]storage.Client, error) {\n\tclients, err := d.client.OAuth2Client.Query().All(ctx)\n\tif err != nil {\n\t\treturn nil, convertDBError(\"list clients: %w\", err)\n\t}\n\n\tstorageClients := make([]storage.Client, 0, len(clients))\n\tfor _, c := range clients {\n\t\tstorageClients = append(storageClients, toStorageClient(c))\n\t}\n\treturn storageClients, nil\n}\n\n// GetClient extracts an oauth2 client from the database by id.\nfunc (d *Database) GetClient(ctx context.Context, id string) (storage.Client, error) {\n\tclient, err := d.client.OAuth2Client.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.Client{}, convertDBError(\"get client: %w\", err)\n\t}\n\treturn toStorageClient(client), nil\n}\n\n// DeleteClient deletes an oauth2 client from the database by id.\nfunc (d *Database) DeleteClient(ctx context.Context, id string) error {\n\terr := d.client.OAuth2Client.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete client: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdateClient changes an oauth2 client by id using an updater function and saves it to the database.\nfunc (d *Database) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error {\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update client tx: %w\", err)\n\t}\n\n\tclient, err := tx.OAuth2Client.Get(ctx, id)\n\tif err != nil {\n\t\treturn rollback(tx, \"update client database: %w\", err)\n\t}\n\n\tnewClient, err := updater(toStorageClient(client))\n\tif err != nil {\n\t\treturn rollback(tx, \"update client updating: %w\", err)\n\t}\n\n\t_, err = tx.OAuth2Client.UpdateOneID(newClient.ID).\n\t\tSetName(newClient.Name).\n\t\tSetSecret(newClient.Secret).\n\t\tSetPublic(newClient.Public).\n\t\tSetLogoURL(newClient.LogoURL).\n\t\tSetRedirectUris(newClient.RedirectURIs).\n\t\tSetTrustedPeers(newClient.TrustedPeers).\n\t\tSetAllowedConnectors(newClient.AllowedConnectors).\n\t\tSetMfaChain(newClient.MFAChain).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update client uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update auth request commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/connector.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateConnector saves a connector into the database.\nfunc (d *Database) CreateConnector(ctx context.Context, connector storage.Connector) error {\n\t_, err := d.client.Connector.Create().\n\t\tSetID(connector.ID).\n\t\tSetName(connector.Name).\n\t\tSetType(connector.Type).\n\t\tSetResourceVersion(connector.ResourceVersion).\n\t\tSetConfig(connector.Config).\n\t\tSetGrantTypes(connector.GrantTypes).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create connector: %w\", err)\n\t}\n\treturn nil\n}\n\n// ListConnectors extracts an array of connectors from the database.\nfunc (d *Database) ListConnectors(ctx context.Context) ([]storage.Connector, error) {\n\tconnectors, err := d.client.Connector.Query().All(ctx)\n\tif err != nil {\n\t\treturn nil, convertDBError(\"list connectors: %w\", err)\n\t}\n\n\tstorageConnectors := make([]storage.Connector, 0, len(connectors))\n\tfor _, c := range connectors {\n\t\tstorageConnectors = append(storageConnectors, toStorageConnector(c))\n\t}\n\treturn storageConnectors, nil\n}\n\n// GetConnector extracts a connector from the database by id.\nfunc (d *Database) GetConnector(ctx context.Context, id string) (storage.Connector, error) {\n\tconnector, err := d.client.Connector.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.Connector{}, convertDBError(\"get connector: %w\", err)\n\t}\n\treturn toStorageConnector(connector), nil\n}\n\n// DeleteConnector deletes a connector from the database by id.\nfunc (d *Database) DeleteConnector(ctx context.Context, id string) error {\n\terr := d.client.Connector.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete connector: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdateConnector changes a connector by id using an updater function and saves it to the database.\nfunc (d *Database) UpdateConnector(ctx context.Context, id string, updater func(old storage.Connector) (storage.Connector, error)) error {\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update connector tx: %w\", err)\n\t}\n\n\tconnector, err := tx.Connector.Get(ctx, id)\n\tif err != nil {\n\t\treturn rollback(tx, \"update connector database: %w\", err)\n\t}\n\n\tnewConnector, err := updater(toStorageConnector(connector))\n\tif err != nil {\n\t\treturn rollback(tx, \"update connector updating: %w\", err)\n\t}\n\n\t_, err = tx.Connector.UpdateOneID(newConnector.ID).\n\t\tSetName(newConnector.Name).\n\t\tSetType(newConnector.Type).\n\t\tSetResourceVersion(newConnector.ResourceVersion).\n\t\tSetConfig(newConnector.Config).\n\t\tSetGrantTypes(newConnector.GrantTypes).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update connector uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update connector commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/devicerequest.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n)\n\n// CreateDeviceRequest saves provided device request into the database.\nfunc (d *Database) CreateDeviceRequest(ctx context.Context, request storage.DeviceRequest) error {\n\t_, err := d.client.DeviceRequest.Create().\n\t\tSetClientID(request.ClientID).\n\t\tSetClientSecret(request.ClientSecret).\n\t\tSetScopes(request.Scopes).\n\t\tSetUserCode(request.UserCode).\n\t\tSetDeviceCode(request.DeviceCode).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetExpiry(request.Expiry.UTC()).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create device request: %w\", err)\n\t}\n\treturn nil\n}\n\n// GetDeviceRequest extracts a device request from the database by user code.\nfunc (d *Database) GetDeviceRequest(ctx context.Context, userCode string) (storage.DeviceRequest, error) {\n\tdeviceRequest, err := d.client.DeviceRequest.Query().\n\t\tWhere(devicerequest.UserCode(userCode)).\n\t\tOnly(ctx)\n\tif err != nil {\n\t\treturn storage.DeviceRequest{}, convertDBError(\"get device request: %w\", err)\n\t}\n\treturn toStorageDeviceRequest(deviceRequest), nil\n}\n"
  },
  {
    "path": "storage/ent/client/devicetoken.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n)\n\n// CreateDeviceToken saves provided token into the database.\nfunc (d *Database) CreateDeviceToken(ctx context.Context, token storage.DeviceToken) error {\n\t_, err := d.client.DeviceToken.Create().\n\t\tSetDeviceCode(token.DeviceCode).\n\t\tSetToken([]byte(token.Token)).\n\t\tSetPollInterval(token.PollIntervalSeconds).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetExpiry(token.Expiry.UTC()).\n\t\tSetLastRequest(token.LastRequestTime.UTC()).\n\t\tSetStatus(token.Status).\n\t\tSetCodeChallenge(token.PKCE.CodeChallenge).\n\t\tSetCodeChallengeMethod(token.PKCE.CodeChallengeMethod).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create device token: %w\", err)\n\t}\n\treturn nil\n}\n\n// GetDeviceToken extracts a token from the database by device code.\nfunc (d *Database) GetDeviceToken(ctx context.Context, deviceCode string) (storage.DeviceToken, error) {\n\tdeviceToken, err := d.client.DeviceToken.Query().\n\t\tWhere(devicetoken.DeviceCode(deviceCode)).\n\t\tOnly(ctx)\n\tif err != nil {\n\t\treturn storage.DeviceToken{}, convertDBError(\"get device token: %w\", err)\n\t}\n\treturn toStorageDeviceToken(deviceToken), nil\n}\n\n// UpdateDeviceToken changes a token by device code using an updater function and saves it to the database.\nfunc (d *Database) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error {\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update device token tx: %w\", err)\n\t}\n\n\ttoken, err := tx.DeviceToken.Query().\n\t\tWhere(devicetoken.DeviceCode(deviceCode)).\n\t\tOnly(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update device token database: %w\", err)\n\t}\n\n\tnewToken, err := updater(toStorageDeviceToken(token))\n\tif err != nil {\n\t\treturn rollback(tx, \"update device token updating: %w\", err)\n\t}\n\n\t_, err = tx.DeviceToken.Update().\n\t\tWhere(devicetoken.DeviceCode(newToken.DeviceCode)).\n\t\tSetDeviceCode(newToken.DeviceCode).\n\t\tSetToken([]byte(newToken.Token)).\n\t\tSetPollInterval(newToken.PollIntervalSeconds).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetExpiry(newToken.Expiry.UTC()).\n\t\tSetLastRequest(newToken.LastRequestTime.UTC()).\n\t\tSetStatus(newToken.Status).\n\t\tSetCodeChallenge(newToken.PKCE.CodeChallenge).\n\t\tSetCodeChallengeMethod(newToken.PKCE.CodeChallengeMethod).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update device token uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update device token commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/keys.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db\"\n)\n\nfunc getKeys(ctx context.Context, client *db.KeysClient) (storage.Keys, error) {\n\trawKeys, err := client.Get(ctx, keysRowID)\n\tif err != nil {\n\t\treturn storage.Keys{}, convertDBError(\"get keys: %w\", err)\n\t}\n\n\treturn toStorageKeys(rawKeys), nil\n}\n\n// GetKeys returns signing keys, public keys and verification keys from the database.\nfunc (d *Database) GetKeys(ctx context.Context) (storage.Keys, error) {\n\treturn getKeys(ctx, d.client.Keys)\n}\n\n// UpdateKeys rotates keys using updater function.\nfunc (d *Database) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error {\n\tfirstUpdate := false\n\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update keys tx: %w\", err)\n\t}\n\n\tstorageKeys, err := getKeys(ctx, tx.Keys)\n\tif err != nil {\n\t\tif !errors.Is(err, storage.ErrNotFound) {\n\t\t\treturn rollback(tx, \"update keys get: %w\", err)\n\t\t}\n\t\tfirstUpdate = true\n\t}\n\n\tnewKeys, err := updater(storageKeys)\n\tif err != nil {\n\t\treturn rollback(tx, \"update keys updating: %w\", err)\n\t}\n\n\t// ent doesn't have an upsert support yet\n\t// https://github.com/facebook/ent/issues/139\n\tif firstUpdate {\n\t\t_, err = tx.Keys.Create().\n\t\t\tSetID(keysRowID).\n\t\t\tSetNextRotation(newKeys.NextRotation).\n\t\t\tSetSigningKey(*newKeys.SigningKey).\n\t\t\tSetSigningKeyPub(*newKeys.SigningKeyPub).\n\t\t\tSetVerificationKeys(newKeys.VerificationKeys).\n\t\t\tSave(ctx)\n\t\tif err != nil {\n\t\t\treturn rollback(tx, \"create keys: %w\", err)\n\t\t}\n\t\tif err = tx.Commit(); err != nil {\n\t\t\treturn rollback(tx, \"update keys commit: %w\", err)\n\t\t}\n\t\treturn nil\n\t}\n\n\terr = tx.Keys.UpdateOneID(keysRowID).\n\t\tSetNextRotation(newKeys.NextRotation.UTC()).\n\t\tSetSigningKey(*newKeys.SigningKey).\n\t\tSetSigningKeyPub(*newKeys.SigningKeyPub).\n\t\tSetVerificationKeys(newKeys.VerificationKeys).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update keys uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update keys commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/main.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"hash\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/migrate\"\n)\n\nvar _ storage.Storage = (*Database)(nil)\n\ntype Database struct {\n\tclient    *db.Client\n\ttxOptions *sql.TxOptions\n\n\thasher func() hash.Hash\n}\n\n// NewDatabase returns new database client with set options.\nfunc NewDatabase(opts ...func(*Database)) *Database {\n\tdatabase := &Database{}\n\tfor _, f := range opts {\n\t\tf(database)\n\t}\n\treturn database\n}\n\n// WithClient sets client option of a Database object.\nfunc WithClient(c *db.Client) func(*Database) {\n\treturn func(s *Database) {\n\t\ts.client = c\n\t}\n}\n\n// WithHasher sets client option of a Database object.\nfunc WithHasher(h func() hash.Hash) func(*Database) {\n\treturn func(s *Database) {\n\t\ts.hasher = h\n\t}\n}\n\n// WithTxIsolationLevel sets correct isolation level for database transactions.\nfunc WithTxIsolationLevel(level sql.IsolationLevel) func(*Database) {\n\treturn func(s *Database) {\n\t\ts.txOptions = &sql.TxOptions{Isolation: level}\n\t}\n}\n\n// Schema exposes migration schema to perform migrations.\nfunc (d *Database) Schema() *migrate.Schema {\n\treturn d.client.Schema\n}\n\n// Close calls the corresponding method of the ent database client.\nfunc (d *Database) Close() error {\n\treturn d.client.Close()\n}\n\n// BeginTx is a wrapper to begin transaction with defined options.\nfunc (d *Database) BeginTx(ctx context.Context) (*db.Tx, error) {\n\treturn d.client.BeginTx(ctx, d.txOptions)\n}\n\n// GarbageCollect removes expired entities from the database.\nfunc (d *Database) GarbageCollect(ctx context.Context, now time.Time) (storage.GCResult, error) {\n\tresult := storage.GCResult{}\n\tutcNow := now.UTC()\n\n\tq, err := d.client.AuthRequest.Delete().\n\t\tWhere(authrequest.ExpiryLT(utcNow)).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn result, convertDBError(\"gc auth request: %w\", err)\n\t}\n\tresult.AuthRequests = int64(q)\n\n\tq, err = d.client.AuthCode.Delete().\n\t\tWhere(authcode.ExpiryLT(utcNow)).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn result, convertDBError(\"gc auth code: %w\", err)\n\t}\n\tresult.AuthCodes = int64(q)\n\n\tq, err = d.client.DeviceRequest.Delete().\n\t\tWhere(devicerequest.ExpiryLT(utcNow)).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn result, convertDBError(\"gc device request: %w\", err)\n\t}\n\tresult.DeviceRequests = int64(q)\n\n\tq, err = d.client.DeviceToken.Delete().\n\t\tWhere(devicetoken.ExpiryLT(utcNow)).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn result, convertDBError(\"gc device token: %w\", err)\n\t}\n\tresult.DeviceTokens = int64(q)\n\n\tq, err = d.client.AuthSession.Delete().\n\t\tWhere(\n\t\t\tauthsession.Or(\n\t\t\t\tauthsession.AbsoluteExpiryLT(utcNow),\n\t\t\t\tauthsession.IdleExpiryLT(utcNow),\n\t\t\t),\n\t\t).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn result, convertDBError(\"gc auth session: %w\", err)\n\t}\n\tresult.AuthSessions = int64(q)\n\n\treturn result, err\n}\n"
  },
  {
    "path": "storage/ent/client/offlinesession.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateOfflineSessions saves provided offline session into the database.\nfunc (d *Database) CreateOfflineSessions(ctx context.Context, session storage.OfflineSessions) error {\n\tencodedRefresh, err := json.Marshal(session.Refresh)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"encode refresh offline session: %w\", err)\n\t}\n\n\tid := compositeKeyID(session.UserID, session.ConnID, d.hasher)\n\t_, err = d.client.OfflineSession.Create().\n\t\tSetID(id).\n\t\tSetUserID(session.UserID).\n\t\tSetConnID(session.ConnID).\n\t\tSetConnectorData(session.ConnectorData).\n\t\tSetRefresh(encodedRefresh).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create offline session: %w\", err)\n\t}\n\treturn nil\n}\n\n// GetOfflineSessions extracts an offline session from the database by user id and connector id.\nfunc (d *Database) GetOfflineSessions(ctx context.Context, userID, connID string) (storage.OfflineSessions, error) {\n\tid := compositeKeyID(userID, connID, d.hasher)\n\n\tofflineSession, err := d.client.OfflineSession.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.OfflineSessions{}, convertDBError(\"get offline session: %w\", err)\n\t}\n\treturn toStorageOfflineSession(offlineSession), nil\n}\n\n// DeleteOfflineSessions deletes an offline session from the database by user id and connector id.\nfunc (d *Database) DeleteOfflineSessions(ctx context.Context, userID, connID string) error {\n\tid := compositeKeyID(userID, connID, d.hasher)\n\n\terr := d.client.OfflineSession.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete offline session: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdateOfflineSessions changes an offline session by user id and connector id using an updater function.\nfunc (d *Database) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s storage.OfflineSessions) (storage.OfflineSessions, error)) error {\n\tid := compositeKeyID(userID, connID, d.hasher)\n\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update offline session tx: %w\", err)\n\t}\n\n\tofflineSession, err := tx.OfflineSession.Get(ctx, id)\n\tif err != nil {\n\t\treturn rollback(tx, \"update offline session database: %w\", err)\n\t}\n\n\tnewOfflineSession, err := updater(toStorageOfflineSession(offlineSession))\n\tif err != nil {\n\t\treturn rollback(tx, \"update offline session updating: %w\", err)\n\t}\n\n\tencodedRefresh, err := json.Marshal(newOfflineSession.Refresh)\n\tif err != nil {\n\t\treturn rollback(tx, \"encode refresh offline session: %w\", err)\n\t}\n\n\t_, err = tx.OfflineSession.UpdateOneID(id).\n\t\tSetUserID(newOfflineSession.UserID).\n\t\tSetConnID(newOfflineSession.ConnID).\n\t\tSetConnectorData(newOfflineSession.ConnectorData).\n\t\tSetRefresh(encodedRefresh).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update offline session uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update offline session commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/password.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n)\n\n// CreatePassword saves provided password into the database.\nfunc (d *Database) CreatePassword(ctx context.Context, password storage.Password) error {\n\t_, err := d.client.Password.Create().\n\t\tSetEmail(password.Email).\n\t\tSetHash(password.Hash).\n\t\tSetUsername(password.Username).\n\t\tSetName(password.Name).\n\t\tSetPreferredUsername(password.PreferredUsername).\n\t\tSetNillableEmailVerified(password.EmailVerified).\n\t\tSetUserID(password.UserID).\n\t\tSetGroups(password.Groups).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create password: %w\", err)\n\t}\n\treturn nil\n}\n\n// ListPasswords extracts an array of passwords from the database.\nfunc (d *Database) ListPasswords(ctx context.Context) ([]storage.Password, error) {\n\tpasswords, err := d.client.Password.Query().All(ctx)\n\tif err != nil {\n\t\treturn nil, convertDBError(\"list passwords: %w\", err)\n\t}\n\n\tstoragePasswords := make([]storage.Password, 0, len(passwords))\n\tfor _, p := range passwords {\n\t\tstoragePasswords = append(storagePasswords, toStoragePassword(p))\n\t}\n\treturn storagePasswords, nil\n}\n\n// GetPassword extracts a password from the database by email.\nfunc (d *Database) GetPassword(ctx context.Context, email string) (storage.Password, error) {\n\temail = strings.ToLower(email)\n\tpasswordFromStorage, err := d.client.Password.Query().\n\t\tWhere(password.Email(email)).\n\t\tOnly(ctx)\n\tif err != nil {\n\t\treturn storage.Password{}, convertDBError(\"get password: %w\", err)\n\t}\n\treturn toStoragePassword(passwordFromStorage), nil\n}\n\n// DeletePassword deletes a password from the database by email.\nfunc (d *Database) DeletePassword(ctx context.Context, email string) error {\n\temail = strings.ToLower(email)\n\t_, err := d.client.Password.Delete().\n\t\tWhere(password.Email(email)).\n\t\tExec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete password: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdatePassword changes a password by email using an updater function and saves it to the database.\nfunc (d *Database) UpdatePassword(ctx context.Context, email string, updater func(old storage.Password) (storage.Password, error)) error {\n\temail = strings.ToLower(email)\n\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update connector tx: %w\", err)\n\t}\n\n\tpasswordToUpdate, err := tx.Password.Query().\n\t\tWhere(password.Email(email)).\n\t\tOnly(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update password database: %w\", err)\n\t}\n\n\tnewPassword, err := updater(toStoragePassword(passwordToUpdate))\n\tif err != nil {\n\t\treturn rollback(tx, \"update password updating: %w\", err)\n\t}\n\n\t_, err = tx.Password.Update().\n\t\tWhere(password.Email(newPassword.Email)).\n\t\tSetEmail(newPassword.Email).\n\t\tSetHash(newPassword.Hash).\n\t\tSetUsername(newPassword.Username).\n\t\tSetName(newPassword.Name).\n\t\tSetPreferredUsername(newPassword.PreferredUsername).\n\t\tSetNillableEmailVerified(newPassword.EmailVerified).\n\t\tSetUserID(newPassword.UserID).\n\t\tSetGroups(newPassword.Groups).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update password uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update password commit: %w\", err)\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/refreshtoken.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateRefresh saves provided refresh token into the database.\nfunc (d *Database) CreateRefresh(ctx context.Context, refresh storage.RefreshToken) error {\n\t_, err := d.client.RefreshToken.Create().\n\t\tSetID(refresh.ID).\n\t\tSetClientID(refresh.ClientID).\n\t\tSetScopes(refresh.Scopes).\n\t\tSetNonce(refresh.Nonce).\n\t\tSetClaimsUserID(refresh.Claims.UserID).\n\t\tSetClaimsEmail(refresh.Claims.Email).\n\t\tSetClaimsEmailVerified(refresh.Claims.EmailVerified).\n\t\tSetClaimsUsername(refresh.Claims.Username).\n\t\tSetClaimsPreferredUsername(refresh.Claims.PreferredUsername).\n\t\tSetClaimsGroups(refresh.Claims.Groups).\n\t\tSetConnectorID(refresh.ConnectorID).\n\t\tSetConnectorData(refresh.ConnectorData).\n\t\tSetToken(refresh.Token).\n\t\tSetObsoleteToken(refresh.ObsoleteToken).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetLastUsed(refresh.LastUsed.UTC()).\n\t\tSetCreatedAt(refresh.CreatedAt.UTC()).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create refresh token: %w\", err)\n\t}\n\treturn nil\n}\n\n// ListRefreshTokens extracts an array of refresh tokens from the database.\nfunc (d *Database) ListRefreshTokens(ctx context.Context) ([]storage.RefreshToken, error) {\n\trefreshTokens, err := d.client.RefreshToken.Query().All(ctx)\n\tif err != nil {\n\t\treturn nil, convertDBError(\"list refresh tokens: %w\", err)\n\t}\n\n\tstorageRefreshTokens := make([]storage.RefreshToken, 0, len(refreshTokens))\n\tfor _, r := range refreshTokens {\n\t\tstorageRefreshTokens = append(storageRefreshTokens, toStorageRefreshToken(r))\n\t}\n\treturn storageRefreshTokens, nil\n}\n\n// GetRefresh extracts a refresh token from the database by id.\nfunc (d *Database) GetRefresh(ctx context.Context, id string) (storage.RefreshToken, error) {\n\trefreshToken, err := d.client.RefreshToken.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.RefreshToken{}, convertDBError(\"get refresh token: %w\", err)\n\t}\n\treturn toStorageRefreshToken(refreshToken), nil\n}\n\n// DeleteRefresh deletes a refresh token from the database by id.\nfunc (d *Database) DeleteRefresh(ctx context.Context, id string) error {\n\terr := d.client.RefreshToken.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete refresh token: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdateRefreshToken changes a refresh token by id using an updater function and saves it to the database.\nfunc (d *Database) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error {\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update refresh token tx: %w\", err)\n\t}\n\n\ttoken, err := tx.RefreshToken.Get(ctx, id)\n\tif err != nil {\n\t\treturn rollback(tx, \"update refresh token database: %w\", err)\n\t}\n\n\tnewtToken, err := updater(toStorageRefreshToken(token))\n\tif err != nil {\n\t\treturn rollback(tx, \"update refresh token updating: %w\", err)\n\t}\n\n\t_, err = tx.RefreshToken.UpdateOneID(newtToken.ID).\n\t\tSetClientID(newtToken.ClientID).\n\t\tSetScopes(newtToken.Scopes).\n\t\tSetNonce(newtToken.Nonce).\n\t\tSetClaimsUserID(newtToken.Claims.UserID).\n\t\tSetClaimsEmail(newtToken.Claims.Email).\n\t\tSetClaimsEmailVerified(newtToken.Claims.EmailVerified).\n\t\tSetClaimsUsername(newtToken.Claims.Username).\n\t\tSetClaimsPreferredUsername(newtToken.Claims.PreferredUsername).\n\t\tSetClaimsGroups(newtToken.Claims.Groups).\n\t\tSetConnectorID(newtToken.ConnectorID).\n\t\tSetConnectorData(newtToken.ConnectorData).\n\t\tSetToken(newtToken.Token).\n\t\tSetObsoleteToken(newtToken.ObsoleteToken).\n\t\t// Save utc time into database because ent doesn't support comparing dates with different timezones\n\t\tSetLastUsed(newtToken.LastUsed.UTC()).\n\t\tSetCreatedAt(newtToken.CreatedAt.UTC()).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update refresh token uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update refresh token commit: %w\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/client/types.go",
    "content": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db\"\n)\n\nconst keysRowID = \"keys\"\n\nfunc toStorageKeys(keys *db.Keys) storage.Keys {\n\treturn storage.Keys{\n\t\tSigningKey:       &keys.SigningKey,\n\t\tSigningKeyPub:    &keys.SigningKeyPub,\n\t\tVerificationKeys: keys.VerificationKeys,\n\t\tNextRotation:     keys.NextRotation,\n\t}\n}\n\nfunc toStorageAuthRequest(a *db.AuthRequest) storage.AuthRequest {\n\treturn storage.AuthRequest{\n\t\tID:                  a.ID,\n\t\tClientID:            a.ClientID,\n\t\tResponseTypes:       a.ResponseTypes,\n\t\tScopes:              a.Scopes,\n\t\tRedirectURI:         a.RedirectURI,\n\t\tNonce:               a.Nonce,\n\t\tState:               a.State,\n\t\tForceApprovalPrompt: a.ForceApprovalPrompt,\n\t\tLoggedIn:            a.LoggedIn,\n\t\tConnectorID:         a.ConnectorID,\n\t\tConnectorData:       *a.ConnectorData,\n\t\tExpiry:              a.Expiry,\n\t\tClaims: storage.Claims{\n\t\t\tUserID:            a.ClaimsUserID,\n\t\t\tUsername:          a.ClaimsUsername,\n\t\t\tPreferredUsername: a.ClaimsPreferredUsername,\n\t\t\tEmail:             a.ClaimsEmail,\n\t\t\tEmailVerified:     a.ClaimsEmailVerified,\n\t\t\tGroups:            a.ClaimsGroups,\n\t\t},\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       a.CodeChallenge,\n\t\t\tCodeChallengeMethod: a.CodeChallengeMethod,\n\t\t},\n\t\tHMACKey:      a.HmacKey,\n\t\tMFAValidated: a.MfaValidated,\n\t\tPrompt:       a.Prompt,\n\t\tMaxAge:       a.MaxAge,\n\t\tAuthTime:     a.AuthTime,\n\t}\n}\n\nfunc toStorageAuthCode(a *db.AuthCode) storage.AuthCode {\n\treturn storage.AuthCode{\n\t\tID:            a.ID,\n\t\tClientID:      a.ClientID,\n\t\tScopes:        a.Scopes,\n\t\tRedirectURI:   a.RedirectURI,\n\t\tNonce:         a.Nonce,\n\t\tConnectorID:   a.ConnectorID,\n\t\tConnectorData: *a.ConnectorData,\n\t\tExpiry:        a.Expiry,\n\t\tClaims: storage.Claims{\n\t\t\tUserID:            a.ClaimsUserID,\n\t\t\tUsername:          a.ClaimsUsername,\n\t\t\tPreferredUsername: a.ClaimsPreferredUsername,\n\t\t\tEmail:             a.ClaimsEmail,\n\t\t\tEmailVerified:     a.ClaimsEmailVerified,\n\t\t\tGroups:            a.ClaimsGroups,\n\t\t},\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       a.CodeChallenge,\n\t\t\tCodeChallengeMethod: a.CodeChallengeMethod,\n\t\t},\n\t\tAuthTime: a.AuthTime,\n\t}\n}\n\nfunc toStorageClient(c *db.OAuth2Client) storage.Client {\n\treturn storage.Client{\n\t\tID:                c.ID,\n\t\tSecret:            c.Secret,\n\t\tRedirectURIs:      c.RedirectUris,\n\t\tTrustedPeers:      c.TrustedPeers,\n\t\tPublic:            c.Public,\n\t\tName:              c.Name,\n\t\tLogoURL:           c.LogoURL,\n\t\tAllowedConnectors: c.AllowedConnectors,\n\t\tMFAChain:          c.MfaChain,\n\t}\n}\n\nfunc toStorageConnector(c *db.Connector) storage.Connector {\n\treturn storage.Connector{\n\t\tID:         c.ID,\n\t\tType:       c.Type,\n\t\tName:       c.Name,\n\t\tConfig:     c.Config,\n\t\tGrantTypes: c.GrantTypes,\n\t}\n}\n\nfunc toStorageOfflineSession(o *db.OfflineSession) storage.OfflineSessions {\n\ts := storage.OfflineSessions{\n\t\tUserID:        o.UserID,\n\t\tConnID:        o.ConnID,\n\t\tConnectorData: *o.ConnectorData,\n\t}\n\n\tif o.Refresh != nil {\n\t\tif err := json.Unmarshal(o.Refresh, &s.Refresh); err != nil {\n\t\t\t// Correctness of json structure if guaranteed on uploading\n\t\t\tpanic(err)\n\t\t}\n\t} else {\n\t\t// Server code assumes this will be non-nil.\n\t\ts.Refresh = make(map[string]*storage.RefreshTokenRef)\n\t}\n\treturn s\n}\n\nfunc toStorageRefreshToken(r *db.RefreshToken) storage.RefreshToken {\n\treturn storage.RefreshToken{\n\t\tID:            r.ID,\n\t\tToken:         r.Token,\n\t\tObsoleteToken: r.ObsoleteToken,\n\t\tCreatedAt:     r.CreatedAt,\n\t\tLastUsed:      r.LastUsed,\n\t\tClientID:      r.ClientID,\n\t\tConnectorID:   r.ConnectorID,\n\t\tConnectorData: *r.ConnectorData,\n\t\tScopes:        r.Scopes,\n\t\tNonce:         r.Nonce,\n\t\tClaims: storage.Claims{\n\t\t\tUserID:            r.ClaimsUserID,\n\t\t\tUsername:          r.ClaimsUsername,\n\t\t\tPreferredUsername: r.ClaimsPreferredUsername,\n\t\t\tEmail:             r.ClaimsEmail,\n\t\t\tEmailVerified:     r.ClaimsEmailVerified,\n\t\t\tGroups:            r.ClaimsGroups,\n\t\t},\n\t}\n}\n\nfunc toStoragePassword(p *db.Password) storage.Password {\n\treturn storage.Password{\n\t\tEmail:             p.Email,\n\t\tHash:              p.Hash,\n\t\tUsername:          p.Username,\n\t\tName:              p.Name,\n\t\tPreferredUsername: p.PreferredUsername,\n\t\tEmailVerified:     p.EmailVerified,\n\t\tUserID:            p.UserID,\n\t\tGroups:            p.Groups,\n\t}\n}\n\nfunc toStorageDeviceRequest(r *db.DeviceRequest) storage.DeviceRequest {\n\treturn storage.DeviceRequest{\n\t\tUserCode:     strings.ToUpper(r.UserCode),\n\t\tDeviceCode:   r.DeviceCode,\n\t\tClientID:     r.ClientID,\n\t\tClientSecret: r.ClientSecret,\n\t\tScopes:       r.Scopes,\n\t\tExpiry:       r.Expiry,\n\t}\n}\n\nfunc toStorageUserIdentity(u *db.UserIdentity) storage.UserIdentity {\n\ts := storage.UserIdentity{\n\t\tUserID:      u.UserID,\n\t\tConnectorID: u.ConnectorID,\n\t\tClaims: storage.Claims{\n\t\t\tUserID:            u.ClaimsUserID,\n\t\t\tUsername:          u.ClaimsUsername,\n\t\t\tPreferredUsername: u.ClaimsPreferredUsername,\n\t\t\tEmail:             u.ClaimsEmail,\n\t\t\tEmailVerified:     u.ClaimsEmailVerified,\n\t\t\tGroups:            u.ClaimsGroups,\n\t\t},\n\t\tCreatedAt:    u.CreatedAt,\n\t\tLastLogin:    u.LastLogin,\n\t\tBlockedUntil: u.BlockedUntil,\n\t}\n\n\tif u.Consents != nil {\n\t\tif err := json.Unmarshal(u.Consents, &s.Consents); err != nil {\n\t\t\t// Correctness of json structure is guaranteed on uploading\n\t\t\tpanic(err)\n\t\t}\n\t\tif s.Consents == nil {\n\t\t\t// Ensure Consents is non-nil even if JSON was \"null\".\n\t\t\ts.Consents = make(map[string][]string)\n\t\t}\n\t} else {\n\t\t// Server code assumes this will be non-nil.\n\t\ts.Consents = make(map[string][]string)\n\t}\n\n\tif u.MfaSecrets != nil {\n\t\tif err := json.Unmarshal(*u.MfaSecrets, &s.MFASecrets); err != nil {\n\t\t\t// Correctness of json structure is guaranteed on uploading\n\t\t\tpanic(err)\n\t\t}\n\t\tif s.MFASecrets == nil {\n\t\t\ts.MFASecrets = make(map[string]*storage.MFASecret)\n\t\t}\n\t} else {\n\t\ts.MFASecrets = make(map[string]*storage.MFASecret)\n\t}\n\treturn s\n}\n\nfunc toStorageAuthSession(s *db.AuthSession) storage.AuthSession {\n\tresult := storage.AuthSession{\n\t\tUserID:         s.UserID,\n\t\tConnectorID:    s.ConnectorID,\n\t\tNonce:          s.Nonce,\n\t\tCreatedAt:      s.CreatedAt,\n\t\tLastActivity:   s.LastActivity,\n\t\tIPAddress:      s.IPAddress,\n\t\tUserAgent:      s.UserAgent,\n\t\tAbsoluteExpiry: s.AbsoluteExpiry,\n\t\tIdleExpiry:     s.IdleExpiry,\n\t}\n\n\tif s.ClientStates != nil {\n\t\tif err := json.Unmarshal(s.ClientStates, &result.ClientStates); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tif result.ClientStates == nil {\n\t\t\tresult.ClientStates = make(map[string]*storage.ClientAuthState)\n\t\t}\n\t} else {\n\t\tresult.ClientStates = make(map[string]*storage.ClientAuthState)\n\t}\n\treturn result\n}\n\nfunc toStorageDeviceToken(t *db.DeviceToken) storage.DeviceToken {\n\treturn storage.DeviceToken{\n\t\tDeviceCode:          t.DeviceCode,\n\t\tStatus:              t.Status,\n\t\tToken:               string(*t.Token),\n\t\tExpiry:              t.Expiry,\n\t\tLastRequestTime:     t.LastRequest,\n\t\tPollIntervalSeconds: t.PollInterval,\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       t.CodeChallenge,\n\t\t\tCodeChallengeMethod: t.CodeChallengeMethod,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "storage/ent/client/useridentity.go",
    "content": "package client\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// CreateUserIdentity saves provided user identity into the database.\nfunc (d *Database) CreateUserIdentity(ctx context.Context, identity storage.UserIdentity) error {\n\tif identity.Consents == nil {\n\t\tidentity.Consents = make(map[string][]string)\n\t}\n\tencodedConsents, err := json.Marshal(identity.Consents)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"encode consents user identity: %w\", err)\n\t}\n\n\tif identity.MFASecrets == nil {\n\t\tidentity.MFASecrets = make(map[string]*storage.MFASecret)\n\t}\n\tencodedMFASecrets, err := json.Marshal(identity.MFASecrets)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"encode mfa secrets user identity: %w\", err)\n\t}\n\n\tid := compositeKeyID(identity.UserID, identity.ConnectorID, d.hasher)\n\t_, err = d.client.UserIdentity.Create().\n\t\tSetID(id).\n\t\tSetUserID(identity.UserID).\n\t\tSetConnectorID(identity.ConnectorID).\n\t\tSetClaimsUserID(identity.Claims.UserID).\n\t\tSetClaimsUsername(identity.Claims.Username).\n\t\tSetClaimsPreferredUsername(identity.Claims.PreferredUsername).\n\t\tSetClaimsEmail(identity.Claims.Email).\n\t\tSetClaimsEmailVerified(identity.Claims.EmailVerified).\n\t\tSetClaimsGroups(identity.Claims.Groups).\n\t\tSetConsents(encodedConsents).\n\t\tSetMfaSecrets(encodedMFASecrets).\n\t\tSetCreatedAt(identity.CreatedAt).\n\t\tSetLastLogin(identity.LastLogin).\n\t\tSetBlockedUntil(identity.BlockedUntil).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"create user identity: %w\", err)\n\t}\n\treturn nil\n}\n\n// GetUserIdentity extracts a user identity from the database by user id and connector id.\nfunc (d *Database) GetUserIdentity(ctx context.Context, userID, connectorID string) (storage.UserIdentity, error) {\n\tid := compositeKeyID(userID, connectorID, d.hasher)\n\n\tuserIdentity, err := d.client.UserIdentity.Get(ctx, id)\n\tif err != nil {\n\t\treturn storage.UserIdentity{}, convertDBError(\"get user identity: %w\", err)\n\t}\n\treturn toStorageUserIdentity(userIdentity), nil\n}\n\n// DeleteUserIdentity deletes a user identity from the database by user id and connector id.\nfunc (d *Database) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error {\n\tid := compositeKeyID(userID, connectorID, d.hasher)\n\n\terr := d.client.UserIdentity.DeleteOneID(id).Exec(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"delete user identity: %w\", err)\n\t}\n\treturn nil\n}\n\n// UpdateUserIdentity changes a user identity by user id and connector id using an updater function.\nfunc (d *Database) UpdateUserIdentity(ctx context.Context, userID string, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) error {\n\tid := compositeKeyID(userID, connectorID, d.hasher)\n\n\ttx, err := d.BeginTx(ctx)\n\tif err != nil {\n\t\treturn convertDBError(\"update user identity tx: %w\", err)\n\t}\n\n\tuserIdentity, err := tx.UserIdentity.Get(ctx, id)\n\tif err != nil {\n\t\treturn rollback(tx, \"update user identity database: %w\", err)\n\t}\n\n\tnewUserIdentity, err := updater(toStorageUserIdentity(userIdentity))\n\tif err != nil {\n\t\treturn rollback(tx, \"update user identity updating: %w\", err)\n\t}\n\n\tif newUserIdentity.Consents == nil {\n\t\tnewUserIdentity.Consents = make(map[string][]string)\n\t}\n\n\tencodedConsents, err := json.Marshal(newUserIdentity.Consents)\n\tif err != nil {\n\t\treturn rollback(tx, \"encode consents user identity: %w\", err)\n\t}\n\n\tif newUserIdentity.MFASecrets == nil {\n\t\tnewUserIdentity.MFASecrets = make(map[string]*storage.MFASecret)\n\t}\n\n\tencodedMFASecrets, err := json.Marshal(newUserIdentity.MFASecrets)\n\tif err != nil {\n\t\treturn rollback(tx, \"encode mfa secrets user identity: %w\", err)\n\t}\n\n\t_, err = tx.UserIdentity.UpdateOneID(id).\n\t\tSetUserID(newUserIdentity.UserID).\n\t\tSetConnectorID(newUserIdentity.ConnectorID).\n\t\tSetClaimsUserID(newUserIdentity.Claims.UserID).\n\t\tSetClaimsUsername(newUserIdentity.Claims.Username).\n\t\tSetClaimsPreferredUsername(newUserIdentity.Claims.PreferredUsername).\n\t\tSetClaimsEmail(newUserIdentity.Claims.Email).\n\t\tSetClaimsEmailVerified(newUserIdentity.Claims.EmailVerified).\n\t\tSetClaimsGroups(newUserIdentity.Claims.Groups).\n\t\tSetConsents(encodedConsents).\n\t\tSetMfaSecrets(encodedMFASecrets).\n\t\tSetCreatedAt(newUserIdentity.CreatedAt).\n\t\tSetLastLogin(newUserIdentity.LastLogin).\n\t\tSetBlockedUntil(newUserIdentity.BlockedUntil).\n\t\tSave(ctx)\n\tif err != nil {\n\t\treturn rollback(tx, \"update user identity uploading: %w\", err)\n\t}\n\n\tif err = tx.Commit(); err != nil {\n\t\treturn rollback(tx, \"update user identity commit: %w\", err)\n\t}\n\n\treturn nil\n}\n\n// ListUserIdentities lists all user identities in the database.\nfunc (d *Database) ListUserIdentities(ctx context.Context) ([]storage.UserIdentity, error) {\n\tuserIdentities, err := d.client.UserIdentity.Query().All(ctx)\n\tif err != nil {\n\t\treturn nil, convertDBError(\"list user identities: %w\", err)\n\t}\n\n\tstorageUserIdentities := make([]storage.UserIdentity, 0, len(userIdentities))\n\tfor _, u := range userIdentities {\n\t\tstorageUserIdentities = append(storageUserIdentities, toStorageUserIdentity(u))\n\t}\n\treturn storageUserIdentities, nil\n}\n"
  },
  {
    "path": "storage/ent/client/utils.go",
    "content": "package client\n\nimport (\n\t\"fmt\"\n\t\"hash\"\n\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db\"\n)\n\nfunc rollback(tx *db.Tx, t string, err error) error {\n\trerr := tx.Rollback()\n\terr = convertDBError(t, err)\n\n\tif rerr == nil {\n\t\treturn err\n\t}\n\treturn errors.Wrapf(err, \"rolling back transaction: %v\", rerr)\n}\n\nfunc convertDBError(t string, err error) error {\n\tif db.IsNotFound(err) {\n\t\treturn storage.ErrNotFound\n\t}\n\n\tif db.IsConstraintError(err) {\n\t\treturn storage.ErrAlreadyExists\n\t}\n\n\treturn fmt.Errorf(t, err)\n}\n\n// compositeKeyID composes a hashed id from two key parts to use as primary key.\n// ent doesn't support multi-key primary yet\n// https://github.com/facebook/ent/issues/400\nfunc compositeKeyID(first string, second string, hasher func() hash.Hash) string {\n\th := hasher()\n\n\th.Write([]byte(first))\n\th.Write([]byte(second))\n\treturn fmt.Sprintf(\"%x\", h.Sum(nil))\n}\n"
  },
  {
    "path": "storage/ent/db/authcode/authcode.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage authcode\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the authcode type in the database.\n\tLabel = \"auth_code\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldClientID holds the string denoting the client_id field in the database.\n\tFieldClientID = \"client_id\"\n\t// FieldScopes holds the string denoting the scopes field in the database.\n\tFieldScopes = \"scopes\"\n\t// FieldNonce holds the string denoting the nonce field in the database.\n\tFieldNonce = \"nonce\"\n\t// FieldRedirectURI holds the string denoting the redirect_uri field in the database.\n\tFieldRedirectURI = \"redirect_uri\"\n\t// FieldClaimsUserID holds the string denoting the claims_user_id field in the database.\n\tFieldClaimsUserID = \"claims_user_id\"\n\t// FieldClaimsUsername holds the string denoting the claims_username field in the database.\n\tFieldClaimsUsername = \"claims_username\"\n\t// FieldClaimsEmail holds the string denoting the claims_email field in the database.\n\tFieldClaimsEmail = \"claims_email\"\n\t// FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database.\n\tFieldClaimsEmailVerified = \"claims_email_verified\"\n\t// FieldClaimsGroups holds the string denoting the claims_groups field in the database.\n\tFieldClaimsGroups = \"claims_groups\"\n\t// FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database.\n\tFieldClaimsPreferredUsername = \"claims_preferred_username\"\n\t// FieldConnectorID holds the string denoting the connector_id field in the database.\n\tFieldConnectorID = \"connector_id\"\n\t// FieldConnectorData holds the string denoting the connector_data field in the database.\n\tFieldConnectorData = \"connector_data\"\n\t// FieldExpiry holds the string denoting the expiry field in the database.\n\tFieldExpiry = \"expiry\"\n\t// FieldCodeChallenge holds the string denoting the code_challenge field in the database.\n\tFieldCodeChallenge = \"code_challenge\"\n\t// FieldCodeChallengeMethod holds the string denoting the code_challenge_method field in the database.\n\tFieldCodeChallengeMethod = \"code_challenge_method\"\n\t// FieldAuthTime holds the string denoting the auth_time field in the database.\n\tFieldAuthTime = \"auth_time\"\n\t// Table holds the table name of the authcode in the database.\n\tTable = \"auth_codes\"\n)\n\n// Columns holds all SQL columns for authcode fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldClientID,\n\tFieldScopes,\n\tFieldNonce,\n\tFieldRedirectURI,\n\tFieldClaimsUserID,\n\tFieldClaimsUsername,\n\tFieldClaimsEmail,\n\tFieldClaimsEmailVerified,\n\tFieldClaimsGroups,\n\tFieldClaimsPreferredUsername,\n\tFieldConnectorID,\n\tFieldConnectorData,\n\tFieldExpiry,\n\tFieldCodeChallenge,\n\tFieldCodeChallengeMethod,\n\tFieldAuthTime,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// ClientIDValidator is a validator for the \"client_id\" field. It is called by the builders before save.\n\tClientIDValidator func(string) error\n\t// NonceValidator is a validator for the \"nonce\" field. It is called by the builders before save.\n\tNonceValidator func(string) error\n\t// RedirectURIValidator is a validator for the \"redirect_uri\" field. It is called by the builders before save.\n\tRedirectURIValidator func(string) error\n\t// ClaimsUserIDValidator is a validator for the \"claims_user_id\" field. It is called by the builders before save.\n\tClaimsUserIDValidator func(string) error\n\t// ClaimsUsernameValidator is a validator for the \"claims_username\" field. It is called by the builders before save.\n\tClaimsUsernameValidator func(string) error\n\t// ClaimsEmailValidator is a validator for the \"claims_email\" field. It is called by the builders before save.\n\tClaimsEmailValidator func(string) error\n\t// DefaultClaimsPreferredUsername holds the default value on creation for the \"claims_preferred_username\" field.\n\tDefaultClaimsPreferredUsername string\n\t// ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\tConnectorIDValidator func(string) error\n\t// DefaultCodeChallenge holds the default value on creation for the \"code_challenge\" field.\n\tDefaultCodeChallenge string\n\t// DefaultCodeChallengeMethod holds the default value on creation for the \"code_challenge_method\" field.\n\tDefaultCodeChallengeMethod string\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the AuthCode queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByClientID orders the results by the client_id field.\nfunc ByClientID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClientID, opts...).ToFunc()\n}\n\n// ByNonce orders the results by the nonce field.\nfunc ByNonce(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldNonce, opts...).ToFunc()\n}\n\n// ByRedirectURI orders the results by the redirect_uri field.\nfunc ByRedirectURI(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldRedirectURI, opts...).ToFunc()\n}\n\n// ByClaimsUserID orders the results by the claims_user_id field.\nfunc ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUserID, opts...).ToFunc()\n}\n\n// ByClaimsUsername orders the results by the claims_username field.\nfunc ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUsername, opts...).ToFunc()\n}\n\n// ByClaimsEmail orders the results by the claims_email field.\nfunc ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmail, opts...).ToFunc()\n}\n\n// ByClaimsEmailVerified orders the results by the claims_email_verified field.\nfunc ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc()\n}\n\n// ByClaimsPreferredUsername orders the results by the claims_preferred_username field.\nfunc ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc()\n}\n\n// ByConnectorID orders the results by the connector_id field.\nfunc ByConnectorID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldConnectorID, opts...).ToFunc()\n}\n\n// ByExpiry orders the results by the expiry field.\nfunc ByExpiry(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldExpiry, opts...).ToFunc()\n}\n\n// ByCodeChallenge orders the results by the code_challenge field.\nfunc ByCodeChallenge(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCodeChallenge, opts...).ToFunc()\n}\n\n// ByCodeChallengeMethod orders the results by the code_challenge_method field.\nfunc ByCodeChallengeMethod(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCodeChallengeMethod, opts...).ToFunc()\n}\n\n// ByAuthTime orders the results by the auth_time field.\nfunc ByAuthTime(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldAuthTime, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/authcode/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage authcode\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldID, id))\n}\n\n// ClientID applies equality check predicate on the \"client_id\" field. It's identical to ClientIDEQ.\nfunc ClientID(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClientID, v))\n}\n\n// Nonce applies equality check predicate on the \"nonce\" field. It's identical to NonceEQ.\nfunc Nonce(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldNonce, v))\n}\n\n// RedirectURI applies equality check predicate on the \"redirect_uri\" field. It's identical to RedirectURIEQ.\nfunc RedirectURI(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldRedirectURI, v))\n}\n\n// ClaimsUserID applies equality check predicate on the \"claims_user_id\" field. It's identical to ClaimsUserIDEQ.\nfunc ClaimsUserID(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUsername applies equality check predicate on the \"claims_username\" field. It's identical to ClaimsUsernameEQ.\nfunc ClaimsUsername(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsEmail applies equality check predicate on the \"claims_email\" field. It's identical to ClaimsEmailEQ.\nfunc ClaimsEmail(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerified applies equality check predicate on the \"claims_email_verified\" field. It's identical to ClaimsEmailVerifiedEQ.\nfunc ClaimsEmailVerified(v bool) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsPreferredUsername applies equality check predicate on the \"claims_preferred_username\" field. It's identical to ClaimsPreferredUsernameEQ.\nfunc ClaimsPreferredUsername(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ConnectorID applies equality check predicate on the \"connector_id\" field. It's identical to ConnectorIDEQ.\nfunc ConnectorID(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorData applies equality check predicate on the \"connector_data\" field. It's identical to ConnectorDataEQ.\nfunc ConnectorData(v []byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// Expiry applies equality check predicate on the \"expiry\" field. It's identical to ExpiryEQ.\nfunc Expiry(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldExpiry, v))\n}\n\n// CodeChallenge applies equality check predicate on the \"code_challenge\" field. It's identical to CodeChallengeEQ.\nfunc CodeChallenge(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeMethod applies equality check predicate on the \"code_challenge_method\" field. It's identical to CodeChallengeMethodEQ.\nfunc CodeChallengeMethod(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldCodeChallengeMethod, v))\n}\n\n// AuthTime applies equality check predicate on the \"auth_time\" field. It's identical to AuthTimeEQ.\nfunc AuthTime(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldAuthTime, v))\n}\n\n// ClientIDEQ applies the EQ predicate on the \"client_id\" field.\nfunc ClientIDEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClientID, v))\n}\n\n// ClientIDNEQ applies the NEQ predicate on the \"client_id\" field.\nfunc ClientIDNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldClientID, v))\n}\n\n// ClientIDIn applies the In predicate on the \"client_id\" field.\nfunc ClientIDIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldClientID, vs...))\n}\n\n// ClientIDNotIn applies the NotIn predicate on the \"client_id\" field.\nfunc ClientIDNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldClientID, vs...))\n}\n\n// ClientIDGT applies the GT predicate on the \"client_id\" field.\nfunc ClientIDGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldClientID, v))\n}\n\n// ClientIDGTE applies the GTE predicate on the \"client_id\" field.\nfunc ClientIDGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldClientID, v))\n}\n\n// ClientIDLT applies the LT predicate on the \"client_id\" field.\nfunc ClientIDLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldClientID, v))\n}\n\n// ClientIDLTE applies the LTE predicate on the \"client_id\" field.\nfunc ClientIDLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldClientID, v))\n}\n\n// ClientIDContains applies the Contains predicate on the \"client_id\" field.\nfunc ClientIDContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldClientID, v))\n}\n\n// ClientIDHasPrefix applies the HasPrefix predicate on the \"client_id\" field.\nfunc ClientIDHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldClientID, v))\n}\n\n// ClientIDHasSuffix applies the HasSuffix predicate on the \"client_id\" field.\nfunc ClientIDHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldClientID, v))\n}\n\n// ClientIDEqualFold applies the EqualFold predicate on the \"client_id\" field.\nfunc ClientIDEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldClientID, v))\n}\n\n// ClientIDContainsFold applies the ContainsFold predicate on the \"client_id\" field.\nfunc ClientIDContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldClientID, v))\n}\n\n// ScopesIsNil applies the IsNil predicate on the \"scopes\" field.\nfunc ScopesIsNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIsNull(FieldScopes))\n}\n\n// ScopesNotNil applies the NotNil predicate on the \"scopes\" field.\nfunc ScopesNotNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotNull(FieldScopes))\n}\n\n// NonceEQ applies the EQ predicate on the \"nonce\" field.\nfunc NonceEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldNonce, v))\n}\n\n// NonceNEQ applies the NEQ predicate on the \"nonce\" field.\nfunc NonceNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldNonce, v))\n}\n\n// NonceIn applies the In predicate on the \"nonce\" field.\nfunc NonceIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldNonce, vs...))\n}\n\n// NonceNotIn applies the NotIn predicate on the \"nonce\" field.\nfunc NonceNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldNonce, vs...))\n}\n\n// NonceGT applies the GT predicate on the \"nonce\" field.\nfunc NonceGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldNonce, v))\n}\n\n// NonceGTE applies the GTE predicate on the \"nonce\" field.\nfunc NonceGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldNonce, v))\n}\n\n// NonceLT applies the LT predicate on the \"nonce\" field.\nfunc NonceLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldNonce, v))\n}\n\n// NonceLTE applies the LTE predicate on the \"nonce\" field.\nfunc NonceLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldNonce, v))\n}\n\n// NonceContains applies the Contains predicate on the \"nonce\" field.\nfunc NonceContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldNonce, v))\n}\n\n// NonceHasPrefix applies the HasPrefix predicate on the \"nonce\" field.\nfunc NonceHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldNonce, v))\n}\n\n// NonceHasSuffix applies the HasSuffix predicate on the \"nonce\" field.\nfunc NonceHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldNonce, v))\n}\n\n// NonceEqualFold applies the EqualFold predicate on the \"nonce\" field.\nfunc NonceEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldNonce, v))\n}\n\n// NonceContainsFold applies the ContainsFold predicate on the \"nonce\" field.\nfunc NonceContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldNonce, v))\n}\n\n// RedirectURIEQ applies the EQ predicate on the \"redirect_uri\" field.\nfunc RedirectURIEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldRedirectURI, v))\n}\n\n// RedirectURINEQ applies the NEQ predicate on the \"redirect_uri\" field.\nfunc RedirectURINEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldRedirectURI, v))\n}\n\n// RedirectURIIn applies the In predicate on the \"redirect_uri\" field.\nfunc RedirectURIIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldRedirectURI, vs...))\n}\n\n// RedirectURINotIn applies the NotIn predicate on the \"redirect_uri\" field.\nfunc RedirectURINotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldRedirectURI, vs...))\n}\n\n// RedirectURIGT applies the GT predicate on the \"redirect_uri\" field.\nfunc RedirectURIGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldRedirectURI, v))\n}\n\n// RedirectURIGTE applies the GTE predicate on the \"redirect_uri\" field.\nfunc RedirectURIGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldRedirectURI, v))\n}\n\n// RedirectURILT applies the LT predicate on the \"redirect_uri\" field.\nfunc RedirectURILT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldRedirectURI, v))\n}\n\n// RedirectURILTE applies the LTE predicate on the \"redirect_uri\" field.\nfunc RedirectURILTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldRedirectURI, v))\n}\n\n// RedirectURIContains applies the Contains predicate on the \"redirect_uri\" field.\nfunc RedirectURIContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldRedirectURI, v))\n}\n\n// RedirectURIHasPrefix applies the HasPrefix predicate on the \"redirect_uri\" field.\nfunc RedirectURIHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldRedirectURI, v))\n}\n\n// RedirectURIHasSuffix applies the HasSuffix predicate on the \"redirect_uri\" field.\nfunc RedirectURIHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldRedirectURI, v))\n}\n\n// RedirectURIEqualFold applies the EqualFold predicate on the \"redirect_uri\" field.\nfunc RedirectURIEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldRedirectURI, v))\n}\n\n// RedirectURIContainsFold applies the ContainsFold predicate on the \"redirect_uri\" field.\nfunc RedirectURIContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldRedirectURI, v))\n}\n\n// ClaimsUserIDEQ applies the EQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDNEQ applies the NEQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDIn applies the In predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDNotIn applies the NotIn predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDGT applies the GT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDGTE applies the GTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLT applies the LT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLTE applies the LTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContains applies the Contains predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasPrefix applies the HasPrefix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasSuffix applies the HasSuffix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDEqualFold applies the EqualFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContainsFold applies the ContainsFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUsernameEQ applies the EQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameNEQ applies the NEQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameIn applies the In predicate on the \"claims_username\" field.\nfunc ClaimsUsernameIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameNotIn applies the NotIn predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameGT applies the GT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameGTE applies the GTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLT applies the LT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLTE applies the LTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContains applies the Contains predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasPrefix applies the HasPrefix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasSuffix applies the HasSuffix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameEqualFold applies the EqualFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContainsFold applies the ContainsFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldClaimsUsername, v))\n}\n\n// ClaimsEmailEQ applies the EQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailNEQ applies the NEQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailIn applies the In predicate on the \"claims_email\" field.\nfunc ClaimsEmailIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailNotIn applies the NotIn predicate on the \"claims_email\" field.\nfunc ClaimsEmailNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailGT applies the GT predicate on the \"claims_email\" field.\nfunc ClaimsEmailGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailGTE applies the GTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLT applies the LT predicate on the \"claims_email\" field.\nfunc ClaimsEmailLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLTE applies the LTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContains applies the Contains predicate on the \"claims_email\" field.\nfunc ClaimsEmailContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasPrefix applies the HasPrefix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasSuffix applies the HasSuffix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailEqualFold applies the EqualFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContainsFold applies the ContainsFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerifiedEQ applies the EQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedEQ(v bool) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsEmailVerifiedNEQ applies the NEQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedNEQ(v bool) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsGroupsIsNil applies the IsNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsIsNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIsNull(FieldClaimsGroups))\n}\n\n// ClaimsGroupsNotNil applies the NotNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsNotNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotNull(FieldClaimsGroups))\n}\n\n// ClaimsPreferredUsernameEQ applies the EQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameNEQ applies the NEQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameIn applies the In predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameNotIn applies the NotIn predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameGT applies the GT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameGTE applies the GTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLT applies the LT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLTE applies the LTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContains applies the Contains predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldClaimsPreferredUsername, v))\n}\n\n// ConnectorIDEQ applies the EQ predicate on the \"connector_id\" field.\nfunc ConnectorIDEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDNEQ applies the NEQ predicate on the \"connector_id\" field.\nfunc ConnectorIDNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDIn applies the In predicate on the \"connector_id\" field.\nfunc ConnectorIDIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDNotIn applies the NotIn predicate on the \"connector_id\" field.\nfunc ConnectorIDNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDGT applies the GT predicate on the \"connector_id\" field.\nfunc ConnectorIDGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldConnectorID, v))\n}\n\n// ConnectorIDGTE applies the GTE predicate on the \"connector_id\" field.\nfunc ConnectorIDGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldConnectorID, v))\n}\n\n// ConnectorIDLT applies the LT predicate on the \"connector_id\" field.\nfunc ConnectorIDLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldConnectorID, v))\n}\n\n// ConnectorIDLTE applies the LTE predicate on the \"connector_id\" field.\nfunc ConnectorIDLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldConnectorID, v))\n}\n\n// ConnectorIDContains applies the Contains predicate on the \"connector_id\" field.\nfunc ConnectorIDContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldConnectorID, v))\n}\n\n// ConnectorIDHasPrefix applies the HasPrefix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldConnectorID, v))\n}\n\n// ConnectorIDHasSuffix applies the HasSuffix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldConnectorID, v))\n}\n\n// ConnectorIDEqualFold applies the EqualFold predicate on the \"connector_id\" field.\nfunc ConnectorIDEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldConnectorID, v))\n}\n\n// ConnectorIDContainsFold applies the ContainsFold predicate on the \"connector_id\" field.\nfunc ConnectorIDContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldConnectorID, v))\n}\n\n// ConnectorDataEQ applies the EQ predicate on the \"connector_data\" field.\nfunc ConnectorDataEQ(v []byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataNEQ applies the NEQ predicate on the \"connector_data\" field.\nfunc ConnectorDataNEQ(v []byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataIn applies the In predicate on the \"connector_data\" field.\nfunc ConnectorDataIn(vs ...[]byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataNotIn applies the NotIn predicate on the \"connector_data\" field.\nfunc ConnectorDataNotIn(vs ...[]byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataGT applies the GT predicate on the \"connector_data\" field.\nfunc ConnectorDataGT(v []byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldConnectorData, v))\n}\n\n// ConnectorDataGTE applies the GTE predicate on the \"connector_data\" field.\nfunc ConnectorDataGTE(v []byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldConnectorData, v))\n}\n\n// ConnectorDataLT applies the LT predicate on the \"connector_data\" field.\nfunc ConnectorDataLT(v []byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldConnectorData, v))\n}\n\n// ConnectorDataLTE applies the LTE predicate on the \"connector_data\" field.\nfunc ConnectorDataLTE(v []byte) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldConnectorData, v))\n}\n\n// ConnectorDataIsNil applies the IsNil predicate on the \"connector_data\" field.\nfunc ConnectorDataIsNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIsNull(FieldConnectorData))\n}\n\n// ConnectorDataNotNil applies the NotNil predicate on the \"connector_data\" field.\nfunc ConnectorDataNotNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotNull(FieldConnectorData))\n}\n\n// ExpiryEQ applies the EQ predicate on the \"expiry\" field.\nfunc ExpiryEQ(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldExpiry, v))\n}\n\n// ExpiryNEQ applies the NEQ predicate on the \"expiry\" field.\nfunc ExpiryNEQ(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldExpiry, v))\n}\n\n// ExpiryIn applies the In predicate on the \"expiry\" field.\nfunc ExpiryIn(vs ...time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldExpiry, vs...))\n}\n\n// ExpiryNotIn applies the NotIn predicate on the \"expiry\" field.\nfunc ExpiryNotIn(vs ...time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldExpiry, vs...))\n}\n\n// ExpiryGT applies the GT predicate on the \"expiry\" field.\nfunc ExpiryGT(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldExpiry, v))\n}\n\n// ExpiryGTE applies the GTE predicate on the \"expiry\" field.\nfunc ExpiryGTE(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldExpiry, v))\n}\n\n// ExpiryLT applies the LT predicate on the \"expiry\" field.\nfunc ExpiryLT(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldExpiry, v))\n}\n\n// ExpiryLTE applies the LTE predicate on the \"expiry\" field.\nfunc ExpiryLTE(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldExpiry, v))\n}\n\n// CodeChallengeEQ applies the EQ predicate on the \"code_challenge\" field.\nfunc CodeChallengeEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeNEQ applies the NEQ predicate on the \"code_challenge\" field.\nfunc CodeChallengeNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeIn applies the In predicate on the \"code_challenge\" field.\nfunc CodeChallengeIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldCodeChallenge, vs...))\n}\n\n// CodeChallengeNotIn applies the NotIn predicate on the \"code_challenge\" field.\nfunc CodeChallengeNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldCodeChallenge, vs...))\n}\n\n// CodeChallengeGT applies the GT predicate on the \"code_challenge\" field.\nfunc CodeChallengeGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldCodeChallenge, v))\n}\n\n// CodeChallengeGTE applies the GTE predicate on the \"code_challenge\" field.\nfunc CodeChallengeGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldCodeChallenge, v))\n}\n\n// CodeChallengeLT applies the LT predicate on the \"code_challenge\" field.\nfunc CodeChallengeLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldCodeChallenge, v))\n}\n\n// CodeChallengeLTE applies the LTE predicate on the \"code_challenge\" field.\nfunc CodeChallengeLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldCodeChallenge, v))\n}\n\n// CodeChallengeContains applies the Contains predicate on the \"code_challenge\" field.\nfunc CodeChallengeContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldCodeChallenge, v))\n}\n\n// CodeChallengeHasPrefix applies the HasPrefix predicate on the \"code_challenge\" field.\nfunc CodeChallengeHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldCodeChallenge, v))\n}\n\n// CodeChallengeHasSuffix applies the HasSuffix predicate on the \"code_challenge\" field.\nfunc CodeChallengeHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldCodeChallenge, v))\n}\n\n// CodeChallengeEqualFold applies the EqualFold predicate on the \"code_challenge\" field.\nfunc CodeChallengeEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldCodeChallenge, v))\n}\n\n// CodeChallengeContainsFold applies the ContainsFold predicate on the \"code_challenge\" field.\nfunc CodeChallengeContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldCodeChallenge, v))\n}\n\n// CodeChallengeMethodEQ applies the EQ predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodNEQ applies the NEQ predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodNEQ(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodIn applies the In predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldCodeChallengeMethod, vs...))\n}\n\n// CodeChallengeMethodNotIn applies the NotIn predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodNotIn(vs ...string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldCodeChallengeMethod, vs...))\n}\n\n// CodeChallengeMethodGT applies the GT predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodGT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodGTE applies the GTE predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodGTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodLT applies the LT predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodLT(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodLTE applies the LTE predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodLTE(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodContains applies the Contains predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodContains(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContains(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodHasPrefix applies the HasPrefix predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodHasPrefix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasPrefix(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodHasSuffix applies the HasSuffix predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodHasSuffix(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldHasSuffix(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodEqualFold applies the EqualFold predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodEqualFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEqualFold(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodContainsFold applies the ContainsFold predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodContainsFold(v string) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldContainsFold(FieldCodeChallengeMethod, v))\n}\n\n// AuthTimeEQ applies the EQ predicate on the \"auth_time\" field.\nfunc AuthTimeEQ(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldEQ(FieldAuthTime, v))\n}\n\n// AuthTimeNEQ applies the NEQ predicate on the \"auth_time\" field.\nfunc AuthTimeNEQ(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNEQ(FieldAuthTime, v))\n}\n\n// AuthTimeIn applies the In predicate on the \"auth_time\" field.\nfunc AuthTimeIn(vs ...time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIn(FieldAuthTime, vs...))\n}\n\n// AuthTimeNotIn applies the NotIn predicate on the \"auth_time\" field.\nfunc AuthTimeNotIn(vs ...time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotIn(FieldAuthTime, vs...))\n}\n\n// AuthTimeGT applies the GT predicate on the \"auth_time\" field.\nfunc AuthTimeGT(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGT(FieldAuthTime, v))\n}\n\n// AuthTimeGTE applies the GTE predicate on the \"auth_time\" field.\nfunc AuthTimeGTE(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldGTE(FieldAuthTime, v))\n}\n\n// AuthTimeLT applies the LT predicate on the \"auth_time\" field.\nfunc AuthTimeLT(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLT(FieldAuthTime, v))\n}\n\n// AuthTimeLTE applies the LTE predicate on the \"auth_time\" field.\nfunc AuthTimeLTE(v time.Time) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldLTE(FieldAuthTime, v))\n}\n\n// AuthTimeIsNil applies the IsNil predicate on the \"auth_time\" field.\nfunc AuthTimeIsNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldIsNull(FieldAuthTime))\n}\n\n// AuthTimeNotNil applies the NotNil predicate on the \"auth_time\" field.\nfunc AuthTimeNotNil() predicate.AuthCode {\n\treturn predicate.AuthCode(sql.FieldNotNull(FieldAuthTime))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.AuthCode) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.AuthCode) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.AuthCode) predicate.AuthCode {\n\treturn predicate.AuthCode(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/authcode.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n)\n\n// AuthCode is the model entity for the AuthCode schema.\ntype AuthCode struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// ClientID holds the value of the \"client_id\" field.\n\tClientID string `json:\"client_id,omitempty\"`\n\t// Scopes holds the value of the \"scopes\" field.\n\tScopes []string `json:\"scopes,omitempty\"`\n\t// Nonce holds the value of the \"nonce\" field.\n\tNonce string `json:\"nonce,omitempty\"`\n\t// RedirectURI holds the value of the \"redirect_uri\" field.\n\tRedirectURI string `json:\"redirect_uri,omitempty\"`\n\t// ClaimsUserID holds the value of the \"claims_user_id\" field.\n\tClaimsUserID string `json:\"claims_user_id,omitempty\"`\n\t// ClaimsUsername holds the value of the \"claims_username\" field.\n\tClaimsUsername string `json:\"claims_username,omitempty\"`\n\t// ClaimsEmail holds the value of the \"claims_email\" field.\n\tClaimsEmail string `json:\"claims_email,omitempty\"`\n\t// ClaimsEmailVerified holds the value of the \"claims_email_verified\" field.\n\tClaimsEmailVerified bool `json:\"claims_email_verified,omitempty\"`\n\t// ClaimsGroups holds the value of the \"claims_groups\" field.\n\tClaimsGroups []string `json:\"claims_groups,omitempty\"`\n\t// ClaimsPreferredUsername holds the value of the \"claims_preferred_username\" field.\n\tClaimsPreferredUsername string `json:\"claims_preferred_username,omitempty\"`\n\t// ConnectorID holds the value of the \"connector_id\" field.\n\tConnectorID string `json:\"connector_id,omitempty\"`\n\t// ConnectorData holds the value of the \"connector_data\" field.\n\tConnectorData *[]byte `json:\"connector_data,omitempty\"`\n\t// Expiry holds the value of the \"expiry\" field.\n\tExpiry time.Time `json:\"expiry,omitempty\"`\n\t// CodeChallenge holds the value of the \"code_challenge\" field.\n\tCodeChallenge string `json:\"code_challenge,omitempty\"`\n\t// CodeChallengeMethod holds the value of the \"code_challenge_method\" field.\n\tCodeChallengeMethod string `json:\"code_challenge_method,omitempty\"`\n\t// AuthTime holds the value of the \"auth_time\" field.\n\tAuthTime     time.Time `json:\"auth_time,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*AuthCode) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase authcode.FieldScopes, authcode.FieldClaimsGroups, authcode.FieldConnectorData:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase authcode.FieldClaimsEmailVerified:\n\t\t\tvalues[i] = new(sql.NullBool)\n\t\tcase authcode.FieldID, authcode.FieldClientID, authcode.FieldNonce, authcode.FieldRedirectURI, authcode.FieldClaimsUserID, authcode.FieldClaimsUsername, authcode.FieldClaimsEmail, authcode.FieldClaimsPreferredUsername, authcode.FieldConnectorID, authcode.FieldCodeChallenge, authcode.FieldCodeChallengeMethod:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase authcode.FieldExpiry, authcode.FieldAuthTime:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the AuthCode fields.\nfunc (_m *AuthCode) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase authcode.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase authcode.FieldClientID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field client_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClientID = value.String\n\t\t\t}\n\t\tcase authcode.FieldScopes:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field scopes\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.Scopes); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field scopes: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase authcode.FieldNonce:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field nonce\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Nonce = value.String\n\t\t\t}\n\t\tcase authcode.FieldRedirectURI:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field redirect_uri\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.RedirectURI = value.String\n\t\t\t}\n\t\tcase authcode.FieldClaimsUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUserID = value.String\n\t\t\t}\n\t\tcase authcode.FieldClaimsUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUsername = value.String\n\t\t\t}\n\t\tcase authcode.FieldClaimsEmail:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmail = value.String\n\t\t\t}\n\t\tcase authcode.FieldClaimsEmailVerified:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email_verified\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmailVerified = value.Bool\n\t\t\t}\n\t\tcase authcode.FieldClaimsGroups:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_groups\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field claims_groups: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase authcode.FieldClaimsPreferredUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_preferred_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsPreferredUsername = value.String\n\t\t\t}\n\t\tcase authcode.FieldConnectorID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ConnectorID = value.String\n\t\t\t}\n\t\tcase authcode.FieldConnectorData:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_data\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.ConnectorData = value\n\t\t\t}\n\t\tcase authcode.FieldExpiry:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field expiry\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Expiry = value.Time\n\t\t\t}\n\t\tcase authcode.FieldCodeChallenge:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field code_challenge\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CodeChallenge = value.String\n\t\t\t}\n\t\tcase authcode.FieldCodeChallengeMethod:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field code_challenge_method\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CodeChallengeMethod = value.String\n\t\t\t}\n\t\tcase authcode.FieldAuthTime:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field auth_time\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.AuthTime = value.Time\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the AuthCode.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *AuthCode) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this AuthCode.\n// Note that you need to call AuthCode.Unwrap() before calling this method if this AuthCode\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *AuthCode) Update() *AuthCodeUpdateOne {\n\treturn NewAuthCodeClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the AuthCode entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *AuthCode) Unwrap() *AuthCode {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: AuthCode is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *AuthCode) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"AuthCode(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"client_id=\")\n\tbuilder.WriteString(_m.ClientID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"scopes=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Scopes))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"nonce=\")\n\tbuilder.WriteString(_m.Nonce)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"redirect_uri=\")\n\tbuilder.WriteString(_m.RedirectURI)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_user_id=\")\n\tbuilder.WriteString(_m.ClaimsUserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_username=\")\n\tbuilder.WriteString(_m.ClaimsUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email=\")\n\tbuilder.WriteString(_m.ClaimsEmail)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email_verified=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsEmailVerified))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_groups=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsGroups))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_preferred_username=\")\n\tbuilder.WriteString(_m.ClaimsPreferredUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"connector_id=\")\n\tbuilder.WriteString(_m.ConnectorID)\n\tbuilder.WriteString(\", \")\n\tif v := _m.ConnectorData; v != nil {\n\t\tbuilder.WriteString(\"connector_data=\")\n\t\tbuilder.WriteString(fmt.Sprintf(\"%v\", *v))\n\t}\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"expiry=\")\n\tbuilder.WriteString(_m.Expiry.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"code_challenge=\")\n\tbuilder.WriteString(_m.CodeChallenge)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"code_challenge_method=\")\n\tbuilder.WriteString(_m.CodeChallengeMethod)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"auth_time=\")\n\tbuilder.WriteString(_m.AuthTime.Format(time.ANSIC))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// AuthCodes is a parsable slice of AuthCode.\ntype AuthCodes []*AuthCode\n"
  },
  {
    "path": "storage/ent/db/authcode_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n)\n\n// AuthCodeCreate is the builder for creating a AuthCode entity.\ntype AuthCodeCreate struct {\n\tconfig\n\tmutation *AuthCodeMutation\n\thooks    []Hook\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_c *AuthCodeCreate) SetClientID(v string) *AuthCodeCreate {\n\t_c.mutation.SetClientID(v)\n\treturn _c\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_c *AuthCodeCreate) SetScopes(v []string) *AuthCodeCreate {\n\t_c.mutation.SetScopes(v)\n\treturn _c\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_c *AuthCodeCreate) SetNonce(v string) *AuthCodeCreate {\n\t_c.mutation.SetNonce(v)\n\treturn _c\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (_c *AuthCodeCreate) SetRedirectURI(v string) *AuthCodeCreate {\n\t_c.mutation.SetRedirectURI(v)\n\treturn _c\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_c *AuthCodeCreate) SetClaimsUserID(v string) *AuthCodeCreate {\n\t_c.mutation.SetClaimsUserID(v)\n\treturn _c\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_c *AuthCodeCreate) SetClaimsUsername(v string) *AuthCodeCreate {\n\t_c.mutation.SetClaimsUsername(v)\n\treturn _c\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_c *AuthCodeCreate) SetClaimsEmail(v string) *AuthCodeCreate {\n\t_c.mutation.SetClaimsEmail(v)\n\treturn _c\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_c *AuthCodeCreate) SetClaimsEmailVerified(v bool) *AuthCodeCreate {\n\t_c.mutation.SetClaimsEmailVerified(v)\n\treturn _c\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_c *AuthCodeCreate) SetClaimsGroups(v []string) *AuthCodeCreate {\n\t_c.mutation.SetClaimsGroups(v)\n\treturn _c\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_c *AuthCodeCreate) SetClaimsPreferredUsername(v string) *AuthCodeCreate {\n\t_c.mutation.SetClaimsPreferredUsername(v)\n\treturn _c\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_c *AuthCodeCreate) SetNillableClaimsPreferredUsername(v *string) *AuthCodeCreate {\n\tif v != nil {\n\t\t_c.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _c\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_c *AuthCodeCreate) SetConnectorID(v string) *AuthCodeCreate {\n\t_c.mutation.SetConnectorID(v)\n\treturn _c\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_c *AuthCodeCreate) SetConnectorData(v []byte) *AuthCodeCreate {\n\t_c.mutation.SetConnectorData(v)\n\treturn _c\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_c *AuthCodeCreate) SetExpiry(v time.Time) *AuthCodeCreate {\n\t_c.mutation.SetExpiry(v)\n\treturn _c\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_c *AuthCodeCreate) SetCodeChallenge(v string) *AuthCodeCreate {\n\t_c.mutation.SetCodeChallenge(v)\n\treturn _c\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_c *AuthCodeCreate) SetNillableCodeChallenge(v *string) *AuthCodeCreate {\n\tif v != nil {\n\t\t_c.SetCodeChallenge(*v)\n\t}\n\treturn _c\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_c *AuthCodeCreate) SetCodeChallengeMethod(v string) *AuthCodeCreate {\n\t_c.mutation.SetCodeChallengeMethod(v)\n\treturn _c\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_c *AuthCodeCreate) SetNillableCodeChallengeMethod(v *string) *AuthCodeCreate {\n\tif v != nil {\n\t\t_c.SetCodeChallengeMethod(*v)\n\t}\n\treturn _c\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (_c *AuthCodeCreate) SetAuthTime(v time.Time) *AuthCodeCreate {\n\t_c.mutation.SetAuthTime(v)\n\treturn _c\n}\n\n// SetNillableAuthTime sets the \"auth_time\" field if the given value is not nil.\nfunc (_c *AuthCodeCreate) SetNillableAuthTime(v *time.Time) *AuthCodeCreate {\n\tif v != nil {\n\t\t_c.SetAuthTime(*v)\n\t}\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *AuthCodeCreate) SetID(v string) *AuthCodeCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the AuthCodeMutation object of the builder.\nfunc (_c *AuthCodeCreate) Mutation() *AuthCodeMutation {\n\treturn _c.mutation\n}\n\n// Save creates the AuthCode in the database.\nfunc (_c *AuthCodeCreate) Save(ctx context.Context) (*AuthCode, error) {\n\t_c.defaults()\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *AuthCodeCreate) SaveX(ctx context.Context) *AuthCode {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *AuthCodeCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *AuthCodeCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// defaults sets the default values of the builder before save.\nfunc (_c *AuthCodeCreate) defaults() {\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\tv := authcode.DefaultClaimsPreferredUsername\n\t\t_c.mutation.SetClaimsPreferredUsername(v)\n\t}\n\tif _, ok := _c.mutation.CodeChallenge(); !ok {\n\t\tv := authcode.DefaultCodeChallenge\n\t\t_c.mutation.SetCodeChallenge(v)\n\t}\n\tif _, ok := _c.mutation.CodeChallengeMethod(); !ok {\n\t\tv := authcode.DefaultCodeChallengeMethod\n\t\t_c.mutation.SetCodeChallengeMethod(v)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *AuthCodeCreate) check() error {\n\tif _, ok := _c.mutation.ClientID(); !ok {\n\t\treturn &ValidationError{Name: \"client_id\", err: errors.New(`db: missing required field \"AuthCode.client_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ClientID(); ok {\n\t\tif err := authcode.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Nonce(); !ok {\n\t\treturn &ValidationError{Name: \"nonce\", err: errors.New(`db: missing required field \"AuthCode.nonce\"`)}\n\t}\n\tif v, ok := _c.mutation.Nonce(); ok {\n\t\tif err := authcode.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.nonce\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.RedirectURI(); !ok {\n\t\treturn &ValidationError{Name: \"redirect_uri\", err: errors.New(`db: missing required field \"AuthCode.redirect_uri\"`)}\n\t}\n\tif v, ok := _c.mutation.RedirectURI(); ok {\n\t\tif err := authcode.RedirectURIValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"redirect_uri\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.redirect_uri\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsUserID(); !ok {\n\t\treturn &ValidationError{Name: \"claims_user_id\", err: errors.New(`db: missing required field \"AuthCode.claims_user_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ClaimsUserID(); ok {\n\t\tif err := authcode.ClaimsUserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_user_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_user_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_username\", err: errors.New(`db: missing required field \"AuthCode.claims_username\"`)}\n\t}\n\tif v, ok := _c.mutation.ClaimsUsername(); ok {\n\t\tif err := authcode.ClaimsUsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_username\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_username\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmail(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email\", err: errors.New(`db: missing required field \"AuthCode.claims_email\"`)}\n\t}\n\tif v, ok := _c.mutation.ClaimsEmail(); ok {\n\t\tif err := authcode.ClaimsEmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_email\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_email\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmailVerified(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email_verified\", err: errors.New(`db: missing required field \"AuthCode.claims_email_verified\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_preferred_username\", err: errors.New(`db: missing required field \"AuthCode.claims_preferred_username\"`)}\n\t}\n\tif _, ok := _c.mutation.ConnectorID(); !ok {\n\t\treturn &ValidationError{Name: \"connector_id\", err: errors.New(`db: missing required field \"AuthCode.connector_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ConnectorID(); ok {\n\t\tif err := authcode.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.connector_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Expiry(); !ok {\n\t\treturn &ValidationError{Name: \"expiry\", err: errors.New(`db: missing required field \"AuthCode.expiry\"`)}\n\t}\n\tif _, ok := _c.mutation.CodeChallenge(); !ok {\n\t\treturn &ValidationError{Name: \"code_challenge\", err: errors.New(`db: missing required field \"AuthCode.code_challenge\"`)}\n\t}\n\tif _, ok := _c.mutation.CodeChallengeMethod(); !ok {\n\t\treturn &ValidationError{Name: \"code_challenge_method\", err: errors.New(`db: missing required field \"AuthCode.code_challenge_method\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := authcode.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *AuthCodeCreate) sqlSave(ctx context.Context) (*AuthCode, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected AuthCode.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *AuthCodeCreate) createSpec() (*AuthCode, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &AuthCode{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(authcode.Table, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.ClientID(); ok {\n\t\t_spec.SetField(authcode.FieldClientID, field.TypeString, value)\n\t\t_node.ClientID = value\n\t}\n\tif value, ok := _c.mutation.Scopes(); ok {\n\t\t_spec.SetField(authcode.FieldScopes, field.TypeJSON, value)\n\t\t_node.Scopes = value\n\t}\n\tif value, ok := _c.mutation.Nonce(); ok {\n\t\t_spec.SetField(authcode.FieldNonce, field.TypeString, value)\n\t\t_node.Nonce = value\n\t}\n\tif value, ok := _c.mutation.RedirectURI(); ok {\n\t\t_spec.SetField(authcode.FieldRedirectURI, field.TypeString, value)\n\t\t_node.RedirectURI = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsUserID, field.TypeString, value)\n\t\t_node.ClaimsUserID = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsUsername, field.TypeString, value)\n\t\t_node.ClaimsUsername = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsEmail, field.TypeString, value)\n\t\t_node.ClaimsEmail = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsEmailVerified, field.TypeBool, value)\n\t\t_node.ClaimsEmailVerified = value\n\t}\n\tif value, ok := _c.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsGroups, field.TypeJSON, value)\n\t\t_node.ClaimsGroups = value\n\t}\n\tif value, ok := _c.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsPreferredUsername, field.TypeString, value)\n\t\t_node.ClaimsPreferredUsername = value\n\t}\n\tif value, ok := _c.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authcode.FieldConnectorID, field.TypeString, value)\n\t\t_node.ConnectorID = value\n\t}\n\tif value, ok := _c.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(authcode.FieldConnectorData, field.TypeBytes, value)\n\t\t_node.ConnectorData = &value\n\t}\n\tif value, ok := _c.mutation.Expiry(); ok {\n\t\t_spec.SetField(authcode.FieldExpiry, field.TypeTime, value)\n\t\t_node.Expiry = value\n\t}\n\tif value, ok := _c.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(authcode.FieldCodeChallenge, field.TypeString, value)\n\t\t_node.CodeChallenge = value\n\t}\n\tif value, ok := _c.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(authcode.FieldCodeChallengeMethod, field.TypeString, value)\n\t\t_node.CodeChallengeMethod = value\n\t}\n\tif value, ok := _c.mutation.AuthTime(); ok {\n\t\t_spec.SetField(authcode.FieldAuthTime, field.TypeTime, value)\n\t\t_node.AuthTime = value\n\t}\n\treturn _node, _spec\n}\n\n// AuthCodeCreateBulk is the builder for creating many AuthCode entities in bulk.\ntype AuthCodeCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*AuthCodeCreate\n}\n\n// Save creates the AuthCode entities in the database.\nfunc (_c *AuthCodeCreateBulk) Save(ctx context.Context) ([]*AuthCode, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*AuthCode, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tbuilder.defaults()\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*AuthCodeMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *AuthCodeCreateBulk) SaveX(ctx context.Context) []*AuthCode {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *AuthCodeCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *AuthCodeCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/authcode_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthCodeDelete is the builder for deleting a AuthCode entity.\ntype AuthCodeDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *AuthCodeMutation\n}\n\n// Where appends a list predicates to the AuthCodeDelete builder.\nfunc (_d *AuthCodeDelete) Where(ps ...predicate.AuthCode) *AuthCodeDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *AuthCodeDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *AuthCodeDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *AuthCodeDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(authcode.Table, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// AuthCodeDeleteOne is the builder for deleting a single AuthCode entity.\ntype AuthCodeDeleteOne struct {\n\t_d *AuthCodeDelete\n}\n\n// Where appends a list predicates to the AuthCodeDelete builder.\nfunc (_d *AuthCodeDeleteOne) Where(ps ...predicate.AuthCode) *AuthCodeDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *AuthCodeDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{authcode.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *AuthCodeDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/authcode_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthCodeQuery is the builder for querying AuthCode entities.\ntype AuthCodeQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []authcode.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.AuthCode\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the AuthCodeQuery builder.\nfunc (_q *AuthCodeQuery) Where(ps ...predicate.AuthCode) *AuthCodeQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *AuthCodeQuery) Limit(limit int) *AuthCodeQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *AuthCodeQuery) Offset(offset int) *AuthCodeQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *AuthCodeQuery) Unique(unique bool) *AuthCodeQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *AuthCodeQuery) Order(o ...authcode.OrderOption) *AuthCodeQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first AuthCode entity from the query.\n// Returns a *NotFoundError when no AuthCode was found.\nfunc (_q *AuthCodeQuery) First(ctx context.Context) (*AuthCode, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{authcode.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) FirstX(ctx context.Context) *AuthCode {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first AuthCode ID from the query.\n// Returns a *NotFoundError when no AuthCode ID was found.\nfunc (_q *AuthCodeQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{authcode.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single AuthCode entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one AuthCode entity is found.\n// Returns a *NotFoundError when no AuthCode entities are found.\nfunc (_q *AuthCodeQuery) Only(ctx context.Context) (*AuthCode, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{authcode.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{authcode.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) OnlyX(ctx context.Context) *AuthCode {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only AuthCode ID in the query.\n// Returns a *NotSingularError when more than one AuthCode ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *AuthCodeQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{authcode.Label}\n\tdefault:\n\t\terr = &NotSingularError{authcode.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of AuthCodes.\nfunc (_q *AuthCodeQuery) All(ctx context.Context) ([]*AuthCode, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*AuthCode, *AuthCodeQuery]()\n\treturn withInterceptors[[]*AuthCode](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) AllX(ctx context.Context) []*AuthCode {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of AuthCode IDs.\nfunc (_q *AuthCodeQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(authcode.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *AuthCodeQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*AuthCodeQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *AuthCodeQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *AuthCodeQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the AuthCodeQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *AuthCodeQuery) Clone() *AuthCodeQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &AuthCodeQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]authcode.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.AuthCode{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tClientID string `json:\"client_id,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.AuthCode.Query().\n//\t\tGroupBy(authcode.FieldClientID).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *AuthCodeQuery) GroupBy(field string, fields ...string) *AuthCodeGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &AuthCodeGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = authcode.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tClientID string `json:\"client_id,omitempty\"`\n//\t}\n//\n//\tclient.AuthCode.Query().\n//\t\tSelect(authcode.FieldClientID).\n//\t\tScan(ctx, &v)\nfunc (_q *AuthCodeQuery) Select(fields ...string) *AuthCodeSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &AuthCodeSelect{AuthCodeQuery: _q}\n\tsbuild.label = authcode.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a AuthCodeSelect configured with the given aggregations.\nfunc (_q *AuthCodeQuery) Aggregate(fns ...AggregateFunc) *AuthCodeSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *AuthCodeQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !authcode.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *AuthCodeQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthCode, error) {\n\tvar (\n\t\tnodes = []*AuthCode{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*AuthCode).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &AuthCode{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *AuthCodeQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *AuthCodeQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(authcode.Table, authcode.Columns, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, authcode.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != authcode.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *AuthCodeQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(authcode.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = authcode.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// AuthCodeGroupBy is the group-by builder for AuthCode entities.\ntype AuthCodeGroupBy struct {\n\tselector\n\tbuild *AuthCodeQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *AuthCodeGroupBy) Aggregate(fns ...AggregateFunc) *AuthCodeGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *AuthCodeGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*AuthCodeQuery, *AuthCodeGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *AuthCodeGroupBy) sqlScan(ctx context.Context, root *AuthCodeQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// AuthCodeSelect is the builder for selecting fields of AuthCode entities.\ntype AuthCodeSelect struct {\n\t*AuthCodeQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *AuthCodeSelect) Aggregate(fns ...AggregateFunc) *AuthCodeSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *AuthCodeSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*AuthCodeQuery, *AuthCodeSelect](ctx, _s.AuthCodeQuery, _s, _s.inters, v)\n}\n\nfunc (_s *AuthCodeSelect) sqlScan(ctx context.Context, root *AuthCodeQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/authcode_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthCodeUpdate is the builder for updating AuthCode entities.\ntype AuthCodeUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *AuthCodeMutation\n}\n\n// Where appends a list predicates to the AuthCodeUpdate builder.\nfunc (_u *AuthCodeUpdate) Where(ps ...predicate.AuthCode) *AuthCodeUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *AuthCodeUpdate) SetClientID(v string) *AuthCodeUpdate {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableClientID(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *AuthCodeUpdate) SetScopes(v []string) *AuthCodeUpdate {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *AuthCodeUpdate) AppendScopes(v []string) *AuthCodeUpdate {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *AuthCodeUpdate) ClearScopes() *AuthCodeUpdate {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *AuthCodeUpdate) SetNonce(v string) *AuthCodeUpdate {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableNonce(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (_u *AuthCodeUpdate) SetRedirectURI(v string) *AuthCodeUpdate {\n\t_u.mutation.SetRedirectURI(v)\n\treturn _u\n}\n\n// SetNillableRedirectURI sets the \"redirect_uri\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableRedirectURI(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetRedirectURI(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *AuthCodeUpdate) SetClaimsUserID(v string) *AuthCodeUpdate {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableClaimsUserID(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *AuthCodeUpdate) SetClaimsUsername(v string) *AuthCodeUpdate {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableClaimsUsername(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *AuthCodeUpdate) SetClaimsEmail(v string) *AuthCodeUpdate {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableClaimsEmail(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *AuthCodeUpdate) SetClaimsEmailVerified(v bool) *AuthCodeUpdate {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableClaimsEmailVerified(v *bool) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *AuthCodeUpdate) SetClaimsGroups(v []string) *AuthCodeUpdate {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *AuthCodeUpdate) AppendClaimsGroups(v []string) *AuthCodeUpdate {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *AuthCodeUpdate) ClearClaimsGroups() *AuthCodeUpdate {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *AuthCodeUpdate) SetClaimsPreferredUsername(v string) *AuthCodeUpdate {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableClaimsPreferredUsername(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *AuthCodeUpdate) SetConnectorID(v string) *AuthCodeUpdate {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableConnectorID(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *AuthCodeUpdate) SetConnectorData(v []byte) *AuthCodeUpdate {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *AuthCodeUpdate) ClearConnectorData() *AuthCodeUpdate {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *AuthCodeUpdate) SetExpiry(v time.Time) *AuthCodeUpdate {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableExpiry(v *time.Time) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_u *AuthCodeUpdate) SetCodeChallenge(v string) *AuthCodeUpdate {\n\t_u.mutation.SetCodeChallenge(v)\n\treturn _u\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableCodeChallenge(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetCodeChallenge(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_u *AuthCodeUpdate) SetCodeChallengeMethod(v string) *AuthCodeUpdate {\n\t_u.mutation.SetCodeChallengeMethod(v)\n\treturn _u\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableCodeChallengeMethod(v *string) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetCodeChallengeMethod(*v)\n\t}\n\treturn _u\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (_u *AuthCodeUpdate) SetAuthTime(v time.Time) *AuthCodeUpdate {\n\t_u.mutation.SetAuthTime(v)\n\treturn _u\n}\n\n// SetNillableAuthTime sets the \"auth_time\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdate) SetNillableAuthTime(v *time.Time) *AuthCodeUpdate {\n\tif v != nil {\n\t\t_u.SetAuthTime(*v)\n\t}\n\treturn _u\n}\n\n// ClearAuthTime clears the value of the \"auth_time\" field.\nfunc (_u *AuthCodeUpdate) ClearAuthTime() *AuthCodeUpdate {\n\t_u.mutation.ClearAuthTime()\n\treturn _u\n}\n\n// Mutation returns the AuthCodeMutation object of the builder.\nfunc (_u *AuthCodeUpdate) Mutation() *AuthCodeMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *AuthCodeUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *AuthCodeUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *AuthCodeUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *AuthCodeUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *AuthCodeUpdate) check() error {\n\tif v, ok := _u.mutation.ClientID(); ok {\n\t\tif err := authcode.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Nonce(); ok {\n\t\tif err := authcode.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.nonce\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.RedirectURI(); ok {\n\t\tif err := authcode.RedirectURIValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"redirect_uri\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.redirect_uri\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUserID(); ok {\n\t\tif err := authcode.ClaimsUserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_user_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUsername(); ok {\n\t\tif err := authcode.ClaimsUsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_username\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_username\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsEmail(); ok {\n\t\tif err := authcode.ClaimsEmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_email\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_email\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := authcode.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.connector_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *AuthCodeUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(authcode.Table, authcode.Columns, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(authcode.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(authcode.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authcode.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(authcode.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(authcode.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.RedirectURI(); ok {\n\t\t_spec.SetField(authcode.FieldRedirectURI, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authcode.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(authcode.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authcode.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(authcode.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(authcode.FieldConnectorData, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(authcode.FieldExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(authcode.FieldCodeChallenge, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(authcode.FieldCodeChallengeMethod, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.AuthTime(); ok {\n\t\t_spec.SetField(authcode.FieldAuthTime, field.TypeTime, value)\n\t}\n\tif _u.mutation.AuthTimeCleared() {\n\t\t_spec.ClearField(authcode.FieldAuthTime, field.TypeTime)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{authcode.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// AuthCodeUpdateOne is the builder for updating a single AuthCode entity.\ntype AuthCodeUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *AuthCodeMutation\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *AuthCodeUpdateOne) SetClientID(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableClientID(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *AuthCodeUpdateOne) SetScopes(v []string) *AuthCodeUpdateOne {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *AuthCodeUpdateOne) AppendScopes(v []string) *AuthCodeUpdateOne {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *AuthCodeUpdateOne) ClearScopes() *AuthCodeUpdateOne {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *AuthCodeUpdateOne) SetNonce(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableNonce(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (_u *AuthCodeUpdateOne) SetRedirectURI(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetRedirectURI(v)\n\treturn _u\n}\n\n// SetNillableRedirectURI sets the \"redirect_uri\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableRedirectURI(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetRedirectURI(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *AuthCodeUpdateOne) SetClaimsUserID(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableClaimsUserID(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *AuthCodeUpdateOne) SetClaimsUsername(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableClaimsUsername(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *AuthCodeUpdateOne) SetClaimsEmail(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableClaimsEmail(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *AuthCodeUpdateOne) SetClaimsEmailVerified(v bool) *AuthCodeUpdateOne {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableClaimsEmailVerified(v *bool) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *AuthCodeUpdateOne) SetClaimsGroups(v []string) *AuthCodeUpdateOne {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *AuthCodeUpdateOne) AppendClaimsGroups(v []string) *AuthCodeUpdateOne {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *AuthCodeUpdateOne) ClearClaimsGroups() *AuthCodeUpdateOne {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *AuthCodeUpdateOne) SetClaimsPreferredUsername(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableClaimsPreferredUsername(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *AuthCodeUpdateOne) SetConnectorID(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableConnectorID(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *AuthCodeUpdateOne) SetConnectorData(v []byte) *AuthCodeUpdateOne {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *AuthCodeUpdateOne) ClearConnectorData() *AuthCodeUpdateOne {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *AuthCodeUpdateOne) SetExpiry(v time.Time) *AuthCodeUpdateOne {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableExpiry(v *time.Time) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_u *AuthCodeUpdateOne) SetCodeChallenge(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetCodeChallenge(v)\n\treturn _u\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableCodeChallenge(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetCodeChallenge(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_u *AuthCodeUpdateOne) SetCodeChallengeMethod(v string) *AuthCodeUpdateOne {\n\t_u.mutation.SetCodeChallengeMethod(v)\n\treturn _u\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableCodeChallengeMethod(v *string) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetCodeChallengeMethod(*v)\n\t}\n\treturn _u\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (_u *AuthCodeUpdateOne) SetAuthTime(v time.Time) *AuthCodeUpdateOne {\n\t_u.mutation.SetAuthTime(v)\n\treturn _u\n}\n\n// SetNillableAuthTime sets the \"auth_time\" field if the given value is not nil.\nfunc (_u *AuthCodeUpdateOne) SetNillableAuthTime(v *time.Time) *AuthCodeUpdateOne {\n\tif v != nil {\n\t\t_u.SetAuthTime(*v)\n\t}\n\treturn _u\n}\n\n// ClearAuthTime clears the value of the \"auth_time\" field.\nfunc (_u *AuthCodeUpdateOne) ClearAuthTime() *AuthCodeUpdateOne {\n\t_u.mutation.ClearAuthTime()\n\treturn _u\n}\n\n// Mutation returns the AuthCodeMutation object of the builder.\nfunc (_u *AuthCodeUpdateOne) Mutation() *AuthCodeMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the AuthCodeUpdate builder.\nfunc (_u *AuthCodeUpdateOne) Where(ps ...predicate.AuthCode) *AuthCodeUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *AuthCodeUpdateOne) Select(field string, fields ...string) *AuthCodeUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated AuthCode entity.\nfunc (_u *AuthCodeUpdateOne) Save(ctx context.Context) (*AuthCode, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *AuthCodeUpdateOne) SaveX(ctx context.Context) *AuthCode {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *AuthCodeUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *AuthCodeUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *AuthCodeUpdateOne) check() error {\n\tif v, ok := _u.mutation.ClientID(); ok {\n\t\tif err := authcode.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Nonce(); ok {\n\t\tif err := authcode.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.nonce\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.RedirectURI(); ok {\n\t\tif err := authcode.RedirectURIValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"redirect_uri\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.redirect_uri\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUserID(); ok {\n\t\tif err := authcode.ClaimsUserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_user_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUsername(); ok {\n\t\tif err := authcode.ClaimsUsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_username\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_username\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsEmail(); ok {\n\t\tif err := authcode.ClaimsEmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_email\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.claims_email\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := authcode.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"AuthCode.connector_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *AuthCodeUpdateOne) sqlSave(ctx context.Context) (_node *AuthCode, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(authcode.Table, authcode.Columns, sqlgraph.NewFieldSpec(authcode.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"AuthCode.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, authcode.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !authcode.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != authcode.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(authcode.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(authcode.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authcode.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(authcode.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(authcode.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.RedirectURI(); ok {\n\t\t_spec.SetField(authcode.FieldRedirectURI, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authcode.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(authcode.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(authcode.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authcode.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(authcode.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(authcode.FieldConnectorData, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(authcode.FieldExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(authcode.FieldCodeChallenge, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(authcode.FieldCodeChallengeMethod, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.AuthTime(); ok {\n\t\t_spec.SetField(authcode.FieldAuthTime, field.TypeTime, value)\n\t}\n\tif _u.mutation.AuthTimeCleared() {\n\t\t_spec.ClearField(authcode.FieldAuthTime, field.TypeTime)\n\t}\n\t_node = &AuthCode{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{authcode.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/authrequest/authrequest.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage authrequest\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the authrequest type in the database.\n\tLabel = \"auth_request\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldClientID holds the string denoting the client_id field in the database.\n\tFieldClientID = \"client_id\"\n\t// FieldScopes holds the string denoting the scopes field in the database.\n\tFieldScopes = \"scopes\"\n\t// FieldResponseTypes holds the string denoting the response_types field in the database.\n\tFieldResponseTypes = \"response_types\"\n\t// FieldRedirectURI holds the string denoting the redirect_uri field in the database.\n\tFieldRedirectURI = \"redirect_uri\"\n\t// FieldNonce holds the string denoting the nonce field in the database.\n\tFieldNonce = \"nonce\"\n\t// FieldState holds the string denoting the state field in the database.\n\tFieldState = \"state\"\n\t// FieldForceApprovalPrompt holds the string denoting the force_approval_prompt field in the database.\n\tFieldForceApprovalPrompt = \"force_approval_prompt\"\n\t// FieldLoggedIn holds the string denoting the logged_in field in the database.\n\tFieldLoggedIn = \"logged_in\"\n\t// FieldClaimsUserID holds the string denoting the claims_user_id field in the database.\n\tFieldClaimsUserID = \"claims_user_id\"\n\t// FieldClaimsUsername holds the string denoting the claims_username field in the database.\n\tFieldClaimsUsername = \"claims_username\"\n\t// FieldClaimsEmail holds the string denoting the claims_email field in the database.\n\tFieldClaimsEmail = \"claims_email\"\n\t// FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database.\n\tFieldClaimsEmailVerified = \"claims_email_verified\"\n\t// FieldClaimsGroups holds the string denoting the claims_groups field in the database.\n\tFieldClaimsGroups = \"claims_groups\"\n\t// FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database.\n\tFieldClaimsPreferredUsername = \"claims_preferred_username\"\n\t// FieldConnectorID holds the string denoting the connector_id field in the database.\n\tFieldConnectorID = \"connector_id\"\n\t// FieldConnectorData holds the string denoting the connector_data field in the database.\n\tFieldConnectorData = \"connector_data\"\n\t// FieldExpiry holds the string denoting the expiry field in the database.\n\tFieldExpiry = \"expiry\"\n\t// FieldCodeChallenge holds the string denoting the code_challenge field in the database.\n\tFieldCodeChallenge = \"code_challenge\"\n\t// FieldCodeChallengeMethod holds the string denoting the code_challenge_method field in the database.\n\tFieldCodeChallengeMethod = \"code_challenge_method\"\n\t// FieldHmacKey holds the string denoting the hmac_key field in the database.\n\tFieldHmacKey = \"hmac_key\"\n\t// FieldMfaValidated holds the string denoting the mfa_validated field in the database.\n\tFieldMfaValidated = \"mfa_validated\"\n\t// FieldPrompt holds the string denoting the prompt field in the database.\n\tFieldPrompt = \"prompt\"\n\t// FieldMaxAge holds the string denoting the max_age field in the database.\n\tFieldMaxAge = \"max_age\"\n\t// FieldAuthTime holds the string denoting the auth_time field in the database.\n\tFieldAuthTime = \"auth_time\"\n\t// Table holds the table name of the authrequest in the database.\n\tTable = \"auth_requests\"\n)\n\n// Columns holds all SQL columns for authrequest fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldClientID,\n\tFieldScopes,\n\tFieldResponseTypes,\n\tFieldRedirectURI,\n\tFieldNonce,\n\tFieldState,\n\tFieldForceApprovalPrompt,\n\tFieldLoggedIn,\n\tFieldClaimsUserID,\n\tFieldClaimsUsername,\n\tFieldClaimsEmail,\n\tFieldClaimsEmailVerified,\n\tFieldClaimsGroups,\n\tFieldClaimsPreferredUsername,\n\tFieldConnectorID,\n\tFieldConnectorData,\n\tFieldExpiry,\n\tFieldCodeChallenge,\n\tFieldCodeChallengeMethod,\n\tFieldHmacKey,\n\tFieldMfaValidated,\n\tFieldPrompt,\n\tFieldMaxAge,\n\tFieldAuthTime,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// DefaultClaimsPreferredUsername holds the default value on creation for the \"claims_preferred_username\" field.\n\tDefaultClaimsPreferredUsername string\n\t// DefaultCodeChallenge holds the default value on creation for the \"code_challenge\" field.\n\tDefaultCodeChallenge string\n\t// DefaultCodeChallengeMethod holds the default value on creation for the \"code_challenge_method\" field.\n\tDefaultCodeChallengeMethod string\n\t// DefaultMfaValidated holds the default value on creation for the \"mfa_validated\" field.\n\tDefaultMfaValidated bool\n\t// DefaultPrompt holds the default value on creation for the \"prompt\" field.\n\tDefaultPrompt string\n\t// DefaultMaxAge holds the default value on creation for the \"max_age\" field.\n\tDefaultMaxAge int\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the AuthRequest queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByClientID orders the results by the client_id field.\nfunc ByClientID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClientID, opts...).ToFunc()\n}\n\n// ByRedirectURI orders the results by the redirect_uri field.\nfunc ByRedirectURI(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldRedirectURI, opts...).ToFunc()\n}\n\n// ByNonce orders the results by the nonce field.\nfunc ByNonce(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldNonce, opts...).ToFunc()\n}\n\n// ByState orders the results by the state field.\nfunc ByState(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldState, opts...).ToFunc()\n}\n\n// ByForceApprovalPrompt orders the results by the force_approval_prompt field.\nfunc ByForceApprovalPrompt(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldForceApprovalPrompt, opts...).ToFunc()\n}\n\n// ByLoggedIn orders the results by the logged_in field.\nfunc ByLoggedIn(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldLoggedIn, opts...).ToFunc()\n}\n\n// ByClaimsUserID orders the results by the claims_user_id field.\nfunc ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUserID, opts...).ToFunc()\n}\n\n// ByClaimsUsername orders the results by the claims_username field.\nfunc ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUsername, opts...).ToFunc()\n}\n\n// ByClaimsEmail orders the results by the claims_email field.\nfunc ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmail, opts...).ToFunc()\n}\n\n// ByClaimsEmailVerified orders the results by the claims_email_verified field.\nfunc ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc()\n}\n\n// ByClaimsPreferredUsername orders the results by the claims_preferred_username field.\nfunc ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc()\n}\n\n// ByConnectorID orders the results by the connector_id field.\nfunc ByConnectorID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldConnectorID, opts...).ToFunc()\n}\n\n// ByExpiry orders the results by the expiry field.\nfunc ByExpiry(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldExpiry, opts...).ToFunc()\n}\n\n// ByCodeChallenge orders the results by the code_challenge field.\nfunc ByCodeChallenge(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCodeChallenge, opts...).ToFunc()\n}\n\n// ByCodeChallengeMethod orders the results by the code_challenge_method field.\nfunc ByCodeChallengeMethod(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCodeChallengeMethod, opts...).ToFunc()\n}\n\n// ByMfaValidated orders the results by the mfa_validated field.\nfunc ByMfaValidated(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldMfaValidated, opts...).ToFunc()\n}\n\n// ByPrompt orders the results by the prompt field.\nfunc ByPrompt(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldPrompt, opts...).ToFunc()\n}\n\n// ByMaxAge orders the results by the max_age field.\nfunc ByMaxAge(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldMaxAge, opts...).ToFunc()\n}\n\n// ByAuthTime orders the results by the auth_time field.\nfunc ByAuthTime(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldAuthTime, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/authrequest/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage authrequest\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldID, id))\n}\n\n// ClientID applies equality check predicate on the \"client_id\" field. It's identical to ClientIDEQ.\nfunc ClientID(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClientID, v))\n}\n\n// RedirectURI applies equality check predicate on the \"redirect_uri\" field. It's identical to RedirectURIEQ.\nfunc RedirectURI(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldRedirectURI, v))\n}\n\n// Nonce applies equality check predicate on the \"nonce\" field. It's identical to NonceEQ.\nfunc Nonce(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldNonce, v))\n}\n\n// State applies equality check predicate on the \"state\" field. It's identical to StateEQ.\nfunc State(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldState, v))\n}\n\n// ForceApprovalPrompt applies equality check predicate on the \"force_approval_prompt\" field. It's identical to ForceApprovalPromptEQ.\nfunc ForceApprovalPrompt(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldForceApprovalPrompt, v))\n}\n\n// LoggedIn applies equality check predicate on the \"logged_in\" field. It's identical to LoggedInEQ.\nfunc LoggedIn(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldLoggedIn, v))\n}\n\n// ClaimsUserID applies equality check predicate on the \"claims_user_id\" field. It's identical to ClaimsUserIDEQ.\nfunc ClaimsUserID(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUsername applies equality check predicate on the \"claims_username\" field. It's identical to ClaimsUsernameEQ.\nfunc ClaimsUsername(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsEmail applies equality check predicate on the \"claims_email\" field. It's identical to ClaimsEmailEQ.\nfunc ClaimsEmail(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerified applies equality check predicate on the \"claims_email_verified\" field. It's identical to ClaimsEmailVerifiedEQ.\nfunc ClaimsEmailVerified(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsPreferredUsername applies equality check predicate on the \"claims_preferred_username\" field. It's identical to ClaimsPreferredUsernameEQ.\nfunc ClaimsPreferredUsername(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ConnectorID applies equality check predicate on the \"connector_id\" field. It's identical to ConnectorIDEQ.\nfunc ConnectorID(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorData applies equality check predicate on the \"connector_data\" field. It's identical to ConnectorDataEQ.\nfunc ConnectorData(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// Expiry applies equality check predicate on the \"expiry\" field. It's identical to ExpiryEQ.\nfunc Expiry(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldExpiry, v))\n}\n\n// CodeChallenge applies equality check predicate on the \"code_challenge\" field. It's identical to CodeChallengeEQ.\nfunc CodeChallenge(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeMethod applies equality check predicate on the \"code_challenge_method\" field. It's identical to CodeChallengeMethodEQ.\nfunc CodeChallengeMethod(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldCodeChallengeMethod, v))\n}\n\n// HmacKey applies equality check predicate on the \"hmac_key\" field. It's identical to HmacKeyEQ.\nfunc HmacKey(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldHmacKey, v))\n}\n\n// MfaValidated applies equality check predicate on the \"mfa_validated\" field. It's identical to MfaValidatedEQ.\nfunc MfaValidated(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldMfaValidated, v))\n}\n\n// Prompt applies equality check predicate on the \"prompt\" field. It's identical to PromptEQ.\nfunc Prompt(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldPrompt, v))\n}\n\n// MaxAge applies equality check predicate on the \"max_age\" field. It's identical to MaxAgeEQ.\nfunc MaxAge(v int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldMaxAge, v))\n}\n\n// AuthTime applies equality check predicate on the \"auth_time\" field. It's identical to AuthTimeEQ.\nfunc AuthTime(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldAuthTime, v))\n}\n\n// ClientIDEQ applies the EQ predicate on the \"client_id\" field.\nfunc ClientIDEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClientID, v))\n}\n\n// ClientIDNEQ applies the NEQ predicate on the \"client_id\" field.\nfunc ClientIDNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldClientID, v))\n}\n\n// ClientIDIn applies the In predicate on the \"client_id\" field.\nfunc ClientIDIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldClientID, vs...))\n}\n\n// ClientIDNotIn applies the NotIn predicate on the \"client_id\" field.\nfunc ClientIDNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldClientID, vs...))\n}\n\n// ClientIDGT applies the GT predicate on the \"client_id\" field.\nfunc ClientIDGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldClientID, v))\n}\n\n// ClientIDGTE applies the GTE predicate on the \"client_id\" field.\nfunc ClientIDGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldClientID, v))\n}\n\n// ClientIDLT applies the LT predicate on the \"client_id\" field.\nfunc ClientIDLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldClientID, v))\n}\n\n// ClientIDLTE applies the LTE predicate on the \"client_id\" field.\nfunc ClientIDLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldClientID, v))\n}\n\n// ClientIDContains applies the Contains predicate on the \"client_id\" field.\nfunc ClientIDContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldClientID, v))\n}\n\n// ClientIDHasPrefix applies the HasPrefix predicate on the \"client_id\" field.\nfunc ClientIDHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldClientID, v))\n}\n\n// ClientIDHasSuffix applies the HasSuffix predicate on the \"client_id\" field.\nfunc ClientIDHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldClientID, v))\n}\n\n// ClientIDEqualFold applies the EqualFold predicate on the \"client_id\" field.\nfunc ClientIDEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldClientID, v))\n}\n\n// ClientIDContainsFold applies the ContainsFold predicate on the \"client_id\" field.\nfunc ClientIDContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldClientID, v))\n}\n\n// ScopesIsNil applies the IsNil predicate on the \"scopes\" field.\nfunc ScopesIsNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIsNull(FieldScopes))\n}\n\n// ScopesNotNil applies the NotNil predicate on the \"scopes\" field.\nfunc ScopesNotNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotNull(FieldScopes))\n}\n\n// ResponseTypesIsNil applies the IsNil predicate on the \"response_types\" field.\nfunc ResponseTypesIsNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIsNull(FieldResponseTypes))\n}\n\n// ResponseTypesNotNil applies the NotNil predicate on the \"response_types\" field.\nfunc ResponseTypesNotNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotNull(FieldResponseTypes))\n}\n\n// RedirectURIEQ applies the EQ predicate on the \"redirect_uri\" field.\nfunc RedirectURIEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldRedirectURI, v))\n}\n\n// RedirectURINEQ applies the NEQ predicate on the \"redirect_uri\" field.\nfunc RedirectURINEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldRedirectURI, v))\n}\n\n// RedirectURIIn applies the In predicate on the \"redirect_uri\" field.\nfunc RedirectURIIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldRedirectURI, vs...))\n}\n\n// RedirectURINotIn applies the NotIn predicate on the \"redirect_uri\" field.\nfunc RedirectURINotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldRedirectURI, vs...))\n}\n\n// RedirectURIGT applies the GT predicate on the \"redirect_uri\" field.\nfunc RedirectURIGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldRedirectURI, v))\n}\n\n// RedirectURIGTE applies the GTE predicate on the \"redirect_uri\" field.\nfunc RedirectURIGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldRedirectURI, v))\n}\n\n// RedirectURILT applies the LT predicate on the \"redirect_uri\" field.\nfunc RedirectURILT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldRedirectURI, v))\n}\n\n// RedirectURILTE applies the LTE predicate on the \"redirect_uri\" field.\nfunc RedirectURILTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldRedirectURI, v))\n}\n\n// RedirectURIContains applies the Contains predicate on the \"redirect_uri\" field.\nfunc RedirectURIContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldRedirectURI, v))\n}\n\n// RedirectURIHasPrefix applies the HasPrefix predicate on the \"redirect_uri\" field.\nfunc RedirectURIHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldRedirectURI, v))\n}\n\n// RedirectURIHasSuffix applies the HasSuffix predicate on the \"redirect_uri\" field.\nfunc RedirectURIHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldRedirectURI, v))\n}\n\n// RedirectURIEqualFold applies the EqualFold predicate on the \"redirect_uri\" field.\nfunc RedirectURIEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldRedirectURI, v))\n}\n\n// RedirectURIContainsFold applies the ContainsFold predicate on the \"redirect_uri\" field.\nfunc RedirectURIContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldRedirectURI, v))\n}\n\n// NonceEQ applies the EQ predicate on the \"nonce\" field.\nfunc NonceEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldNonce, v))\n}\n\n// NonceNEQ applies the NEQ predicate on the \"nonce\" field.\nfunc NonceNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldNonce, v))\n}\n\n// NonceIn applies the In predicate on the \"nonce\" field.\nfunc NonceIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldNonce, vs...))\n}\n\n// NonceNotIn applies the NotIn predicate on the \"nonce\" field.\nfunc NonceNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldNonce, vs...))\n}\n\n// NonceGT applies the GT predicate on the \"nonce\" field.\nfunc NonceGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldNonce, v))\n}\n\n// NonceGTE applies the GTE predicate on the \"nonce\" field.\nfunc NonceGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldNonce, v))\n}\n\n// NonceLT applies the LT predicate on the \"nonce\" field.\nfunc NonceLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldNonce, v))\n}\n\n// NonceLTE applies the LTE predicate on the \"nonce\" field.\nfunc NonceLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldNonce, v))\n}\n\n// NonceContains applies the Contains predicate on the \"nonce\" field.\nfunc NonceContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldNonce, v))\n}\n\n// NonceHasPrefix applies the HasPrefix predicate on the \"nonce\" field.\nfunc NonceHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldNonce, v))\n}\n\n// NonceHasSuffix applies the HasSuffix predicate on the \"nonce\" field.\nfunc NonceHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldNonce, v))\n}\n\n// NonceEqualFold applies the EqualFold predicate on the \"nonce\" field.\nfunc NonceEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldNonce, v))\n}\n\n// NonceContainsFold applies the ContainsFold predicate on the \"nonce\" field.\nfunc NonceContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldNonce, v))\n}\n\n// StateEQ applies the EQ predicate on the \"state\" field.\nfunc StateEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldState, v))\n}\n\n// StateNEQ applies the NEQ predicate on the \"state\" field.\nfunc StateNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldState, v))\n}\n\n// StateIn applies the In predicate on the \"state\" field.\nfunc StateIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldState, vs...))\n}\n\n// StateNotIn applies the NotIn predicate on the \"state\" field.\nfunc StateNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldState, vs...))\n}\n\n// StateGT applies the GT predicate on the \"state\" field.\nfunc StateGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldState, v))\n}\n\n// StateGTE applies the GTE predicate on the \"state\" field.\nfunc StateGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldState, v))\n}\n\n// StateLT applies the LT predicate on the \"state\" field.\nfunc StateLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldState, v))\n}\n\n// StateLTE applies the LTE predicate on the \"state\" field.\nfunc StateLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldState, v))\n}\n\n// StateContains applies the Contains predicate on the \"state\" field.\nfunc StateContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldState, v))\n}\n\n// StateHasPrefix applies the HasPrefix predicate on the \"state\" field.\nfunc StateHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldState, v))\n}\n\n// StateHasSuffix applies the HasSuffix predicate on the \"state\" field.\nfunc StateHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldState, v))\n}\n\n// StateEqualFold applies the EqualFold predicate on the \"state\" field.\nfunc StateEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldState, v))\n}\n\n// StateContainsFold applies the ContainsFold predicate on the \"state\" field.\nfunc StateContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldState, v))\n}\n\n// ForceApprovalPromptEQ applies the EQ predicate on the \"force_approval_prompt\" field.\nfunc ForceApprovalPromptEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldForceApprovalPrompt, v))\n}\n\n// ForceApprovalPromptNEQ applies the NEQ predicate on the \"force_approval_prompt\" field.\nfunc ForceApprovalPromptNEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldForceApprovalPrompt, v))\n}\n\n// LoggedInEQ applies the EQ predicate on the \"logged_in\" field.\nfunc LoggedInEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldLoggedIn, v))\n}\n\n// LoggedInNEQ applies the NEQ predicate on the \"logged_in\" field.\nfunc LoggedInNEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldLoggedIn, v))\n}\n\n// ClaimsUserIDEQ applies the EQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDNEQ applies the NEQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDIn applies the In predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDNotIn applies the NotIn predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDGT applies the GT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDGTE applies the GTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLT applies the LT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLTE applies the LTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContains applies the Contains predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasPrefix applies the HasPrefix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasSuffix applies the HasSuffix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDEqualFold applies the EqualFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContainsFold applies the ContainsFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUsernameEQ applies the EQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameNEQ applies the NEQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameIn applies the In predicate on the \"claims_username\" field.\nfunc ClaimsUsernameIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameNotIn applies the NotIn predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameGT applies the GT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameGTE applies the GTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLT applies the LT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLTE applies the LTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContains applies the Contains predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasPrefix applies the HasPrefix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasSuffix applies the HasSuffix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameEqualFold applies the EqualFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContainsFold applies the ContainsFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsUsername, v))\n}\n\n// ClaimsEmailEQ applies the EQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailNEQ applies the NEQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailIn applies the In predicate on the \"claims_email\" field.\nfunc ClaimsEmailIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailNotIn applies the NotIn predicate on the \"claims_email\" field.\nfunc ClaimsEmailNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailGT applies the GT predicate on the \"claims_email\" field.\nfunc ClaimsEmailGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailGTE applies the GTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLT applies the LT predicate on the \"claims_email\" field.\nfunc ClaimsEmailLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLTE applies the LTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContains applies the Contains predicate on the \"claims_email\" field.\nfunc ClaimsEmailContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasPrefix applies the HasPrefix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasSuffix applies the HasSuffix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailEqualFold applies the EqualFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContainsFold applies the ContainsFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerifiedEQ applies the EQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsEmailVerifiedNEQ applies the NEQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedNEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsGroupsIsNil applies the IsNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsIsNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIsNull(FieldClaimsGroups))\n}\n\n// ClaimsGroupsNotNil applies the NotNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsNotNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotNull(FieldClaimsGroups))\n}\n\n// ClaimsPreferredUsernameEQ applies the EQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameNEQ applies the NEQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameIn applies the In predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameNotIn applies the NotIn predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameGT applies the GT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameGTE applies the GTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLT applies the LT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLTE applies the LTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContains applies the Contains predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldClaimsPreferredUsername, v))\n}\n\n// ConnectorIDEQ applies the EQ predicate on the \"connector_id\" field.\nfunc ConnectorIDEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDNEQ applies the NEQ predicate on the \"connector_id\" field.\nfunc ConnectorIDNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDIn applies the In predicate on the \"connector_id\" field.\nfunc ConnectorIDIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDNotIn applies the NotIn predicate on the \"connector_id\" field.\nfunc ConnectorIDNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDGT applies the GT predicate on the \"connector_id\" field.\nfunc ConnectorIDGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldConnectorID, v))\n}\n\n// ConnectorIDGTE applies the GTE predicate on the \"connector_id\" field.\nfunc ConnectorIDGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldConnectorID, v))\n}\n\n// ConnectorIDLT applies the LT predicate on the \"connector_id\" field.\nfunc ConnectorIDLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldConnectorID, v))\n}\n\n// ConnectorIDLTE applies the LTE predicate on the \"connector_id\" field.\nfunc ConnectorIDLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldConnectorID, v))\n}\n\n// ConnectorIDContains applies the Contains predicate on the \"connector_id\" field.\nfunc ConnectorIDContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldConnectorID, v))\n}\n\n// ConnectorIDHasPrefix applies the HasPrefix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldConnectorID, v))\n}\n\n// ConnectorIDHasSuffix applies the HasSuffix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldConnectorID, v))\n}\n\n// ConnectorIDEqualFold applies the EqualFold predicate on the \"connector_id\" field.\nfunc ConnectorIDEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldConnectorID, v))\n}\n\n// ConnectorIDContainsFold applies the ContainsFold predicate on the \"connector_id\" field.\nfunc ConnectorIDContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldConnectorID, v))\n}\n\n// ConnectorDataEQ applies the EQ predicate on the \"connector_data\" field.\nfunc ConnectorDataEQ(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataNEQ applies the NEQ predicate on the \"connector_data\" field.\nfunc ConnectorDataNEQ(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataIn applies the In predicate on the \"connector_data\" field.\nfunc ConnectorDataIn(vs ...[]byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataNotIn applies the NotIn predicate on the \"connector_data\" field.\nfunc ConnectorDataNotIn(vs ...[]byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataGT applies the GT predicate on the \"connector_data\" field.\nfunc ConnectorDataGT(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldConnectorData, v))\n}\n\n// ConnectorDataGTE applies the GTE predicate on the \"connector_data\" field.\nfunc ConnectorDataGTE(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldConnectorData, v))\n}\n\n// ConnectorDataLT applies the LT predicate on the \"connector_data\" field.\nfunc ConnectorDataLT(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldConnectorData, v))\n}\n\n// ConnectorDataLTE applies the LTE predicate on the \"connector_data\" field.\nfunc ConnectorDataLTE(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldConnectorData, v))\n}\n\n// ConnectorDataIsNil applies the IsNil predicate on the \"connector_data\" field.\nfunc ConnectorDataIsNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIsNull(FieldConnectorData))\n}\n\n// ConnectorDataNotNil applies the NotNil predicate on the \"connector_data\" field.\nfunc ConnectorDataNotNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotNull(FieldConnectorData))\n}\n\n// ExpiryEQ applies the EQ predicate on the \"expiry\" field.\nfunc ExpiryEQ(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldExpiry, v))\n}\n\n// ExpiryNEQ applies the NEQ predicate on the \"expiry\" field.\nfunc ExpiryNEQ(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldExpiry, v))\n}\n\n// ExpiryIn applies the In predicate on the \"expiry\" field.\nfunc ExpiryIn(vs ...time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldExpiry, vs...))\n}\n\n// ExpiryNotIn applies the NotIn predicate on the \"expiry\" field.\nfunc ExpiryNotIn(vs ...time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldExpiry, vs...))\n}\n\n// ExpiryGT applies the GT predicate on the \"expiry\" field.\nfunc ExpiryGT(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldExpiry, v))\n}\n\n// ExpiryGTE applies the GTE predicate on the \"expiry\" field.\nfunc ExpiryGTE(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldExpiry, v))\n}\n\n// ExpiryLT applies the LT predicate on the \"expiry\" field.\nfunc ExpiryLT(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldExpiry, v))\n}\n\n// ExpiryLTE applies the LTE predicate on the \"expiry\" field.\nfunc ExpiryLTE(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldExpiry, v))\n}\n\n// CodeChallengeEQ applies the EQ predicate on the \"code_challenge\" field.\nfunc CodeChallengeEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeNEQ applies the NEQ predicate on the \"code_challenge\" field.\nfunc CodeChallengeNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeIn applies the In predicate on the \"code_challenge\" field.\nfunc CodeChallengeIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldCodeChallenge, vs...))\n}\n\n// CodeChallengeNotIn applies the NotIn predicate on the \"code_challenge\" field.\nfunc CodeChallengeNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldCodeChallenge, vs...))\n}\n\n// CodeChallengeGT applies the GT predicate on the \"code_challenge\" field.\nfunc CodeChallengeGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldCodeChallenge, v))\n}\n\n// CodeChallengeGTE applies the GTE predicate on the \"code_challenge\" field.\nfunc CodeChallengeGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldCodeChallenge, v))\n}\n\n// CodeChallengeLT applies the LT predicate on the \"code_challenge\" field.\nfunc CodeChallengeLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldCodeChallenge, v))\n}\n\n// CodeChallengeLTE applies the LTE predicate on the \"code_challenge\" field.\nfunc CodeChallengeLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldCodeChallenge, v))\n}\n\n// CodeChallengeContains applies the Contains predicate on the \"code_challenge\" field.\nfunc CodeChallengeContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldCodeChallenge, v))\n}\n\n// CodeChallengeHasPrefix applies the HasPrefix predicate on the \"code_challenge\" field.\nfunc CodeChallengeHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldCodeChallenge, v))\n}\n\n// CodeChallengeHasSuffix applies the HasSuffix predicate on the \"code_challenge\" field.\nfunc CodeChallengeHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldCodeChallenge, v))\n}\n\n// CodeChallengeEqualFold applies the EqualFold predicate on the \"code_challenge\" field.\nfunc CodeChallengeEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldCodeChallenge, v))\n}\n\n// CodeChallengeContainsFold applies the ContainsFold predicate on the \"code_challenge\" field.\nfunc CodeChallengeContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldCodeChallenge, v))\n}\n\n// CodeChallengeMethodEQ applies the EQ predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodNEQ applies the NEQ predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodIn applies the In predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldCodeChallengeMethod, vs...))\n}\n\n// CodeChallengeMethodNotIn applies the NotIn predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldCodeChallengeMethod, vs...))\n}\n\n// CodeChallengeMethodGT applies the GT predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodGTE applies the GTE predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodLT applies the LT predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodLTE applies the LTE predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodContains applies the Contains predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodHasPrefix applies the HasPrefix predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodHasSuffix applies the HasSuffix predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodEqualFold applies the EqualFold predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodContainsFold applies the ContainsFold predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldCodeChallengeMethod, v))\n}\n\n// HmacKeyEQ applies the EQ predicate on the \"hmac_key\" field.\nfunc HmacKeyEQ(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldHmacKey, v))\n}\n\n// HmacKeyNEQ applies the NEQ predicate on the \"hmac_key\" field.\nfunc HmacKeyNEQ(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldHmacKey, v))\n}\n\n// HmacKeyIn applies the In predicate on the \"hmac_key\" field.\nfunc HmacKeyIn(vs ...[]byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldHmacKey, vs...))\n}\n\n// HmacKeyNotIn applies the NotIn predicate on the \"hmac_key\" field.\nfunc HmacKeyNotIn(vs ...[]byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldHmacKey, vs...))\n}\n\n// HmacKeyGT applies the GT predicate on the \"hmac_key\" field.\nfunc HmacKeyGT(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldHmacKey, v))\n}\n\n// HmacKeyGTE applies the GTE predicate on the \"hmac_key\" field.\nfunc HmacKeyGTE(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldHmacKey, v))\n}\n\n// HmacKeyLT applies the LT predicate on the \"hmac_key\" field.\nfunc HmacKeyLT(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldHmacKey, v))\n}\n\n// HmacKeyLTE applies the LTE predicate on the \"hmac_key\" field.\nfunc HmacKeyLTE(v []byte) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldHmacKey, v))\n}\n\n// MfaValidatedEQ applies the EQ predicate on the \"mfa_validated\" field.\nfunc MfaValidatedEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldMfaValidated, v))\n}\n\n// MfaValidatedNEQ applies the NEQ predicate on the \"mfa_validated\" field.\nfunc MfaValidatedNEQ(v bool) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldMfaValidated, v))\n}\n\n// PromptEQ applies the EQ predicate on the \"prompt\" field.\nfunc PromptEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldPrompt, v))\n}\n\n// PromptNEQ applies the NEQ predicate on the \"prompt\" field.\nfunc PromptNEQ(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldPrompt, v))\n}\n\n// PromptIn applies the In predicate on the \"prompt\" field.\nfunc PromptIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldPrompt, vs...))\n}\n\n// PromptNotIn applies the NotIn predicate on the \"prompt\" field.\nfunc PromptNotIn(vs ...string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldPrompt, vs...))\n}\n\n// PromptGT applies the GT predicate on the \"prompt\" field.\nfunc PromptGT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldPrompt, v))\n}\n\n// PromptGTE applies the GTE predicate on the \"prompt\" field.\nfunc PromptGTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldPrompt, v))\n}\n\n// PromptLT applies the LT predicate on the \"prompt\" field.\nfunc PromptLT(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldPrompt, v))\n}\n\n// PromptLTE applies the LTE predicate on the \"prompt\" field.\nfunc PromptLTE(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldPrompt, v))\n}\n\n// PromptContains applies the Contains predicate on the \"prompt\" field.\nfunc PromptContains(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContains(FieldPrompt, v))\n}\n\n// PromptHasPrefix applies the HasPrefix predicate on the \"prompt\" field.\nfunc PromptHasPrefix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasPrefix(FieldPrompt, v))\n}\n\n// PromptHasSuffix applies the HasSuffix predicate on the \"prompt\" field.\nfunc PromptHasSuffix(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldHasSuffix(FieldPrompt, v))\n}\n\n// PromptEqualFold applies the EqualFold predicate on the \"prompt\" field.\nfunc PromptEqualFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEqualFold(FieldPrompt, v))\n}\n\n// PromptContainsFold applies the ContainsFold predicate on the \"prompt\" field.\nfunc PromptContainsFold(v string) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldContainsFold(FieldPrompt, v))\n}\n\n// MaxAgeEQ applies the EQ predicate on the \"max_age\" field.\nfunc MaxAgeEQ(v int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldMaxAge, v))\n}\n\n// MaxAgeNEQ applies the NEQ predicate on the \"max_age\" field.\nfunc MaxAgeNEQ(v int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldMaxAge, v))\n}\n\n// MaxAgeIn applies the In predicate on the \"max_age\" field.\nfunc MaxAgeIn(vs ...int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldMaxAge, vs...))\n}\n\n// MaxAgeNotIn applies the NotIn predicate on the \"max_age\" field.\nfunc MaxAgeNotIn(vs ...int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldMaxAge, vs...))\n}\n\n// MaxAgeGT applies the GT predicate on the \"max_age\" field.\nfunc MaxAgeGT(v int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldMaxAge, v))\n}\n\n// MaxAgeGTE applies the GTE predicate on the \"max_age\" field.\nfunc MaxAgeGTE(v int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldMaxAge, v))\n}\n\n// MaxAgeLT applies the LT predicate on the \"max_age\" field.\nfunc MaxAgeLT(v int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldMaxAge, v))\n}\n\n// MaxAgeLTE applies the LTE predicate on the \"max_age\" field.\nfunc MaxAgeLTE(v int) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldMaxAge, v))\n}\n\n// AuthTimeEQ applies the EQ predicate on the \"auth_time\" field.\nfunc AuthTimeEQ(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldEQ(FieldAuthTime, v))\n}\n\n// AuthTimeNEQ applies the NEQ predicate on the \"auth_time\" field.\nfunc AuthTimeNEQ(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNEQ(FieldAuthTime, v))\n}\n\n// AuthTimeIn applies the In predicate on the \"auth_time\" field.\nfunc AuthTimeIn(vs ...time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIn(FieldAuthTime, vs...))\n}\n\n// AuthTimeNotIn applies the NotIn predicate on the \"auth_time\" field.\nfunc AuthTimeNotIn(vs ...time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotIn(FieldAuthTime, vs...))\n}\n\n// AuthTimeGT applies the GT predicate on the \"auth_time\" field.\nfunc AuthTimeGT(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGT(FieldAuthTime, v))\n}\n\n// AuthTimeGTE applies the GTE predicate on the \"auth_time\" field.\nfunc AuthTimeGTE(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldGTE(FieldAuthTime, v))\n}\n\n// AuthTimeLT applies the LT predicate on the \"auth_time\" field.\nfunc AuthTimeLT(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLT(FieldAuthTime, v))\n}\n\n// AuthTimeLTE applies the LTE predicate on the \"auth_time\" field.\nfunc AuthTimeLTE(v time.Time) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldLTE(FieldAuthTime, v))\n}\n\n// AuthTimeIsNil applies the IsNil predicate on the \"auth_time\" field.\nfunc AuthTimeIsNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldIsNull(FieldAuthTime))\n}\n\n// AuthTimeNotNil applies the NotNil predicate on the \"auth_time\" field.\nfunc AuthTimeNotNil() predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.FieldNotNull(FieldAuthTime))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.AuthRequest) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.AuthRequest) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.AuthRequest) predicate.AuthRequest {\n\treturn predicate.AuthRequest(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/authrequest.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n)\n\n// AuthRequest is the model entity for the AuthRequest schema.\ntype AuthRequest struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// ClientID holds the value of the \"client_id\" field.\n\tClientID string `json:\"client_id,omitempty\"`\n\t// Scopes holds the value of the \"scopes\" field.\n\tScopes []string `json:\"scopes,omitempty\"`\n\t// ResponseTypes holds the value of the \"response_types\" field.\n\tResponseTypes []string `json:\"response_types,omitempty\"`\n\t// RedirectURI holds the value of the \"redirect_uri\" field.\n\tRedirectURI string `json:\"redirect_uri,omitempty\"`\n\t// Nonce holds the value of the \"nonce\" field.\n\tNonce string `json:\"nonce,omitempty\"`\n\t// State holds the value of the \"state\" field.\n\tState string `json:\"state,omitempty\"`\n\t// ForceApprovalPrompt holds the value of the \"force_approval_prompt\" field.\n\tForceApprovalPrompt bool `json:\"force_approval_prompt,omitempty\"`\n\t// LoggedIn holds the value of the \"logged_in\" field.\n\tLoggedIn bool `json:\"logged_in,omitempty\"`\n\t// ClaimsUserID holds the value of the \"claims_user_id\" field.\n\tClaimsUserID string `json:\"claims_user_id,omitempty\"`\n\t// ClaimsUsername holds the value of the \"claims_username\" field.\n\tClaimsUsername string `json:\"claims_username,omitempty\"`\n\t// ClaimsEmail holds the value of the \"claims_email\" field.\n\tClaimsEmail string `json:\"claims_email,omitempty\"`\n\t// ClaimsEmailVerified holds the value of the \"claims_email_verified\" field.\n\tClaimsEmailVerified bool `json:\"claims_email_verified,omitempty\"`\n\t// ClaimsGroups holds the value of the \"claims_groups\" field.\n\tClaimsGroups []string `json:\"claims_groups,omitempty\"`\n\t// ClaimsPreferredUsername holds the value of the \"claims_preferred_username\" field.\n\tClaimsPreferredUsername string `json:\"claims_preferred_username,omitempty\"`\n\t// ConnectorID holds the value of the \"connector_id\" field.\n\tConnectorID string `json:\"connector_id,omitempty\"`\n\t// ConnectorData holds the value of the \"connector_data\" field.\n\tConnectorData *[]byte `json:\"connector_data,omitempty\"`\n\t// Expiry holds the value of the \"expiry\" field.\n\tExpiry time.Time `json:\"expiry,omitempty\"`\n\t// CodeChallenge holds the value of the \"code_challenge\" field.\n\tCodeChallenge string `json:\"code_challenge,omitempty\"`\n\t// CodeChallengeMethod holds the value of the \"code_challenge_method\" field.\n\tCodeChallengeMethod string `json:\"code_challenge_method,omitempty\"`\n\t// HmacKey holds the value of the \"hmac_key\" field.\n\tHmacKey []byte `json:\"hmac_key,omitempty\"`\n\t// MfaValidated holds the value of the \"mfa_validated\" field.\n\tMfaValidated bool `json:\"mfa_validated,omitempty\"`\n\t// Prompt holds the value of the \"prompt\" field.\n\tPrompt string `json:\"prompt,omitempty\"`\n\t// MaxAge holds the value of the \"max_age\" field.\n\tMaxAge int `json:\"max_age,omitempty\"`\n\t// AuthTime holds the value of the \"auth_time\" field.\n\tAuthTime     time.Time `json:\"auth_time,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*AuthRequest) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase authrequest.FieldScopes, authrequest.FieldResponseTypes, authrequest.FieldClaimsGroups, authrequest.FieldConnectorData, authrequest.FieldHmacKey:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase authrequest.FieldForceApprovalPrompt, authrequest.FieldLoggedIn, authrequest.FieldClaimsEmailVerified, authrequest.FieldMfaValidated:\n\t\t\tvalues[i] = new(sql.NullBool)\n\t\tcase authrequest.FieldMaxAge:\n\t\t\tvalues[i] = new(sql.NullInt64)\n\t\tcase authrequest.FieldID, authrequest.FieldClientID, authrequest.FieldRedirectURI, authrequest.FieldNonce, authrequest.FieldState, authrequest.FieldClaimsUserID, authrequest.FieldClaimsUsername, authrequest.FieldClaimsEmail, authrequest.FieldClaimsPreferredUsername, authrequest.FieldConnectorID, authrequest.FieldCodeChallenge, authrequest.FieldCodeChallengeMethod, authrequest.FieldPrompt:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase authrequest.FieldExpiry, authrequest.FieldAuthTime:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the AuthRequest fields.\nfunc (_m *AuthRequest) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase authrequest.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase authrequest.FieldClientID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field client_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClientID = value.String\n\t\t\t}\n\t\tcase authrequest.FieldScopes:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field scopes\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.Scopes); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field scopes: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase authrequest.FieldResponseTypes:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field response_types\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.ResponseTypes); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field response_types: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase authrequest.FieldRedirectURI:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field redirect_uri\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.RedirectURI = value.String\n\t\t\t}\n\t\tcase authrequest.FieldNonce:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field nonce\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Nonce = value.String\n\t\t\t}\n\t\tcase authrequest.FieldState:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field state\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.State = value.String\n\t\t\t}\n\t\tcase authrequest.FieldForceApprovalPrompt:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field force_approval_prompt\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ForceApprovalPrompt = value.Bool\n\t\t\t}\n\t\tcase authrequest.FieldLoggedIn:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field logged_in\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.LoggedIn = value.Bool\n\t\t\t}\n\t\tcase authrequest.FieldClaimsUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUserID = value.String\n\t\t\t}\n\t\tcase authrequest.FieldClaimsUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUsername = value.String\n\t\t\t}\n\t\tcase authrequest.FieldClaimsEmail:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmail = value.String\n\t\t\t}\n\t\tcase authrequest.FieldClaimsEmailVerified:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email_verified\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmailVerified = value.Bool\n\t\t\t}\n\t\tcase authrequest.FieldClaimsGroups:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_groups\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field claims_groups: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase authrequest.FieldClaimsPreferredUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_preferred_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsPreferredUsername = value.String\n\t\t\t}\n\t\tcase authrequest.FieldConnectorID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ConnectorID = value.String\n\t\t\t}\n\t\tcase authrequest.FieldConnectorData:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_data\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.ConnectorData = value\n\t\t\t}\n\t\tcase authrequest.FieldExpiry:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field expiry\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Expiry = value.Time\n\t\t\t}\n\t\tcase authrequest.FieldCodeChallenge:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field code_challenge\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CodeChallenge = value.String\n\t\t\t}\n\t\tcase authrequest.FieldCodeChallengeMethod:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field code_challenge_method\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CodeChallengeMethod = value.String\n\t\t\t}\n\t\tcase authrequest.FieldHmacKey:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field hmac_key\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.HmacKey = *value\n\t\t\t}\n\t\tcase authrequest.FieldMfaValidated:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field mfa_validated\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.MfaValidated = value.Bool\n\t\t\t}\n\t\tcase authrequest.FieldPrompt:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field prompt\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Prompt = value.String\n\t\t\t}\n\t\tcase authrequest.FieldMaxAge:\n\t\t\tif value, ok := values[i].(*sql.NullInt64); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field max_age\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.MaxAge = int(value.Int64)\n\t\t\t}\n\t\tcase authrequest.FieldAuthTime:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field auth_time\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.AuthTime = value.Time\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the AuthRequest.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *AuthRequest) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this AuthRequest.\n// Note that you need to call AuthRequest.Unwrap() before calling this method if this AuthRequest\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *AuthRequest) Update() *AuthRequestUpdateOne {\n\treturn NewAuthRequestClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the AuthRequest entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *AuthRequest) Unwrap() *AuthRequest {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: AuthRequest is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *AuthRequest) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"AuthRequest(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"client_id=\")\n\tbuilder.WriteString(_m.ClientID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"scopes=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Scopes))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"response_types=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ResponseTypes))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"redirect_uri=\")\n\tbuilder.WriteString(_m.RedirectURI)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"nonce=\")\n\tbuilder.WriteString(_m.Nonce)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"state=\")\n\tbuilder.WriteString(_m.State)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"force_approval_prompt=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ForceApprovalPrompt))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"logged_in=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.LoggedIn))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_user_id=\")\n\tbuilder.WriteString(_m.ClaimsUserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_username=\")\n\tbuilder.WriteString(_m.ClaimsUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email=\")\n\tbuilder.WriteString(_m.ClaimsEmail)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email_verified=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsEmailVerified))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_groups=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsGroups))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_preferred_username=\")\n\tbuilder.WriteString(_m.ClaimsPreferredUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"connector_id=\")\n\tbuilder.WriteString(_m.ConnectorID)\n\tbuilder.WriteString(\", \")\n\tif v := _m.ConnectorData; v != nil {\n\t\tbuilder.WriteString(\"connector_data=\")\n\t\tbuilder.WriteString(fmt.Sprintf(\"%v\", *v))\n\t}\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"expiry=\")\n\tbuilder.WriteString(_m.Expiry.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"code_challenge=\")\n\tbuilder.WriteString(_m.CodeChallenge)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"code_challenge_method=\")\n\tbuilder.WriteString(_m.CodeChallengeMethod)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"hmac_key=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.HmacKey))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"mfa_validated=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.MfaValidated))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"prompt=\")\n\tbuilder.WriteString(_m.Prompt)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"max_age=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.MaxAge))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"auth_time=\")\n\tbuilder.WriteString(_m.AuthTime.Format(time.ANSIC))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// AuthRequests is a parsable slice of AuthRequest.\ntype AuthRequests []*AuthRequest\n"
  },
  {
    "path": "storage/ent/db/authrequest_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n)\n\n// AuthRequestCreate is the builder for creating a AuthRequest entity.\ntype AuthRequestCreate struct {\n\tconfig\n\tmutation *AuthRequestMutation\n\thooks    []Hook\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_c *AuthRequestCreate) SetClientID(v string) *AuthRequestCreate {\n\t_c.mutation.SetClientID(v)\n\treturn _c\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_c *AuthRequestCreate) SetScopes(v []string) *AuthRequestCreate {\n\t_c.mutation.SetScopes(v)\n\treturn _c\n}\n\n// SetResponseTypes sets the \"response_types\" field.\nfunc (_c *AuthRequestCreate) SetResponseTypes(v []string) *AuthRequestCreate {\n\t_c.mutation.SetResponseTypes(v)\n\treturn _c\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (_c *AuthRequestCreate) SetRedirectURI(v string) *AuthRequestCreate {\n\t_c.mutation.SetRedirectURI(v)\n\treturn _c\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_c *AuthRequestCreate) SetNonce(v string) *AuthRequestCreate {\n\t_c.mutation.SetNonce(v)\n\treturn _c\n}\n\n// SetState sets the \"state\" field.\nfunc (_c *AuthRequestCreate) SetState(v string) *AuthRequestCreate {\n\t_c.mutation.SetState(v)\n\treturn _c\n}\n\n// SetForceApprovalPrompt sets the \"force_approval_prompt\" field.\nfunc (_c *AuthRequestCreate) SetForceApprovalPrompt(v bool) *AuthRequestCreate {\n\t_c.mutation.SetForceApprovalPrompt(v)\n\treturn _c\n}\n\n// SetLoggedIn sets the \"logged_in\" field.\nfunc (_c *AuthRequestCreate) SetLoggedIn(v bool) *AuthRequestCreate {\n\t_c.mutation.SetLoggedIn(v)\n\treturn _c\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_c *AuthRequestCreate) SetClaimsUserID(v string) *AuthRequestCreate {\n\t_c.mutation.SetClaimsUserID(v)\n\treturn _c\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_c *AuthRequestCreate) SetClaimsUsername(v string) *AuthRequestCreate {\n\t_c.mutation.SetClaimsUsername(v)\n\treturn _c\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_c *AuthRequestCreate) SetClaimsEmail(v string) *AuthRequestCreate {\n\t_c.mutation.SetClaimsEmail(v)\n\treturn _c\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_c *AuthRequestCreate) SetClaimsEmailVerified(v bool) *AuthRequestCreate {\n\t_c.mutation.SetClaimsEmailVerified(v)\n\treturn _c\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_c *AuthRequestCreate) SetClaimsGroups(v []string) *AuthRequestCreate {\n\t_c.mutation.SetClaimsGroups(v)\n\treturn _c\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_c *AuthRequestCreate) SetClaimsPreferredUsername(v string) *AuthRequestCreate {\n\t_c.mutation.SetClaimsPreferredUsername(v)\n\treturn _c\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_c *AuthRequestCreate) SetNillableClaimsPreferredUsername(v *string) *AuthRequestCreate {\n\tif v != nil {\n\t\t_c.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _c\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_c *AuthRequestCreate) SetConnectorID(v string) *AuthRequestCreate {\n\t_c.mutation.SetConnectorID(v)\n\treturn _c\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_c *AuthRequestCreate) SetConnectorData(v []byte) *AuthRequestCreate {\n\t_c.mutation.SetConnectorData(v)\n\treturn _c\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_c *AuthRequestCreate) SetExpiry(v time.Time) *AuthRequestCreate {\n\t_c.mutation.SetExpiry(v)\n\treturn _c\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_c *AuthRequestCreate) SetCodeChallenge(v string) *AuthRequestCreate {\n\t_c.mutation.SetCodeChallenge(v)\n\treturn _c\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_c *AuthRequestCreate) SetNillableCodeChallenge(v *string) *AuthRequestCreate {\n\tif v != nil {\n\t\t_c.SetCodeChallenge(*v)\n\t}\n\treturn _c\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_c *AuthRequestCreate) SetCodeChallengeMethod(v string) *AuthRequestCreate {\n\t_c.mutation.SetCodeChallengeMethod(v)\n\treturn _c\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_c *AuthRequestCreate) SetNillableCodeChallengeMethod(v *string) *AuthRequestCreate {\n\tif v != nil {\n\t\t_c.SetCodeChallengeMethod(*v)\n\t}\n\treturn _c\n}\n\n// SetHmacKey sets the \"hmac_key\" field.\nfunc (_c *AuthRequestCreate) SetHmacKey(v []byte) *AuthRequestCreate {\n\t_c.mutation.SetHmacKey(v)\n\treturn _c\n}\n\n// SetMfaValidated sets the \"mfa_validated\" field.\nfunc (_c *AuthRequestCreate) SetMfaValidated(v bool) *AuthRequestCreate {\n\t_c.mutation.SetMfaValidated(v)\n\treturn _c\n}\n\n// SetNillableMfaValidated sets the \"mfa_validated\" field if the given value is not nil.\nfunc (_c *AuthRequestCreate) SetNillableMfaValidated(v *bool) *AuthRequestCreate {\n\tif v != nil {\n\t\t_c.SetMfaValidated(*v)\n\t}\n\treturn _c\n}\n\n// SetPrompt sets the \"prompt\" field.\nfunc (_c *AuthRequestCreate) SetPrompt(v string) *AuthRequestCreate {\n\t_c.mutation.SetPrompt(v)\n\treturn _c\n}\n\n// SetNillablePrompt sets the \"prompt\" field if the given value is not nil.\nfunc (_c *AuthRequestCreate) SetNillablePrompt(v *string) *AuthRequestCreate {\n\tif v != nil {\n\t\t_c.SetPrompt(*v)\n\t}\n\treturn _c\n}\n\n// SetMaxAge sets the \"max_age\" field.\nfunc (_c *AuthRequestCreate) SetMaxAge(v int) *AuthRequestCreate {\n\t_c.mutation.SetMaxAge(v)\n\treturn _c\n}\n\n// SetNillableMaxAge sets the \"max_age\" field if the given value is not nil.\nfunc (_c *AuthRequestCreate) SetNillableMaxAge(v *int) *AuthRequestCreate {\n\tif v != nil {\n\t\t_c.SetMaxAge(*v)\n\t}\n\treturn _c\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (_c *AuthRequestCreate) SetAuthTime(v time.Time) *AuthRequestCreate {\n\t_c.mutation.SetAuthTime(v)\n\treturn _c\n}\n\n// SetNillableAuthTime sets the \"auth_time\" field if the given value is not nil.\nfunc (_c *AuthRequestCreate) SetNillableAuthTime(v *time.Time) *AuthRequestCreate {\n\tif v != nil {\n\t\t_c.SetAuthTime(*v)\n\t}\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *AuthRequestCreate) SetID(v string) *AuthRequestCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the AuthRequestMutation object of the builder.\nfunc (_c *AuthRequestCreate) Mutation() *AuthRequestMutation {\n\treturn _c.mutation\n}\n\n// Save creates the AuthRequest in the database.\nfunc (_c *AuthRequestCreate) Save(ctx context.Context) (*AuthRequest, error) {\n\t_c.defaults()\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *AuthRequestCreate) SaveX(ctx context.Context) *AuthRequest {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *AuthRequestCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *AuthRequestCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// defaults sets the default values of the builder before save.\nfunc (_c *AuthRequestCreate) defaults() {\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\tv := authrequest.DefaultClaimsPreferredUsername\n\t\t_c.mutation.SetClaimsPreferredUsername(v)\n\t}\n\tif _, ok := _c.mutation.CodeChallenge(); !ok {\n\t\tv := authrequest.DefaultCodeChallenge\n\t\t_c.mutation.SetCodeChallenge(v)\n\t}\n\tif _, ok := _c.mutation.CodeChallengeMethod(); !ok {\n\t\tv := authrequest.DefaultCodeChallengeMethod\n\t\t_c.mutation.SetCodeChallengeMethod(v)\n\t}\n\tif _, ok := _c.mutation.MfaValidated(); !ok {\n\t\tv := authrequest.DefaultMfaValidated\n\t\t_c.mutation.SetMfaValidated(v)\n\t}\n\tif _, ok := _c.mutation.Prompt(); !ok {\n\t\tv := authrequest.DefaultPrompt\n\t\t_c.mutation.SetPrompt(v)\n\t}\n\tif _, ok := _c.mutation.MaxAge(); !ok {\n\t\tv := authrequest.DefaultMaxAge\n\t\t_c.mutation.SetMaxAge(v)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *AuthRequestCreate) check() error {\n\tif _, ok := _c.mutation.ClientID(); !ok {\n\t\treturn &ValidationError{Name: \"client_id\", err: errors.New(`db: missing required field \"AuthRequest.client_id\"`)}\n\t}\n\tif _, ok := _c.mutation.RedirectURI(); !ok {\n\t\treturn &ValidationError{Name: \"redirect_uri\", err: errors.New(`db: missing required field \"AuthRequest.redirect_uri\"`)}\n\t}\n\tif _, ok := _c.mutation.Nonce(); !ok {\n\t\treturn &ValidationError{Name: \"nonce\", err: errors.New(`db: missing required field \"AuthRequest.nonce\"`)}\n\t}\n\tif _, ok := _c.mutation.State(); !ok {\n\t\treturn &ValidationError{Name: \"state\", err: errors.New(`db: missing required field \"AuthRequest.state\"`)}\n\t}\n\tif _, ok := _c.mutation.ForceApprovalPrompt(); !ok {\n\t\treturn &ValidationError{Name: \"force_approval_prompt\", err: errors.New(`db: missing required field \"AuthRequest.force_approval_prompt\"`)}\n\t}\n\tif _, ok := _c.mutation.LoggedIn(); !ok {\n\t\treturn &ValidationError{Name: \"logged_in\", err: errors.New(`db: missing required field \"AuthRequest.logged_in\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsUserID(); !ok {\n\t\treturn &ValidationError{Name: \"claims_user_id\", err: errors.New(`db: missing required field \"AuthRequest.claims_user_id\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_username\", err: errors.New(`db: missing required field \"AuthRequest.claims_username\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmail(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email\", err: errors.New(`db: missing required field \"AuthRequest.claims_email\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmailVerified(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email_verified\", err: errors.New(`db: missing required field \"AuthRequest.claims_email_verified\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_preferred_username\", err: errors.New(`db: missing required field \"AuthRequest.claims_preferred_username\"`)}\n\t}\n\tif _, ok := _c.mutation.ConnectorID(); !ok {\n\t\treturn &ValidationError{Name: \"connector_id\", err: errors.New(`db: missing required field \"AuthRequest.connector_id\"`)}\n\t}\n\tif _, ok := _c.mutation.Expiry(); !ok {\n\t\treturn &ValidationError{Name: \"expiry\", err: errors.New(`db: missing required field \"AuthRequest.expiry\"`)}\n\t}\n\tif _, ok := _c.mutation.CodeChallenge(); !ok {\n\t\treturn &ValidationError{Name: \"code_challenge\", err: errors.New(`db: missing required field \"AuthRequest.code_challenge\"`)}\n\t}\n\tif _, ok := _c.mutation.CodeChallengeMethod(); !ok {\n\t\treturn &ValidationError{Name: \"code_challenge_method\", err: errors.New(`db: missing required field \"AuthRequest.code_challenge_method\"`)}\n\t}\n\tif _, ok := _c.mutation.HmacKey(); !ok {\n\t\treturn &ValidationError{Name: \"hmac_key\", err: errors.New(`db: missing required field \"AuthRequest.hmac_key\"`)}\n\t}\n\tif _, ok := _c.mutation.MfaValidated(); !ok {\n\t\treturn &ValidationError{Name: \"mfa_validated\", err: errors.New(`db: missing required field \"AuthRequest.mfa_validated\"`)}\n\t}\n\tif _, ok := _c.mutation.Prompt(); !ok {\n\t\treturn &ValidationError{Name: \"prompt\", err: errors.New(`db: missing required field \"AuthRequest.prompt\"`)}\n\t}\n\tif _, ok := _c.mutation.MaxAge(); !ok {\n\t\treturn &ValidationError{Name: \"max_age\", err: errors.New(`db: missing required field \"AuthRequest.max_age\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := authrequest.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"AuthRequest.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *AuthRequestCreate) sqlSave(ctx context.Context) (*AuthRequest, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected AuthRequest.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *AuthRequestCreate) createSpec() (*AuthRequest, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &AuthRequest{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(authrequest.Table, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.ClientID(); ok {\n\t\t_spec.SetField(authrequest.FieldClientID, field.TypeString, value)\n\t\t_node.ClientID = value\n\t}\n\tif value, ok := _c.mutation.Scopes(); ok {\n\t\t_spec.SetField(authrequest.FieldScopes, field.TypeJSON, value)\n\t\t_node.Scopes = value\n\t}\n\tif value, ok := _c.mutation.ResponseTypes(); ok {\n\t\t_spec.SetField(authrequest.FieldResponseTypes, field.TypeJSON, value)\n\t\t_node.ResponseTypes = value\n\t}\n\tif value, ok := _c.mutation.RedirectURI(); ok {\n\t\t_spec.SetField(authrequest.FieldRedirectURI, field.TypeString, value)\n\t\t_node.RedirectURI = value\n\t}\n\tif value, ok := _c.mutation.Nonce(); ok {\n\t\t_spec.SetField(authrequest.FieldNonce, field.TypeString, value)\n\t\t_node.Nonce = value\n\t}\n\tif value, ok := _c.mutation.State(); ok {\n\t\t_spec.SetField(authrequest.FieldState, field.TypeString, value)\n\t\t_node.State = value\n\t}\n\tif value, ok := _c.mutation.ForceApprovalPrompt(); ok {\n\t\t_spec.SetField(authrequest.FieldForceApprovalPrompt, field.TypeBool, value)\n\t\t_node.ForceApprovalPrompt = value\n\t}\n\tif value, ok := _c.mutation.LoggedIn(); ok {\n\t\t_spec.SetField(authrequest.FieldLoggedIn, field.TypeBool, value)\n\t\t_node.LoggedIn = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsUserID, field.TypeString, value)\n\t\t_node.ClaimsUserID = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsUsername, field.TypeString, value)\n\t\t_node.ClaimsUsername = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsEmail, field.TypeString, value)\n\t\t_node.ClaimsEmail = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsEmailVerified, field.TypeBool, value)\n\t\t_node.ClaimsEmailVerified = value\n\t}\n\tif value, ok := _c.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsGroups, field.TypeJSON, value)\n\t\t_node.ClaimsGroups = value\n\t}\n\tif value, ok := _c.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsPreferredUsername, field.TypeString, value)\n\t\t_node.ClaimsPreferredUsername = value\n\t}\n\tif value, ok := _c.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authrequest.FieldConnectorID, field.TypeString, value)\n\t\t_node.ConnectorID = value\n\t}\n\tif value, ok := _c.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(authrequest.FieldConnectorData, field.TypeBytes, value)\n\t\t_node.ConnectorData = &value\n\t}\n\tif value, ok := _c.mutation.Expiry(); ok {\n\t\t_spec.SetField(authrequest.FieldExpiry, field.TypeTime, value)\n\t\t_node.Expiry = value\n\t}\n\tif value, ok := _c.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(authrequest.FieldCodeChallenge, field.TypeString, value)\n\t\t_node.CodeChallenge = value\n\t}\n\tif value, ok := _c.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(authrequest.FieldCodeChallengeMethod, field.TypeString, value)\n\t\t_node.CodeChallengeMethod = value\n\t}\n\tif value, ok := _c.mutation.HmacKey(); ok {\n\t\t_spec.SetField(authrequest.FieldHmacKey, field.TypeBytes, value)\n\t\t_node.HmacKey = value\n\t}\n\tif value, ok := _c.mutation.MfaValidated(); ok {\n\t\t_spec.SetField(authrequest.FieldMfaValidated, field.TypeBool, value)\n\t\t_node.MfaValidated = value\n\t}\n\tif value, ok := _c.mutation.Prompt(); ok {\n\t\t_spec.SetField(authrequest.FieldPrompt, field.TypeString, value)\n\t\t_node.Prompt = value\n\t}\n\tif value, ok := _c.mutation.MaxAge(); ok {\n\t\t_spec.SetField(authrequest.FieldMaxAge, field.TypeInt, value)\n\t\t_node.MaxAge = value\n\t}\n\tif value, ok := _c.mutation.AuthTime(); ok {\n\t\t_spec.SetField(authrequest.FieldAuthTime, field.TypeTime, value)\n\t\t_node.AuthTime = value\n\t}\n\treturn _node, _spec\n}\n\n// AuthRequestCreateBulk is the builder for creating many AuthRequest entities in bulk.\ntype AuthRequestCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*AuthRequestCreate\n}\n\n// Save creates the AuthRequest entities in the database.\nfunc (_c *AuthRequestCreateBulk) Save(ctx context.Context) ([]*AuthRequest, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*AuthRequest, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tbuilder.defaults()\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*AuthRequestMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *AuthRequestCreateBulk) SaveX(ctx context.Context) []*AuthRequest {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *AuthRequestCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *AuthRequestCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/authrequest_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthRequestDelete is the builder for deleting a AuthRequest entity.\ntype AuthRequestDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *AuthRequestMutation\n}\n\n// Where appends a list predicates to the AuthRequestDelete builder.\nfunc (_d *AuthRequestDelete) Where(ps ...predicate.AuthRequest) *AuthRequestDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *AuthRequestDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *AuthRequestDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *AuthRequestDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(authrequest.Table, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// AuthRequestDeleteOne is the builder for deleting a single AuthRequest entity.\ntype AuthRequestDeleteOne struct {\n\t_d *AuthRequestDelete\n}\n\n// Where appends a list predicates to the AuthRequestDelete builder.\nfunc (_d *AuthRequestDeleteOne) Where(ps ...predicate.AuthRequest) *AuthRequestDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *AuthRequestDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{authrequest.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *AuthRequestDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/authrequest_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthRequestQuery is the builder for querying AuthRequest entities.\ntype AuthRequestQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []authrequest.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.AuthRequest\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the AuthRequestQuery builder.\nfunc (_q *AuthRequestQuery) Where(ps ...predicate.AuthRequest) *AuthRequestQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *AuthRequestQuery) Limit(limit int) *AuthRequestQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *AuthRequestQuery) Offset(offset int) *AuthRequestQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *AuthRequestQuery) Unique(unique bool) *AuthRequestQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *AuthRequestQuery) Order(o ...authrequest.OrderOption) *AuthRequestQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first AuthRequest entity from the query.\n// Returns a *NotFoundError when no AuthRequest was found.\nfunc (_q *AuthRequestQuery) First(ctx context.Context) (*AuthRequest, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{authrequest.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) FirstX(ctx context.Context) *AuthRequest {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first AuthRequest ID from the query.\n// Returns a *NotFoundError when no AuthRequest ID was found.\nfunc (_q *AuthRequestQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{authrequest.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single AuthRequest entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one AuthRequest entity is found.\n// Returns a *NotFoundError when no AuthRequest entities are found.\nfunc (_q *AuthRequestQuery) Only(ctx context.Context) (*AuthRequest, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{authrequest.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{authrequest.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) OnlyX(ctx context.Context) *AuthRequest {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only AuthRequest ID in the query.\n// Returns a *NotSingularError when more than one AuthRequest ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *AuthRequestQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{authrequest.Label}\n\tdefault:\n\t\terr = &NotSingularError{authrequest.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of AuthRequests.\nfunc (_q *AuthRequestQuery) All(ctx context.Context) ([]*AuthRequest, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*AuthRequest, *AuthRequestQuery]()\n\treturn withInterceptors[[]*AuthRequest](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) AllX(ctx context.Context) []*AuthRequest {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of AuthRequest IDs.\nfunc (_q *AuthRequestQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(authrequest.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *AuthRequestQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*AuthRequestQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *AuthRequestQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *AuthRequestQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the AuthRequestQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *AuthRequestQuery) Clone() *AuthRequestQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &AuthRequestQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]authrequest.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.AuthRequest{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tClientID string `json:\"client_id,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.AuthRequest.Query().\n//\t\tGroupBy(authrequest.FieldClientID).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *AuthRequestQuery) GroupBy(field string, fields ...string) *AuthRequestGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &AuthRequestGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = authrequest.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tClientID string `json:\"client_id,omitempty\"`\n//\t}\n//\n//\tclient.AuthRequest.Query().\n//\t\tSelect(authrequest.FieldClientID).\n//\t\tScan(ctx, &v)\nfunc (_q *AuthRequestQuery) Select(fields ...string) *AuthRequestSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &AuthRequestSelect{AuthRequestQuery: _q}\n\tsbuild.label = authrequest.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a AuthRequestSelect configured with the given aggregations.\nfunc (_q *AuthRequestQuery) Aggregate(fns ...AggregateFunc) *AuthRequestSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *AuthRequestQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !authrequest.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *AuthRequestQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthRequest, error) {\n\tvar (\n\t\tnodes = []*AuthRequest{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*AuthRequest).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &AuthRequest{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *AuthRequestQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *AuthRequestQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(authrequest.Table, authrequest.Columns, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, authrequest.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != authrequest.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *AuthRequestQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(authrequest.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = authrequest.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// AuthRequestGroupBy is the group-by builder for AuthRequest entities.\ntype AuthRequestGroupBy struct {\n\tselector\n\tbuild *AuthRequestQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *AuthRequestGroupBy) Aggregate(fns ...AggregateFunc) *AuthRequestGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *AuthRequestGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*AuthRequestQuery, *AuthRequestGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *AuthRequestGroupBy) sqlScan(ctx context.Context, root *AuthRequestQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// AuthRequestSelect is the builder for selecting fields of AuthRequest entities.\ntype AuthRequestSelect struct {\n\t*AuthRequestQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *AuthRequestSelect) Aggregate(fns ...AggregateFunc) *AuthRequestSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *AuthRequestSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*AuthRequestQuery, *AuthRequestSelect](ctx, _s.AuthRequestQuery, _s, _s.inters, v)\n}\n\nfunc (_s *AuthRequestSelect) sqlScan(ctx context.Context, root *AuthRequestQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/authrequest_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthRequestUpdate is the builder for updating AuthRequest entities.\ntype AuthRequestUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *AuthRequestMutation\n}\n\n// Where appends a list predicates to the AuthRequestUpdate builder.\nfunc (_u *AuthRequestUpdate) Where(ps ...predicate.AuthRequest) *AuthRequestUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *AuthRequestUpdate) SetClientID(v string) *AuthRequestUpdate {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableClientID(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *AuthRequestUpdate) SetScopes(v []string) *AuthRequestUpdate {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *AuthRequestUpdate) AppendScopes(v []string) *AuthRequestUpdate {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *AuthRequestUpdate) ClearScopes() *AuthRequestUpdate {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetResponseTypes sets the \"response_types\" field.\nfunc (_u *AuthRequestUpdate) SetResponseTypes(v []string) *AuthRequestUpdate {\n\t_u.mutation.SetResponseTypes(v)\n\treturn _u\n}\n\n// AppendResponseTypes appends value to the \"response_types\" field.\nfunc (_u *AuthRequestUpdate) AppendResponseTypes(v []string) *AuthRequestUpdate {\n\t_u.mutation.AppendResponseTypes(v)\n\treturn _u\n}\n\n// ClearResponseTypes clears the value of the \"response_types\" field.\nfunc (_u *AuthRequestUpdate) ClearResponseTypes() *AuthRequestUpdate {\n\t_u.mutation.ClearResponseTypes()\n\treturn _u\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (_u *AuthRequestUpdate) SetRedirectURI(v string) *AuthRequestUpdate {\n\t_u.mutation.SetRedirectURI(v)\n\treturn _u\n}\n\n// SetNillableRedirectURI sets the \"redirect_uri\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableRedirectURI(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetRedirectURI(*v)\n\t}\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *AuthRequestUpdate) SetNonce(v string) *AuthRequestUpdate {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableNonce(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetState sets the \"state\" field.\nfunc (_u *AuthRequestUpdate) SetState(v string) *AuthRequestUpdate {\n\t_u.mutation.SetState(v)\n\treturn _u\n}\n\n// SetNillableState sets the \"state\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableState(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetState(*v)\n\t}\n\treturn _u\n}\n\n// SetForceApprovalPrompt sets the \"force_approval_prompt\" field.\nfunc (_u *AuthRequestUpdate) SetForceApprovalPrompt(v bool) *AuthRequestUpdate {\n\t_u.mutation.SetForceApprovalPrompt(v)\n\treturn _u\n}\n\n// SetNillableForceApprovalPrompt sets the \"force_approval_prompt\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableForceApprovalPrompt(v *bool) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetForceApprovalPrompt(*v)\n\t}\n\treturn _u\n}\n\n// SetLoggedIn sets the \"logged_in\" field.\nfunc (_u *AuthRequestUpdate) SetLoggedIn(v bool) *AuthRequestUpdate {\n\t_u.mutation.SetLoggedIn(v)\n\treturn _u\n}\n\n// SetNillableLoggedIn sets the \"logged_in\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableLoggedIn(v *bool) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetLoggedIn(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *AuthRequestUpdate) SetClaimsUserID(v string) *AuthRequestUpdate {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableClaimsUserID(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *AuthRequestUpdate) SetClaimsUsername(v string) *AuthRequestUpdate {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableClaimsUsername(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *AuthRequestUpdate) SetClaimsEmail(v string) *AuthRequestUpdate {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableClaimsEmail(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *AuthRequestUpdate) SetClaimsEmailVerified(v bool) *AuthRequestUpdate {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableClaimsEmailVerified(v *bool) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *AuthRequestUpdate) SetClaimsGroups(v []string) *AuthRequestUpdate {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *AuthRequestUpdate) AppendClaimsGroups(v []string) *AuthRequestUpdate {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *AuthRequestUpdate) ClearClaimsGroups() *AuthRequestUpdate {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *AuthRequestUpdate) SetClaimsPreferredUsername(v string) *AuthRequestUpdate {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableClaimsPreferredUsername(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *AuthRequestUpdate) SetConnectorID(v string) *AuthRequestUpdate {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableConnectorID(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *AuthRequestUpdate) SetConnectorData(v []byte) *AuthRequestUpdate {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *AuthRequestUpdate) ClearConnectorData() *AuthRequestUpdate {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *AuthRequestUpdate) SetExpiry(v time.Time) *AuthRequestUpdate {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableExpiry(v *time.Time) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_u *AuthRequestUpdate) SetCodeChallenge(v string) *AuthRequestUpdate {\n\t_u.mutation.SetCodeChallenge(v)\n\treturn _u\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableCodeChallenge(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetCodeChallenge(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_u *AuthRequestUpdate) SetCodeChallengeMethod(v string) *AuthRequestUpdate {\n\t_u.mutation.SetCodeChallengeMethod(v)\n\treturn _u\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableCodeChallengeMethod(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetCodeChallengeMethod(*v)\n\t}\n\treturn _u\n}\n\n// SetHmacKey sets the \"hmac_key\" field.\nfunc (_u *AuthRequestUpdate) SetHmacKey(v []byte) *AuthRequestUpdate {\n\t_u.mutation.SetHmacKey(v)\n\treturn _u\n}\n\n// SetMfaValidated sets the \"mfa_validated\" field.\nfunc (_u *AuthRequestUpdate) SetMfaValidated(v bool) *AuthRequestUpdate {\n\t_u.mutation.SetMfaValidated(v)\n\treturn _u\n}\n\n// SetNillableMfaValidated sets the \"mfa_validated\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableMfaValidated(v *bool) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetMfaValidated(*v)\n\t}\n\treturn _u\n}\n\n// SetPrompt sets the \"prompt\" field.\nfunc (_u *AuthRequestUpdate) SetPrompt(v string) *AuthRequestUpdate {\n\t_u.mutation.SetPrompt(v)\n\treturn _u\n}\n\n// SetNillablePrompt sets the \"prompt\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillablePrompt(v *string) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetPrompt(*v)\n\t}\n\treturn _u\n}\n\n// SetMaxAge sets the \"max_age\" field.\nfunc (_u *AuthRequestUpdate) SetMaxAge(v int) *AuthRequestUpdate {\n\t_u.mutation.ResetMaxAge()\n\t_u.mutation.SetMaxAge(v)\n\treturn _u\n}\n\n// SetNillableMaxAge sets the \"max_age\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableMaxAge(v *int) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetMaxAge(*v)\n\t}\n\treturn _u\n}\n\n// AddMaxAge adds value to the \"max_age\" field.\nfunc (_u *AuthRequestUpdate) AddMaxAge(v int) *AuthRequestUpdate {\n\t_u.mutation.AddMaxAge(v)\n\treturn _u\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (_u *AuthRequestUpdate) SetAuthTime(v time.Time) *AuthRequestUpdate {\n\t_u.mutation.SetAuthTime(v)\n\treturn _u\n}\n\n// SetNillableAuthTime sets the \"auth_time\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdate) SetNillableAuthTime(v *time.Time) *AuthRequestUpdate {\n\tif v != nil {\n\t\t_u.SetAuthTime(*v)\n\t}\n\treturn _u\n}\n\n// ClearAuthTime clears the value of the \"auth_time\" field.\nfunc (_u *AuthRequestUpdate) ClearAuthTime() *AuthRequestUpdate {\n\t_u.mutation.ClearAuthTime()\n\treturn _u\n}\n\n// Mutation returns the AuthRequestMutation object of the builder.\nfunc (_u *AuthRequestUpdate) Mutation() *AuthRequestMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *AuthRequestUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *AuthRequestUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *AuthRequestUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *AuthRequestUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (_u *AuthRequestUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\t_spec := sqlgraph.NewUpdateSpec(authrequest.Table, authrequest.Columns, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(authrequest.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(authrequest.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authrequest.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(authrequest.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ResponseTypes(); ok {\n\t\t_spec.SetField(authrequest.FieldResponseTypes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedResponseTypes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authrequest.FieldResponseTypes, value)\n\t\t})\n\t}\n\tif _u.mutation.ResponseTypesCleared() {\n\t\t_spec.ClearField(authrequest.FieldResponseTypes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.RedirectURI(); ok {\n\t\t_spec.SetField(authrequest.FieldRedirectURI, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(authrequest.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.State(); ok {\n\t\t_spec.SetField(authrequest.FieldState, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ForceApprovalPrompt(); ok {\n\t\t_spec.SetField(authrequest.FieldForceApprovalPrompt, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.LoggedIn(); ok {\n\t\t_spec.SetField(authrequest.FieldLoggedIn, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authrequest.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(authrequest.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authrequest.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(authrequest.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(authrequest.FieldConnectorData, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(authrequest.FieldExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(authrequest.FieldCodeChallenge, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(authrequest.FieldCodeChallengeMethod, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.HmacKey(); ok {\n\t\t_spec.SetField(authrequest.FieldHmacKey, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.MfaValidated(); ok {\n\t\t_spec.SetField(authrequest.FieldMfaValidated, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.Prompt(); ok {\n\t\t_spec.SetField(authrequest.FieldPrompt, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.MaxAge(); ok {\n\t\t_spec.SetField(authrequest.FieldMaxAge, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.AddedMaxAge(); ok {\n\t\t_spec.AddField(authrequest.FieldMaxAge, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.AuthTime(); ok {\n\t\t_spec.SetField(authrequest.FieldAuthTime, field.TypeTime, value)\n\t}\n\tif _u.mutation.AuthTimeCleared() {\n\t\t_spec.ClearField(authrequest.FieldAuthTime, field.TypeTime)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{authrequest.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// AuthRequestUpdateOne is the builder for updating a single AuthRequest entity.\ntype AuthRequestUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *AuthRequestMutation\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *AuthRequestUpdateOne) SetClientID(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableClientID(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *AuthRequestUpdateOne) SetScopes(v []string) *AuthRequestUpdateOne {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *AuthRequestUpdateOne) AppendScopes(v []string) *AuthRequestUpdateOne {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *AuthRequestUpdateOne) ClearScopes() *AuthRequestUpdateOne {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetResponseTypes sets the \"response_types\" field.\nfunc (_u *AuthRequestUpdateOne) SetResponseTypes(v []string) *AuthRequestUpdateOne {\n\t_u.mutation.SetResponseTypes(v)\n\treturn _u\n}\n\n// AppendResponseTypes appends value to the \"response_types\" field.\nfunc (_u *AuthRequestUpdateOne) AppendResponseTypes(v []string) *AuthRequestUpdateOne {\n\t_u.mutation.AppendResponseTypes(v)\n\treturn _u\n}\n\n// ClearResponseTypes clears the value of the \"response_types\" field.\nfunc (_u *AuthRequestUpdateOne) ClearResponseTypes() *AuthRequestUpdateOne {\n\t_u.mutation.ClearResponseTypes()\n\treturn _u\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (_u *AuthRequestUpdateOne) SetRedirectURI(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetRedirectURI(v)\n\treturn _u\n}\n\n// SetNillableRedirectURI sets the \"redirect_uri\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableRedirectURI(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetRedirectURI(*v)\n\t}\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *AuthRequestUpdateOne) SetNonce(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableNonce(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetState sets the \"state\" field.\nfunc (_u *AuthRequestUpdateOne) SetState(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetState(v)\n\treturn _u\n}\n\n// SetNillableState sets the \"state\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableState(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetState(*v)\n\t}\n\treturn _u\n}\n\n// SetForceApprovalPrompt sets the \"force_approval_prompt\" field.\nfunc (_u *AuthRequestUpdateOne) SetForceApprovalPrompt(v bool) *AuthRequestUpdateOne {\n\t_u.mutation.SetForceApprovalPrompt(v)\n\treturn _u\n}\n\n// SetNillableForceApprovalPrompt sets the \"force_approval_prompt\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableForceApprovalPrompt(v *bool) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetForceApprovalPrompt(*v)\n\t}\n\treturn _u\n}\n\n// SetLoggedIn sets the \"logged_in\" field.\nfunc (_u *AuthRequestUpdateOne) SetLoggedIn(v bool) *AuthRequestUpdateOne {\n\t_u.mutation.SetLoggedIn(v)\n\treturn _u\n}\n\n// SetNillableLoggedIn sets the \"logged_in\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableLoggedIn(v *bool) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetLoggedIn(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *AuthRequestUpdateOne) SetClaimsUserID(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableClaimsUserID(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *AuthRequestUpdateOne) SetClaimsUsername(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableClaimsUsername(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *AuthRequestUpdateOne) SetClaimsEmail(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableClaimsEmail(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *AuthRequestUpdateOne) SetClaimsEmailVerified(v bool) *AuthRequestUpdateOne {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableClaimsEmailVerified(v *bool) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *AuthRequestUpdateOne) SetClaimsGroups(v []string) *AuthRequestUpdateOne {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *AuthRequestUpdateOne) AppendClaimsGroups(v []string) *AuthRequestUpdateOne {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *AuthRequestUpdateOne) ClearClaimsGroups() *AuthRequestUpdateOne {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *AuthRequestUpdateOne) SetClaimsPreferredUsername(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableClaimsPreferredUsername(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *AuthRequestUpdateOne) SetConnectorID(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableConnectorID(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *AuthRequestUpdateOne) SetConnectorData(v []byte) *AuthRequestUpdateOne {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *AuthRequestUpdateOne) ClearConnectorData() *AuthRequestUpdateOne {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *AuthRequestUpdateOne) SetExpiry(v time.Time) *AuthRequestUpdateOne {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableExpiry(v *time.Time) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_u *AuthRequestUpdateOne) SetCodeChallenge(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetCodeChallenge(v)\n\treturn _u\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableCodeChallenge(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetCodeChallenge(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_u *AuthRequestUpdateOne) SetCodeChallengeMethod(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetCodeChallengeMethod(v)\n\treturn _u\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableCodeChallengeMethod(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetCodeChallengeMethod(*v)\n\t}\n\treturn _u\n}\n\n// SetHmacKey sets the \"hmac_key\" field.\nfunc (_u *AuthRequestUpdateOne) SetHmacKey(v []byte) *AuthRequestUpdateOne {\n\t_u.mutation.SetHmacKey(v)\n\treturn _u\n}\n\n// SetMfaValidated sets the \"mfa_validated\" field.\nfunc (_u *AuthRequestUpdateOne) SetMfaValidated(v bool) *AuthRequestUpdateOne {\n\t_u.mutation.SetMfaValidated(v)\n\treturn _u\n}\n\n// SetNillableMfaValidated sets the \"mfa_validated\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableMfaValidated(v *bool) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetMfaValidated(*v)\n\t}\n\treturn _u\n}\n\n// SetPrompt sets the \"prompt\" field.\nfunc (_u *AuthRequestUpdateOne) SetPrompt(v string) *AuthRequestUpdateOne {\n\t_u.mutation.SetPrompt(v)\n\treturn _u\n}\n\n// SetNillablePrompt sets the \"prompt\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillablePrompt(v *string) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetPrompt(*v)\n\t}\n\treturn _u\n}\n\n// SetMaxAge sets the \"max_age\" field.\nfunc (_u *AuthRequestUpdateOne) SetMaxAge(v int) *AuthRequestUpdateOne {\n\t_u.mutation.ResetMaxAge()\n\t_u.mutation.SetMaxAge(v)\n\treturn _u\n}\n\n// SetNillableMaxAge sets the \"max_age\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableMaxAge(v *int) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetMaxAge(*v)\n\t}\n\treturn _u\n}\n\n// AddMaxAge adds value to the \"max_age\" field.\nfunc (_u *AuthRequestUpdateOne) AddMaxAge(v int) *AuthRequestUpdateOne {\n\t_u.mutation.AddMaxAge(v)\n\treturn _u\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (_u *AuthRequestUpdateOne) SetAuthTime(v time.Time) *AuthRequestUpdateOne {\n\t_u.mutation.SetAuthTime(v)\n\treturn _u\n}\n\n// SetNillableAuthTime sets the \"auth_time\" field if the given value is not nil.\nfunc (_u *AuthRequestUpdateOne) SetNillableAuthTime(v *time.Time) *AuthRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetAuthTime(*v)\n\t}\n\treturn _u\n}\n\n// ClearAuthTime clears the value of the \"auth_time\" field.\nfunc (_u *AuthRequestUpdateOne) ClearAuthTime() *AuthRequestUpdateOne {\n\t_u.mutation.ClearAuthTime()\n\treturn _u\n}\n\n// Mutation returns the AuthRequestMutation object of the builder.\nfunc (_u *AuthRequestUpdateOne) Mutation() *AuthRequestMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the AuthRequestUpdate builder.\nfunc (_u *AuthRequestUpdateOne) Where(ps ...predicate.AuthRequest) *AuthRequestUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *AuthRequestUpdateOne) Select(field string, fields ...string) *AuthRequestUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated AuthRequest entity.\nfunc (_u *AuthRequestUpdateOne) Save(ctx context.Context) (*AuthRequest, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *AuthRequestUpdateOne) SaveX(ctx context.Context) *AuthRequest {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *AuthRequestUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *AuthRequestUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (_u *AuthRequestUpdateOne) sqlSave(ctx context.Context) (_node *AuthRequest, err error) {\n\t_spec := sqlgraph.NewUpdateSpec(authrequest.Table, authrequest.Columns, sqlgraph.NewFieldSpec(authrequest.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"AuthRequest.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, authrequest.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !authrequest.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != authrequest.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(authrequest.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(authrequest.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authrequest.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(authrequest.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ResponseTypes(); ok {\n\t\t_spec.SetField(authrequest.FieldResponseTypes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedResponseTypes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authrequest.FieldResponseTypes, value)\n\t\t})\n\t}\n\tif _u.mutation.ResponseTypesCleared() {\n\t\t_spec.ClearField(authrequest.FieldResponseTypes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.RedirectURI(); ok {\n\t\t_spec.SetField(authrequest.FieldRedirectURI, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(authrequest.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.State(); ok {\n\t\t_spec.SetField(authrequest.FieldState, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ForceApprovalPrompt(); ok {\n\t\t_spec.SetField(authrequest.FieldForceApprovalPrompt, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.LoggedIn(); ok {\n\t\t_spec.SetField(authrequest.FieldLoggedIn, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, authrequest.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(authrequest.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(authrequest.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authrequest.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(authrequest.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(authrequest.FieldConnectorData, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(authrequest.FieldExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(authrequest.FieldCodeChallenge, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(authrequest.FieldCodeChallengeMethod, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.HmacKey(); ok {\n\t\t_spec.SetField(authrequest.FieldHmacKey, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.MfaValidated(); ok {\n\t\t_spec.SetField(authrequest.FieldMfaValidated, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.Prompt(); ok {\n\t\t_spec.SetField(authrequest.FieldPrompt, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.MaxAge(); ok {\n\t\t_spec.SetField(authrequest.FieldMaxAge, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.AddedMaxAge(); ok {\n\t\t_spec.AddField(authrequest.FieldMaxAge, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.AuthTime(); ok {\n\t\t_spec.SetField(authrequest.FieldAuthTime, field.TypeTime, value)\n\t}\n\tif _u.mutation.AuthTimeCleared() {\n\t\t_spec.ClearField(authrequest.FieldAuthTime, field.TypeTime)\n\t}\n\t_node = &AuthRequest{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{authrequest.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/authsession/authsession.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage authsession\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the authsession type in the database.\n\tLabel = \"auth_session\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldUserID holds the string denoting the user_id field in the database.\n\tFieldUserID = \"user_id\"\n\t// FieldConnectorID holds the string denoting the connector_id field in the database.\n\tFieldConnectorID = \"connector_id\"\n\t// FieldNonce holds the string denoting the nonce field in the database.\n\tFieldNonce = \"nonce\"\n\t// FieldClientStates holds the string denoting the client_states field in the database.\n\tFieldClientStates = \"client_states\"\n\t// FieldCreatedAt holds the string denoting the created_at field in the database.\n\tFieldCreatedAt = \"created_at\"\n\t// FieldLastActivity holds the string denoting the last_activity field in the database.\n\tFieldLastActivity = \"last_activity\"\n\t// FieldIPAddress holds the string denoting the ip_address field in the database.\n\tFieldIPAddress = \"ip_address\"\n\t// FieldUserAgent holds the string denoting the user_agent field in the database.\n\tFieldUserAgent = \"user_agent\"\n\t// FieldAbsoluteExpiry holds the string denoting the absolute_expiry field in the database.\n\tFieldAbsoluteExpiry = \"absolute_expiry\"\n\t// FieldIdleExpiry holds the string denoting the idle_expiry field in the database.\n\tFieldIdleExpiry = \"idle_expiry\"\n\t// Table holds the table name of the authsession in the database.\n\tTable = \"auth_sessions\"\n)\n\n// Columns holds all SQL columns for authsession fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldUserID,\n\tFieldConnectorID,\n\tFieldNonce,\n\tFieldClientStates,\n\tFieldCreatedAt,\n\tFieldLastActivity,\n\tFieldIPAddress,\n\tFieldUserAgent,\n\tFieldAbsoluteExpiry,\n\tFieldIdleExpiry,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tUserIDValidator func(string) error\n\t// ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\tConnectorIDValidator func(string) error\n\t// NonceValidator is a validator for the \"nonce\" field. It is called by the builders before save.\n\tNonceValidator func(string) error\n\t// DefaultIPAddress holds the default value on creation for the \"ip_address\" field.\n\tDefaultIPAddress string\n\t// DefaultUserAgent holds the default value on creation for the \"user_agent\" field.\n\tDefaultUserAgent string\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the AuthSession queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByUserID orders the results by the user_id field.\nfunc ByUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldUserID, opts...).ToFunc()\n}\n\n// ByConnectorID orders the results by the connector_id field.\nfunc ByConnectorID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldConnectorID, opts...).ToFunc()\n}\n\n// ByNonce orders the results by the nonce field.\nfunc ByNonce(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldNonce, opts...).ToFunc()\n}\n\n// ByCreatedAt orders the results by the created_at field.\nfunc ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCreatedAt, opts...).ToFunc()\n}\n\n// ByLastActivity orders the results by the last_activity field.\nfunc ByLastActivity(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldLastActivity, opts...).ToFunc()\n}\n\n// ByIPAddress orders the results by the ip_address field.\nfunc ByIPAddress(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldIPAddress, opts...).ToFunc()\n}\n\n// ByUserAgent orders the results by the user_agent field.\nfunc ByUserAgent(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldUserAgent, opts...).ToFunc()\n}\n\n// ByAbsoluteExpiry orders the results by the absolute_expiry field.\nfunc ByAbsoluteExpiry(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldAbsoluteExpiry, opts...).ToFunc()\n}\n\n// ByIdleExpiry orders the results by the idle_expiry field.\nfunc ByIdleExpiry(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldIdleExpiry, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/authsession/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage authsession\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContainsFold(FieldID, id))\n}\n\n// UserID applies equality check predicate on the \"user_id\" field. It's identical to UserIDEQ.\nfunc UserID(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldUserID, v))\n}\n\n// ConnectorID applies equality check predicate on the \"connector_id\" field. It's identical to ConnectorIDEQ.\nfunc ConnectorID(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// Nonce applies equality check predicate on the \"nonce\" field. It's identical to NonceEQ.\nfunc Nonce(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldNonce, v))\n}\n\n// ClientStates applies equality check predicate on the \"client_states\" field. It's identical to ClientStatesEQ.\nfunc ClientStates(v []byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldClientStates, v))\n}\n\n// CreatedAt applies equality check predicate on the \"created_at\" field. It's identical to CreatedAtEQ.\nfunc CreatedAt(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldCreatedAt, v))\n}\n\n// LastActivity applies equality check predicate on the \"last_activity\" field. It's identical to LastActivityEQ.\nfunc LastActivity(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldLastActivity, v))\n}\n\n// IPAddress applies equality check predicate on the \"ip_address\" field. It's identical to IPAddressEQ.\nfunc IPAddress(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldIPAddress, v))\n}\n\n// UserAgent applies equality check predicate on the \"user_agent\" field. It's identical to UserAgentEQ.\nfunc UserAgent(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldUserAgent, v))\n}\n\n// AbsoluteExpiry applies equality check predicate on the \"absolute_expiry\" field. It's identical to AbsoluteExpiryEQ.\nfunc AbsoluteExpiry(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldAbsoluteExpiry, v))\n}\n\n// IdleExpiry applies equality check predicate on the \"idle_expiry\" field. It's identical to IdleExpiryEQ.\nfunc IdleExpiry(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldIdleExpiry, v))\n}\n\n// UserIDEQ applies the EQ predicate on the \"user_id\" field.\nfunc UserIDEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldUserID, v))\n}\n\n// UserIDNEQ applies the NEQ predicate on the \"user_id\" field.\nfunc UserIDNEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldUserID, v))\n}\n\n// UserIDIn applies the In predicate on the \"user_id\" field.\nfunc UserIDIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldUserID, vs...))\n}\n\n// UserIDNotIn applies the NotIn predicate on the \"user_id\" field.\nfunc UserIDNotIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldUserID, vs...))\n}\n\n// UserIDGT applies the GT predicate on the \"user_id\" field.\nfunc UserIDGT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldUserID, v))\n}\n\n// UserIDGTE applies the GTE predicate on the \"user_id\" field.\nfunc UserIDGTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldUserID, v))\n}\n\n// UserIDLT applies the LT predicate on the \"user_id\" field.\nfunc UserIDLT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldUserID, v))\n}\n\n// UserIDLTE applies the LTE predicate on the \"user_id\" field.\nfunc UserIDLTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldUserID, v))\n}\n\n// UserIDContains applies the Contains predicate on the \"user_id\" field.\nfunc UserIDContains(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContains(FieldUserID, v))\n}\n\n// UserIDHasPrefix applies the HasPrefix predicate on the \"user_id\" field.\nfunc UserIDHasPrefix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasPrefix(FieldUserID, v))\n}\n\n// UserIDHasSuffix applies the HasSuffix predicate on the \"user_id\" field.\nfunc UserIDHasSuffix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasSuffix(FieldUserID, v))\n}\n\n// UserIDEqualFold applies the EqualFold predicate on the \"user_id\" field.\nfunc UserIDEqualFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEqualFold(FieldUserID, v))\n}\n\n// UserIDContainsFold applies the ContainsFold predicate on the \"user_id\" field.\nfunc UserIDContainsFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContainsFold(FieldUserID, v))\n}\n\n// ConnectorIDEQ applies the EQ predicate on the \"connector_id\" field.\nfunc ConnectorIDEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDNEQ applies the NEQ predicate on the \"connector_id\" field.\nfunc ConnectorIDNEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDIn applies the In predicate on the \"connector_id\" field.\nfunc ConnectorIDIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDNotIn applies the NotIn predicate on the \"connector_id\" field.\nfunc ConnectorIDNotIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDGT applies the GT predicate on the \"connector_id\" field.\nfunc ConnectorIDGT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldConnectorID, v))\n}\n\n// ConnectorIDGTE applies the GTE predicate on the \"connector_id\" field.\nfunc ConnectorIDGTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldConnectorID, v))\n}\n\n// ConnectorIDLT applies the LT predicate on the \"connector_id\" field.\nfunc ConnectorIDLT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldConnectorID, v))\n}\n\n// ConnectorIDLTE applies the LTE predicate on the \"connector_id\" field.\nfunc ConnectorIDLTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldConnectorID, v))\n}\n\n// ConnectorIDContains applies the Contains predicate on the \"connector_id\" field.\nfunc ConnectorIDContains(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContains(FieldConnectorID, v))\n}\n\n// ConnectorIDHasPrefix applies the HasPrefix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasPrefix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasPrefix(FieldConnectorID, v))\n}\n\n// ConnectorIDHasSuffix applies the HasSuffix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasSuffix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasSuffix(FieldConnectorID, v))\n}\n\n// ConnectorIDEqualFold applies the EqualFold predicate on the \"connector_id\" field.\nfunc ConnectorIDEqualFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEqualFold(FieldConnectorID, v))\n}\n\n// ConnectorIDContainsFold applies the ContainsFold predicate on the \"connector_id\" field.\nfunc ConnectorIDContainsFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContainsFold(FieldConnectorID, v))\n}\n\n// NonceEQ applies the EQ predicate on the \"nonce\" field.\nfunc NonceEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldNonce, v))\n}\n\n// NonceNEQ applies the NEQ predicate on the \"nonce\" field.\nfunc NonceNEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldNonce, v))\n}\n\n// NonceIn applies the In predicate on the \"nonce\" field.\nfunc NonceIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldNonce, vs...))\n}\n\n// NonceNotIn applies the NotIn predicate on the \"nonce\" field.\nfunc NonceNotIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldNonce, vs...))\n}\n\n// NonceGT applies the GT predicate on the \"nonce\" field.\nfunc NonceGT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldNonce, v))\n}\n\n// NonceGTE applies the GTE predicate on the \"nonce\" field.\nfunc NonceGTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldNonce, v))\n}\n\n// NonceLT applies the LT predicate on the \"nonce\" field.\nfunc NonceLT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldNonce, v))\n}\n\n// NonceLTE applies the LTE predicate on the \"nonce\" field.\nfunc NonceLTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldNonce, v))\n}\n\n// NonceContains applies the Contains predicate on the \"nonce\" field.\nfunc NonceContains(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContains(FieldNonce, v))\n}\n\n// NonceHasPrefix applies the HasPrefix predicate on the \"nonce\" field.\nfunc NonceHasPrefix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasPrefix(FieldNonce, v))\n}\n\n// NonceHasSuffix applies the HasSuffix predicate on the \"nonce\" field.\nfunc NonceHasSuffix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasSuffix(FieldNonce, v))\n}\n\n// NonceEqualFold applies the EqualFold predicate on the \"nonce\" field.\nfunc NonceEqualFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEqualFold(FieldNonce, v))\n}\n\n// NonceContainsFold applies the ContainsFold predicate on the \"nonce\" field.\nfunc NonceContainsFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContainsFold(FieldNonce, v))\n}\n\n// ClientStatesEQ applies the EQ predicate on the \"client_states\" field.\nfunc ClientStatesEQ(v []byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldClientStates, v))\n}\n\n// ClientStatesNEQ applies the NEQ predicate on the \"client_states\" field.\nfunc ClientStatesNEQ(v []byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldClientStates, v))\n}\n\n// ClientStatesIn applies the In predicate on the \"client_states\" field.\nfunc ClientStatesIn(vs ...[]byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldClientStates, vs...))\n}\n\n// ClientStatesNotIn applies the NotIn predicate on the \"client_states\" field.\nfunc ClientStatesNotIn(vs ...[]byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldClientStates, vs...))\n}\n\n// ClientStatesGT applies the GT predicate on the \"client_states\" field.\nfunc ClientStatesGT(v []byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldClientStates, v))\n}\n\n// ClientStatesGTE applies the GTE predicate on the \"client_states\" field.\nfunc ClientStatesGTE(v []byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldClientStates, v))\n}\n\n// ClientStatesLT applies the LT predicate on the \"client_states\" field.\nfunc ClientStatesLT(v []byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldClientStates, v))\n}\n\n// ClientStatesLTE applies the LTE predicate on the \"client_states\" field.\nfunc ClientStatesLTE(v []byte) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldClientStates, v))\n}\n\n// CreatedAtEQ applies the EQ predicate on the \"created_at\" field.\nfunc CreatedAtEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldCreatedAt, v))\n}\n\n// CreatedAtNEQ applies the NEQ predicate on the \"created_at\" field.\nfunc CreatedAtNEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldCreatedAt, v))\n}\n\n// CreatedAtIn applies the In predicate on the \"created_at\" field.\nfunc CreatedAtIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldCreatedAt, vs...))\n}\n\n// CreatedAtNotIn applies the NotIn predicate on the \"created_at\" field.\nfunc CreatedAtNotIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldCreatedAt, vs...))\n}\n\n// CreatedAtGT applies the GT predicate on the \"created_at\" field.\nfunc CreatedAtGT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldCreatedAt, v))\n}\n\n// CreatedAtGTE applies the GTE predicate on the \"created_at\" field.\nfunc CreatedAtGTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldCreatedAt, v))\n}\n\n// CreatedAtLT applies the LT predicate on the \"created_at\" field.\nfunc CreatedAtLT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldCreatedAt, v))\n}\n\n// CreatedAtLTE applies the LTE predicate on the \"created_at\" field.\nfunc CreatedAtLTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldCreatedAt, v))\n}\n\n// LastActivityEQ applies the EQ predicate on the \"last_activity\" field.\nfunc LastActivityEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldLastActivity, v))\n}\n\n// LastActivityNEQ applies the NEQ predicate on the \"last_activity\" field.\nfunc LastActivityNEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldLastActivity, v))\n}\n\n// LastActivityIn applies the In predicate on the \"last_activity\" field.\nfunc LastActivityIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldLastActivity, vs...))\n}\n\n// LastActivityNotIn applies the NotIn predicate on the \"last_activity\" field.\nfunc LastActivityNotIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldLastActivity, vs...))\n}\n\n// LastActivityGT applies the GT predicate on the \"last_activity\" field.\nfunc LastActivityGT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldLastActivity, v))\n}\n\n// LastActivityGTE applies the GTE predicate on the \"last_activity\" field.\nfunc LastActivityGTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldLastActivity, v))\n}\n\n// LastActivityLT applies the LT predicate on the \"last_activity\" field.\nfunc LastActivityLT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldLastActivity, v))\n}\n\n// LastActivityLTE applies the LTE predicate on the \"last_activity\" field.\nfunc LastActivityLTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldLastActivity, v))\n}\n\n// IPAddressEQ applies the EQ predicate on the \"ip_address\" field.\nfunc IPAddressEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldIPAddress, v))\n}\n\n// IPAddressNEQ applies the NEQ predicate on the \"ip_address\" field.\nfunc IPAddressNEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldIPAddress, v))\n}\n\n// IPAddressIn applies the In predicate on the \"ip_address\" field.\nfunc IPAddressIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldIPAddress, vs...))\n}\n\n// IPAddressNotIn applies the NotIn predicate on the \"ip_address\" field.\nfunc IPAddressNotIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldIPAddress, vs...))\n}\n\n// IPAddressGT applies the GT predicate on the \"ip_address\" field.\nfunc IPAddressGT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldIPAddress, v))\n}\n\n// IPAddressGTE applies the GTE predicate on the \"ip_address\" field.\nfunc IPAddressGTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldIPAddress, v))\n}\n\n// IPAddressLT applies the LT predicate on the \"ip_address\" field.\nfunc IPAddressLT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldIPAddress, v))\n}\n\n// IPAddressLTE applies the LTE predicate on the \"ip_address\" field.\nfunc IPAddressLTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldIPAddress, v))\n}\n\n// IPAddressContains applies the Contains predicate on the \"ip_address\" field.\nfunc IPAddressContains(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContains(FieldIPAddress, v))\n}\n\n// IPAddressHasPrefix applies the HasPrefix predicate on the \"ip_address\" field.\nfunc IPAddressHasPrefix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasPrefix(FieldIPAddress, v))\n}\n\n// IPAddressHasSuffix applies the HasSuffix predicate on the \"ip_address\" field.\nfunc IPAddressHasSuffix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasSuffix(FieldIPAddress, v))\n}\n\n// IPAddressEqualFold applies the EqualFold predicate on the \"ip_address\" field.\nfunc IPAddressEqualFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEqualFold(FieldIPAddress, v))\n}\n\n// IPAddressContainsFold applies the ContainsFold predicate on the \"ip_address\" field.\nfunc IPAddressContainsFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContainsFold(FieldIPAddress, v))\n}\n\n// UserAgentEQ applies the EQ predicate on the \"user_agent\" field.\nfunc UserAgentEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldUserAgent, v))\n}\n\n// UserAgentNEQ applies the NEQ predicate on the \"user_agent\" field.\nfunc UserAgentNEQ(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldUserAgent, v))\n}\n\n// UserAgentIn applies the In predicate on the \"user_agent\" field.\nfunc UserAgentIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldUserAgent, vs...))\n}\n\n// UserAgentNotIn applies the NotIn predicate on the \"user_agent\" field.\nfunc UserAgentNotIn(vs ...string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldUserAgent, vs...))\n}\n\n// UserAgentGT applies the GT predicate on the \"user_agent\" field.\nfunc UserAgentGT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldUserAgent, v))\n}\n\n// UserAgentGTE applies the GTE predicate on the \"user_agent\" field.\nfunc UserAgentGTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldUserAgent, v))\n}\n\n// UserAgentLT applies the LT predicate on the \"user_agent\" field.\nfunc UserAgentLT(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldUserAgent, v))\n}\n\n// UserAgentLTE applies the LTE predicate on the \"user_agent\" field.\nfunc UserAgentLTE(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldUserAgent, v))\n}\n\n// UserAgentContains applies the Contains predicate on the \"user_agent\" field.\nfunc UserAgentContains(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContains(FieldUserAgent, v))\n}\n\n// UserAgentHasPrefix applies the HasPrefix predicate on the \"user_agent\" field.\nfunc UserAgentHasPrefix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasPrefix(FieldUserAgent, v))\n}\n\n// UserAgentHasSuffix applies the HasSuffix predicate on the \"user_agent\" field.\nfunc UserAgentHasSuffix(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldHasSuffix(FieldUserAgent, v))\n}\n\n// UserAgentEqualFold applies the EqualFold predicate on the \"user_agent\" field.\nfunc UserAgentEqualFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEqualFold(FieldUserAgent, v))\n}\n\n// UserAgentContainsFold applies the ContainsFold predicate on the \"user_agent\" field.\nfunc UserAgentContainsFold(v string) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldContainsFold(FieldUserAgent, v))\n}\n\n// AbsoluteExpiryEQ applies the EQ predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldAbsoluteExpiry, v))\n}\n\n// AbsoluteExpiryNEQ applies the NEQ predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryNEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldAbsoluteExpiry, v))\n}\n\n// AbsoluteExpiryIn applies the In predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldAbsoluteExpiry, vs...))\n}\n\n// AbsoluteExpiryNotIn applies the NotIn predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryNotIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldAbsoluteExpiry, vs...))\n}\n\n// AbsoluteExpiryGT applies the GT predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryGT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldAbsoluteExpiry, v))\n}\n\n// AbsoluteExpiryGTE applies the GTE predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryGTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldAbsoluteExpiry, v))\n}\n\n// AbsoluteExpiryLT applies the LT predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryLT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldAbsoluteExpiry, v))\n}\n\n// AbsoluteExpiryLTE applies the LTE predicate on the \"absolute_expiry\" field.\nfunc AbsoluteExpiryLTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldAbsoluteExpiry, v))\n}\n\n// IdleExpiryEQ applies the EQ predicate on the \"idle_expiry\" field.\nfunc IdleExpiryEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldEQ(FieldIdleExpiry, v))\n}\n\n// IdleExpiryNEQ applies the NEQ predicate on the \"idle_expiry\" field.\nfunc IdleExpiryNEQ(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNEQ(FieldIdleExpiry, v))\n}\n\n// IdleExpiryIn applies the In predicate on the \"idle_expiry\" field.\nfunc IdleExpiryIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldIn(FieldIdleExpiry, vs...))\n}\n\n// IdleExpiryNotIn applies the NotIn predicate on the \"idle_expiry\" field.\nfunc IdleExpiryNotIn(vs ...time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldNotIn(FieldIdleExpiry, vs...))\n}\n\n// IdleExpiryGT applies the GT predicate on the \"idle_expiry\" field.\nfunc IdleExpiryGT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGT(FieldIdleExpiry, v))\n}\n\n// IdleExpiryGTE applies the GTE predicate on the \"idle_expiry\" field.\nfunc IdleExpiryGTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldGTE(FieldIdleExpiry, v))\n}\n\n// IdleExpiryLT applies the LT predicate on the \"idle_expiry\" field.\nfunc IdleExpiryLT(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLT(FieldIdleExpiry, v))\n}\n\n// IdleExpiryLTE applies the LTE predicate on the \"idle_expiry\" field.\nfunc IdleExpiryLTE(v time.Time) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.FieldLTE(FieldIdleExpiry, v))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.AuthSession) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.AuthSession) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.AuthSession) predicate.AuthSession {\n\treturn predicate.AuthSession(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/authsession.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n)\n\n// AuthSession is the model entity for the AuthSession schema.\ntype AuthSession struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// UserID holds the value of the \"user_id\" field.\n\tUserID string `json:\"user_id,omitempty\"`\n\t// ConnectorID holds the value of the \"connector_id\" field.\n\tConnectorID string `json:\"connector_id,omitempty\"`\n\t// Nonce holds the value of the \"nonce\" field.\n\tNonce string `json:\"nonce,omitempty\"`\n\t// ClientStates holds the value of the \"client_states\" field.\n\tClientStates []byte `json:\"client_states,omitempty\"`\n\t// CreatedAt holds the value of the \"created_at\" field.\n\tCreatedAt time.Time `json:\"created_at,omitempty\"`\n\t// LastActivity holds the value of the \"last_activity\" field.\n\tLastActivity time.Time `json:\"last_activity,omitempty\"`\n\t// IPAddress holds the value of the \"ip_address\" field.\n\tIPAddress string `json:\"ip_address,omitempty\"`\n\t// UserAgent holds the value of the \"user_agent\" field.\n\tUserAgent string `json:\"user_agent,omitempty\"`\n\t// AbsoluteExpiry holds the value of the \"absolute_expiry\" field.\n\tAbsoluteExpiry time.Time `json:\"absolute_expiry,omitempty\"`\n\t// IdleExpiry holds the value of the \"idle_expiry\" field.\n\tIdleExpiry   time.Time `json:\"idle_expiry,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*AuthSession) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase authsession.FieldClientStates:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase authsession.FieldID, authsession.FieldUserID, authsession.FieldConnectorID, authsession.FieldNonce, authsession.FieldIPAddress, authsession.FieldUserAgent:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase authsession.FieldCreatedAt, authsession.FieldLastActivity, authsession.FieldAbsoluteExpiry, authsession.FieldIdleExpiry:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the AuthSession fields.\nfunc (_m *AuthSession) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase authsession.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase authsession.FieldUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.UserID = value.String\n\t\t\t}\n\t\tcase authsession.FieldConnectorID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ConnectorID = value.String\n\t\t\t}\n\t\tcase authsession.FieldNonce:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field nonce\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Nonce = value.String\n\t\t\t}\n\t\tcase authsession.FieldClientStates:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field client_states\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.ClientStates = *value\n\t\t\t}\n\t\tcase authsession.FieldCreatedAt:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field created_at\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CreatedAt = value.Time\n\t\t\t}\n\t\tcase authsession.FieldLastActivity:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field last_activity\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.LastActivity = value.Time\n\t\t\t}\n\t\tcase authsession.FieldIPAddress:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field ip_address\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.IPAddress = value.String\n\t\t\t}\n\t\tcase authsession.FieldUserAgent:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field user_agent\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.UserAgent = value.String\n\t\t\t}\n\t\tcase authsession.FieldAbsoluteExpiry:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field absolute_expiry\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.AbsoluteExpiry = value.Time\n\t\t\t}\n\t\tcase authsession.FieldIdleExpiry:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field idle_expiry\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.IdleExpiry = value.Time\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the AuthSession.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *AuthSession) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this AuthSession.\n// Note that you need to call AuthSession.Unwrap() before calling this method if this AuthSession\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *AuthSession) Update() *AuthSessionUpdateOne {\n\treturn NewAuthSessionClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the AuthSession entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *AuthSession) Unwrap() *AuthSession {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: AuthSession is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *AuthSession) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"AuthSession(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"user_id=\")\n\tbuilder.WriteString(_m.UserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"connector_id=\")\n\tbuilder.WriteString(_m.ConnectorID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"nonce=\")\n\tbuilder.WriteString(_m.Nonce)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"client_states=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClientStates))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"created_at=\")\n\tbuilder.WriteString(_m.CreatedAt.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"last_activity=\")\n\tbuilder.WriteString(_m.LastActivity.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"ip_address=\")\n\tbuilder.WriteString(_m.IPAddress)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"user_agent=\")\n\tbuilder.WriteString(_m.UserAgent)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"absolute_expiry=\")\n\tbuilder.WriteString(_m.AbsoluteExpiry.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"idle_expiry=\")\n\tbuilder.WriteString(_m.IdleExpiry.Format(time.ANSIC))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// AuthSessions is a parsable slice of AuthSession.\ntype AuthSessions []*AuthSession\n"
  },
  {
    "path": "storage/ent/db/authsession_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n)\n\n// AuthSessionCreate is the builder for creating a AuthSession entity.\ntype AuthSessionCreate struct {\n\tconfig\n\tmutation *AuthSessionMutation\n\thooks    []Hook\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_c *AuthSessionCreate) SetUserID(v string) *AuthSessionCreate {\n\t_c.mutation.SetUserID(v)\n\treturn _c\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_c *AuthSessionCreate) SetConnectorID(v string) *AuthSessionCreate {\n\t_c.mutation.SetConnectorID(v)\n\treturn _c\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_c *AuthSessionCreate) SetNonce(v string) *AuthSessionCreate {\n\t_c.mutation.SetNonce(v)\n\treturn _c\n}\n\n// SetClientStates sets the \"client_states\" field.\nfunc (_c *AuthSessionCreate) SetClientStates(v []byte) *AuthSessionCreate {\n\t_c.mutation.SetClientStates(v)\n\treturn _c\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_c *AuthSessionCreate) SetCreatedAt(v time.Time) *AuthSessionCreate {\n\t_c.mutation.SetCreatedAt(v)\n\treturn _c\n}\n\n// SetLastActivity sets the \"last_activity\" field.\nfunc (_c *AuthSessionCreate) SetLastActivity(v time.Time) *AuthSessionCreate {\n\t_c.mutation.SetLastActivity(v)\n\treturn _c\n}\n\n// SetIPAddress sets the \"ip_address\" field.\nfunc (_c *AuthSessionCreate) SetIPAddress(v string) *AuthSessionCreate {\n\t_c.mutation.SetIPAddress(v)\n\treturn _c\n}\n\n// SetNillableIPAddress sets the \"ip_address\" field if the given value is not nil.\nfunc (_c *AuthSessionCreate) SetNillableIPAddress(v *string) *AuthSessionCreate {\n\tif v != nil {\n\t\t_c.SetIPAddress(*v)\n\t}\n\treturn _c\n}\n\n// SetUserAgent sets the \"user_agent\" field.\nfunc (_c *AuthSessionCreate) SetUserAgent(v string) *AuthSessionCreate {\n\t_c.mutation.SetUserAgent(v)\n\treturn _c\n}\n\n// SetNillableUserAgent sets the \"user_agent\" field if the given value is not nil.\nfunc (_c *AuthSessionCreate) SetNillableUserAgent(v *string) *AuthSessionCreate {\n\tif v != nil {\n\t\t_c.SetUserAgent(*v)\n\t}\n\treturn _c\n}\n\n// SetAbsoluteExpiry sets the \"absolute_expiry\" field.\nfunc (_c *AuthSessionCreate) SetAbsoluteExpiry(v time.Time) *AuthSessionCreate {\n\t_c.mutation.SetAbsoluteExpiry(v)\n\treturn _c\n}\n\n// SetIdleExpiry sets the \"idle_expiry\" field.\nfunc (_c *AuthSessionCreate) SetIdleExpiry(v time.Time) *AuthSessionCreate {\n\t_c.mutation.SetIdleExpiry(v)\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *AuthSessionCreate) SetID(v string) *AuthSessionCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the AuthSessionMutation object of the builder.\nfunc (_c *AuthSessionCreate) Mutation() *AuthSessionMutation {\n\treturn _c.mutation\n}\n\n// Save creates the AuthSession in the database.\nfunc (_c *AuthSessionCreate) Save(ctx context.Context) (*AuthSession, error) {\n\t_c.defaults()\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *AuthSessionCreate) SaveX(ctx context.Context) *AuthSession {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *AuthSessionCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *AuthSessionCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// defaults sets the default values of the builder before save.\nfunc (_c *AuthSessionCreate) defaults() {\n\tif _, ok := _c.mutation.IPAddress(); !ok {\n\t\tv := authsession.DefaultIPAddress\n\t\t_c.mutation.SetIPAddress(v)\n\t}\n\tif _, ok := _c.mutation.UserAgent(); !ok {\n\t\tv := authsession.DefaultUserAgent\n\t\t_c.mutation.SetUserAgent(v)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *AuthSessionCreate) check() error {\n\tif _, ok := _c.mutation.UserID(); !ok {\n\t\treturn &ValidationError{Name: \"user_id\", err: errors.New(`db: missing required field \"AuthSession.user_id\"`)}\n\t}\n\tif v, ok := _c.mutation.UserID(); ok {\n\t\tif err := authsession.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ConnectorID(); !ok {\n\t\treturn &ValidationError{Name: \"connector_id\", err: errors.New(`db: missing required field \"AuthSession.connector_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ConnectorID(); ok {\n\t\tif err := authsession.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.connector_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Nonce(); !ok {\n\t\treturn &ValidationError{Name: \"nonce\", err: errors.New(`db: missing required field \"AuthSession.nonce\"`)}\n\t}\n\tif v, ok := _c.mutation.Nonce(); ok {\n\t\tif err := authsession.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.nonce\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClientStates(); !ok {\n\t\treturn &ValidationError{Name: \"client_states\", err: errors.New(`db: missing required field \"AuthSession.client_states\"`)}\n\t}\n\tif _, ok := _c.mutation.CreatedAt(); !ok {\n\t\treturn &ValidationError{Name: \"created_at\", err: errors.New(`db: missing required field \"AuthSession.created_at\"`)}\n\t}\n\tif _, ok := _c.mutation.LastActivity(); !ok {\n\t\treturn &ValidationError{Name: \"last_activity\", err: errors.New(`db: missing required field \"AuthSession.last_activity\"`)}\n\t}\n\tif _, ok := _c.mutation.IPAddress(); !ok {\n\t\treturn &ValidationError{Name: \"ip_address\", err: errors.New(`db: missing required field \"AuthSession.ip_address\"`)}\n\t}\n\tif _, ok := _c.mutation.UserAgent(); !ok {\n\t\treturn &ValidationError{Name: \"user_agent\", err: errors.New(`db: missing required field \"AuthSession.user_agent\"`)}\n\t}\n\tif _, ok := _c.mutation.AbsoluteExpiry(); !ok {\n\t\treturn &ValidationError{Name: \"absolute_expiry\", err: errors.New(`db: missing required field \"AuthSession.absolute_expiry\"`)}\n\t}\n\tif _, ok := _c.mutation.IdleExpiry(); !ok {\n\t\treturn &ValidationError{Name: \"idle_expiry\", err: errors.New(`db: missing required field \"AuthSession.idle_expiry\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := authsession.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *AuthSessionCreate) sqlSave(ctx context.Context) (*AuthSession, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected AuthSession.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *AuthSessionCreate) createSpec() (*AuthSession, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &AuthSession{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(authsession.Table, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.UserID(); ok {\n\t\t_spec.SetField(authsession.FieldUserID, field.TypeString, value)\n\t\t_node.UserID = value\n\t}\n\tif value, ok := _c.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authsession.FieldConnectorID, field.TypeString, value)\n\t\t_node.ConnectorID = value\n\t}\n\tif value, ok := _c.mutation.Nonce(); ok {\n\t\t_spec.SetField(authsession.FieldNonce, field.TypeString, value)\n\t\t_node.Nonce = value\n\t}\n\tif value, ok := _c.mutation.ClientStates(); ok {\n\t\t_spec.SetField(authsession.FieldClientStates, field.TypeBytes, value)\n\t\t_node.ClientStates = value\n\t}\n\tif value, ok := _c.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(authsession.FieldCreatedAt, field.TypeTime, value)\n\t\t_node.CreatedAt = value\n\t}\n\tif value, ok := _c.mutation.LastActivity(); ok {\n\t\t_spec.SetField(authsession.FieldLastActivity, field.TypeTime, value)\n\t\t_node.LastActivity = value\n\t}\n\tif value, ok := _c.mutation.IPAddress(); ok {\n\t\t_spec.SetField(authsession.FieldIPAddress, field.TypeString, value)\n\t\t_node.IPAddress = value\n\t}\n\tif value, ok := _c.mutation.UserAgent(); ok {\n\t\t_spec.SetField(authsession.FieldUserAgent, field.TypeString, value)\n\t\t_node.UserAgent = value\n\t}\n\tif value, ok := _c.mutation.AbsoluteExpiry(); ok {\n\t\t_spec.SetField(authsession.FieldAbsoluteExpiry, field.TypeTime, value)\n\t\t_node.AbsoluteExpiry = value\n\t}\n\tif value, ok := _c.mutation.IdleExpiry(); ok {\n\t\t_spec.SetField(authsession.FieldIdleExpiry, field.TypeTime, value)\n\t\t_node.IdleExpiry = value\n\t}\n\treturn _node, _spec\n}\n\n// AuthSessionCreateBulk is the builder for creating many AuthSession entities in bulk.\ntype AuthSessionCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*AuthSessionCreate\n}\n\n// Save creates the AuthSession entities in the database.\nfunc (_c *AuthSessionCreateBulk) Save(ctx context.Context) ([]*AuthSession, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*AuthSession, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tbuilder.defaults()\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*AuthSessionMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *AuthSessionCreateBulk) SaveX(ctx context.Context) []*AuthSession {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *AuthSessionCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *AuthSessionCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/authsession_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthSessionDelete is the builder for deleting a AuthSession entity.\ntype AuthSessionDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *AuthSessionMutation\n}\n\n// Where appends a list predicates to the AuthSessionDelete builder.\nfunc (_d *AuthSessionDelete) Where(ps ...predicate.AuthSession) *AuthSessionDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *AuthSessionDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *AuthSessionDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *AuthSessionDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(authsession.Table, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// AuthSessionDeleteOne is the builder for deleting a single AuthSession entity.\ntype AuthSessionDeleteOne struct {\n\t_d *AuthSessionDelete\n}\n\n// Where appends a list predicates to the AuthSessionDelete builder.\nfunc (_d *AuthSessionDeleteOne) Where(ps ...predicate.AuthSession) *AuthSessionDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *AuthSessionDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{authsession.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *AuthSessionDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/authsession_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthSessionQuery is the builder for querying AuthSession entities.\ntype AuthSessionQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []authsession.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.AuthSession\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the AuthSessionQuery builder.\nfunc (_q *AuthSessionQuery) Where(ps ...predicate.AuthSession) *AuthSessionQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *AuthSessionQuery) Limit(limit int) *AuthSessionQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *AuthSessionQuery) Offset(offset int) *AuthSessionQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *AuthSessionQuery) Unique(unique bool) *AuthSessionQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *AuthSessionQuery) Order(o ...authsession.OrderOption) *AuthSessionQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first AuthSession entity from the query.\n// Returns a *NotFoundError when no AuthSession was found.\nfunc (_q *AuthSessionQuery) First(ctx context.Context) (*AuthSession, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{authsession.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) FirstX(ctx context.Context) *AuthSession {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first AuthSession ID from the query.\n// Returns a *NotFoundError when no AuthSession ID was found.\nfunc (_q *AuthSessionQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{authsession.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single AuthSession entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one AuthSession entity is found.\n// Returns a *NotFoundError when no AuthSession entities are found.\nfunc (_q *AuthSessionQuery) Only(ctx context.Context) (*AuthSession, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{authsession.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{authsession.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) OnlyX(ctx context.Context) *AuthSession {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only AuthSession ID in the query.\n// Returns a *NotSingularError when more than one AuthSession ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *AuthSessionQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{authsession.Label}\n\tdefault:\n\t\terr = &NotSingularError{authsession.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of AuthSessions.\nfunc (_q *AuthSessionQuery) All(ctx context.Context) ([]*AuthSession, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*AuthSession, *AuthSessionQuery]()\n\treturn withInterceptors[[]*AuthSession](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) AllX(ctx context.Context) []*AuthSession {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of AuthSession IDs.\nfunc (_q *AuthSessionQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(authsession.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *AuthSessionQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*AuthSessionQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *AuthSessionQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *AuthSessionQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the AuthSessionQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *AuthSessionQuery) Clone() *AuthSessionQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &AuthSessionQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]authsession.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.AuthSession{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserID string `json:\"user_id,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.AuthSession.Query().\n//\t\tGroupBy(authsession.FieldUserID).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *AuthSessionQuery) GroupBy(field string, fields ...string) *AuthSessionGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &AuthSessionGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = authsession.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserID string `json:\"user_id,omitempty\"`\n//\t}\n//\n//\tclient.AuthSession.Query().\n//\t\tSelect(authsession.FieldUserID).\n//\t\tScan(ctx, &v)\nfunc (_q *AuthSessionQuery) Select(fields ...string) *AuthSessionSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &AuthSessionSelect{AuthSessionQuery: _q}\n\tsbuild.label = authsession.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a AuthSessionSelect configured with the given aggregations.\nfunc (_q *AuthSessionQuery) Aggregate(fns ...AggregateFunc) *AuthSessionSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *AuthSessionQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !authsession.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *AuthSessionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*AuthSession, error) {\n\tvar (\n\t\tnodes = []*AuthSession{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*AuthSession).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &AuthSession{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *AuthSessionQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *AuthSessionQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(authsession.Table, authsession.Columns, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, authsession.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != authsession.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *AuthSessionQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(authsession.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = authsession.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// AuthSessionGroupBy is the group-by builder for AuthSession entities.\ntype AuthSessionGroupBy struct {\n\tselector\n\tbuild *AuthSessionQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *AuthSessionGroupBy) Aggregate(fns ...AggregateFunc) *AuthSessionGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *AuthSessionGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*AuthSessionQuery, *AuthSessionGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *AuthSessionGroupBy) sqlScan(ctx context.Context, root *AuthSessionQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// AuthSessionSelect is the builder for selecting fields of AuthSession entities.\ntype AuthSessionSelect struct {\n\t*AuthSessionQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *AuthSessionSelect) Aggregate(fns ...AggregateFunc) *AuthSessionSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *AuthSessionSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*AuthSessionQuery, *AuthSessionSelect](ctx, _s.AuthSessionQuery, _s, _s.inters, v)\n}\n\nfunc (_s *AuthSessionSelect) sqlScan(ctx context.Context, root *AuthSessionQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/authsession_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// AuthSessionUpdate is the builder for updating AuthSession entities.\ntype AuthSessionUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *AuthSessionMutation\n}\n\n// Where appends a list predicates to the AuthSessionUpdate builder.\nfunc (_u *AuthSessionUpdate) Where(ps ...predicate.AuthSession) *AuthSessionUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *AuthSessionUpdate) SetUserID(v string) *AuthSessionUpdate {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableUserID(v *string) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *AuthSessionUpdate) SetConnectorID(v string) *AuthSessionUpdate {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableConnectorID(v *string) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *AuthSessionUpdate) SetNonce(v string) *AuthSessionUpdate {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableNonce(v *string) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetClientStates sets the \"client_states\" field.\nfunc (_u *AuthSessionUpdate) SetClientStates(v []byte) *AuthSessionUpdate {\n\t_u.mutation.SetClientStates(v)\n\treturn _u\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_u *AuthSessionUpdate) SetCreatedAt(v time.Time) *AuthSessionUpdate {\n\t_u.mutation.SetCreatedAt(v)\n\treturn _u\n}\n\n// SetNillableCreatedAt sets the \"created_at\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableCreatedAt(v *time.Time) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetCreatedAt(*v)\n\t}\n\treturn _u\n}\n\n// SetLastActivity sets the \"last_activity\" field.\nfunc (_u *AuthSessionUpdate) SetLastActivity(v time.Time) *AuthSessionUpdate {\n\t_u.mutation.SetLastActivity(v)\n\treturn _u\n}\n\n// SetNillableLastActivity sets the \"last_activity\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableLastActivity(v *time.Time) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetLastActivity(*v)\n\t}\n\treturn _u\n}\n\n// SetIPAddress sets the \"ip_address\" field.\nfunc (_u *AuthSessionUpdate) SetIPAddress(v string) *AuthSessionUpdate {\n\t_u.mutation.SetIPAddress(v)\n\treturn _u\n}\n\n// SetNillableIPAddress sets the \"ip_address\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableIPAddress(v *string) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetIPAddress(*v)\n\t}\n\treturn _u\n}\n\n// SetUserAgent sets the \"user_agent\" field.\nfunc (_u *AuthSessionUpdate) SetUserAgent(v string) *AuthSessionUpdate {\n\t_u.mutation.SetUserAgent(v)\n\treturn _u\n}\n\n// SetNillableUserAgent sets the \"user_agent\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableUserAgent(v *string) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetUserAgent(*v)\n\t}\n\treturn _u\n}\n\n// SetAbsoluteExpiry sets the \"absolute_expiry\" field.\nfunc (_u *AuthSessionUpdate) SetAbsoluteExpiry(v time.Time) *AuthSessionUpdate {\n\t_u.mutation.SetAbsoluteExpiry(v)\n\treturn _u\n}\n\n// SetNillableAbsoluteExpiry sets the \"absolute_expiry\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableAbsoluteExpiry(v *time.Time) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetAbsoluteExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetIdleExpiry sets the \"idle_expiry\" field.\nfunc (_u *AuthSessionUpdate) SetIdleExpiry(v time.Time) *AuthSessionUpdate {\n\t_u.mutation.SetIdleExpiry(v)\n\treturn _u\n}\n\n// SetNillableIdleExpiry sets the \"idle_expiry\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdate) SetNillableIdleExpiry(v *time.Time) *AuthSessionUpdate {\n\tif v != nil {\n\t\t_u.SetIdleExpiry(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the AuthSessionMutation object of the builder.\nfunc (_u *AuthSessionUpdate) Mutation() *AuthSessionMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *AuthSessionUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *AuthSessionUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *AuthSessionUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *AuthSessionUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *AuthSessionUpdate) check() error {\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := authsession.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := authsession.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.connector_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Nonce(); ok {\n\t\tif err := authsession.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.nonce\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *AuthSessionUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(authsession.Table, authsession.Columns, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(authsession.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authsession.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(authsession.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClientStates(); ok {\n\t\t_spec.SetField(authsession.FieldClientStates, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(authsession.FieldCreatedAt, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastActivity(); ok {\n\t\t_spec.SetField(authsession.FieldLastActivity, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.IPAddress(); ok {\n\t\t_spec.SetField(authsession.FieldIPAddress, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.UserAgent(); ok {\n\t\t_spec.SetField(authsession.FieldUserAgent, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.AbsoluteExpiry(); ok {\n\t\t_spec.SetField(authsession.FieldAbsoluteExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.IdleExpiry(); ok {\n\t\t_spec.SetField(authsession.FieldIdleExpiry, field.TypeTime, value)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{authsession.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// AuthSessionUpdateOne is the builder for updating a single AuthSession entity.\ntype AuthSessionUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *AuthSessionMutation\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *AuthSessionUpdateOne) SetUserID(v string) *AuthSessionUpdateOne {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableUserID(v *string) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *AuthSessionUpdateOne) SetConnectorID(v string) *AuthSessionUpdateOne {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableConnectorID(v *string) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *AuthSessionUpdateOne) SetNonce(v string) *AuthSessionUpdateOne {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableNonce(v *string) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetClientStates sets the \"client_states\" field.\nfunc (_u *AuthSessionUpdateOne) SetClientStates(v []byte) *AuthSessionUpdateOne {\n\t_u.mutation.SetClientStates(v)\n\treturn _u\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_u *AuthSessionUpdateOne) SetCreatedAt(v time.Time) *AuthSessionUpdateOne {\n\t_u.mutation.SetCreatedAt(v)\n\treturn _u\n}\n\n// SetNillableCreatedAt sets the \"created_at\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableCreatedAt(v *time.Time) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetCreatedAt(*v)\n\t}\n\treturn _u\n}\n\n// SetLastActivity sets the \"last_activity\" field.\nfunc (_u *AuthSessionUpdateOne) SetLastActivity(v time.Time) *AuthSessionUpdateOne {\n\t_u.mutation.SetLastActivity(v)\n\treturn _u\n}\n\n// SetNillableLastActivity sets the \"last_activity\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableLastActivity(v *time.Time) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetLastActivity(*v)\n\t}\n\treturn _u\n}\n\n// SetIPAddress sets the \"ip_address\" field.\nfunc (_u *AuthSessionUpdateOne) SetIPAddress(v string) *AuthSessionUpdateOne {\n\t_u.mutation.SetIPAddress(v)\n\treturn _u\n}\n\n// SetNillableIPAddress sets the \"ip_address\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableIPAddress(v *string) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetIPAddress(*v)\n\t}\n\treturn _u\n}\n\n// SetUserAgent sets the \"user_agent\" field.\nfunc (_u *AuthSessionUpdateOne) SetUserAgent(v string) *AuthSessionUpdateOne {\n\t_u.mutation.SetUserAgent(v)\n\treturn _u\n}\n\n// SetNillableUserAgent sets the \"user_agent\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableUserAgent(v *string) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetUserAgent(*v)\n\t}\n\treturn _u\n}\n\n// SetAbsoluteExpiry sets the \"absolute_expiry\" field.\nfunc (_u *AuthSessionUpdateOne) SetAbsoluteExpiry(v time.Time) *AuthSessionUpdateOne {\n\t_u.mutation.SetAbsoluteExpiry(v)\n\treturn _u\n}\n\n// SetNillableAbsoluteExpiry sets the \"absolute_expiry\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableAbsoluteExpiry(v *time.Time) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetAbsoluteExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetIdleExpiry sets the \"idle_expiry\" field.\nfunc (_u *AuthSessionUpdateOne) SetIdleExpiry(v time.Time) *AuthSessionUpdateOne {\n\t_u.mutation.SetIdleExpiry(v)\n\treturn _u\n}\n\n// SetNillableIdleExpiry sets the \"idle_expiry\" field if the given value is not nil.\nfunc (_u *AuthSessionUpdateOne) SetNillableIdleExpiry(v *time.Time) *AuthSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetIdleExpiry(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the AuthSessionMutation object of the builder.\nfunc (_u *AuthSessionUpdateOne) Mutation() *AuthSessionMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the AuthSessionUpdate builder.\nfunc (_u *AuthSessionUpdateOne) Where(ps ...predicate.AuthSession) *AuthSessionUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *AuthSessionUpdateOne) Select(field string, fields ...string) *AuthSessionUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated AuthSession entity.\nfunc (_u *AuthSessionUpdateOne) Save(ctx context.Context) (*AuthSession, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *AuthSessionUpdateOne) SaveX(ctx context.Context) *AuthSession {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *AuthSessionUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *AuthSessionUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *AuthSessionUpdateOne) check() error {\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := authsession.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := authsession.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.connector_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Nonce(); ok {\n\t\tif err := authsession.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"AuthSession.nonce\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *AuthSessionUpdateOne) sqlSave(ctx context.Context) (_node *AuthSession, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(authsession.Table, authsession.Columns, sqlgraph.NewFieldSpec(authsession.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"AuthSession.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, authsession.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !authsession.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != authsession.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(authsession.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(authsession.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(authsession.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClientStates(); ok {\n\t\t_spec.SetField(authsession.FieldClientStates, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(authsession.FieldCreatedAt, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastActivity(); ok {\n\t\t_spec.SetField(authsession.FieldLastActivity, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.IPAddress(); ok {\n\t\t_spec.SetField(authsession.FieldIPAddress, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.UserAgent(); ok {\n\t\t_spec.SetField(authsession.FieldUserAgent, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.AbsoluteExpiry(); ok {\n\t\t_spec.SetField(authsession.FieldAbsoluteExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.IdleExpiry(); ok {\n\t\t_spec.SetField(authsession.FieldIdleExpiry, field.TypeTime, value)\n\t}\n\t_node = &AuthSession{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{authsession.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/client.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log\"\n\t\"reflect\"\n\n\t\"github.com/dexidp/dex/storage/ent/db/migrate\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n)\n\n// Client is the client that holds all ent builders.\ntype Client struct {\n\tconfig\n\t// Schema is the client for creating, migrating and dropping schema.\n\tSchema *migrate.Schema\n\t// AuthCode is the client for interacting with the AuthCode builders.\n\tAuthCode *AuthCodeClient\n\t// AuthRequest is the client for interacting with the AuthRequest builders.\n\tAuthRequest *AuthRequestClient\n\t// AuthSession is the client for interacting with the AuthSession builders.\n\tAuthSession *AuthSessionClient\n\t// Connector is the client for interacting with the Connector builders.\n\tConnector *ConnectorClient\n\t// DeviceRequest is the client for interacting with the DeviceRequest builders.\n\tDeviceRequest *DeviceRequestClient\n\t// DeviceToken is the client for interacting with the DeviceToken builders.\n\tDeviceToken *DeviceTokenClient\n\t// Keys is the client for interacting with the Keys builders.\n\tKeys *KeysClient\n\t// OAuth2Client is the client for interacting with the OAuth2Client builders.\n\tOAuth2Client *OAuth2ClientClient\n\t// OfflineSession is the client for interacting with the OfflineSession builders.\n\tOfflineSession *OfflineSessionClient\n\t// Password is the client for interacting with the Password builders.\n\tPassword *PasswordClient\n\t// RefreshToken is the client for interacting with the RefreshToken builders.\n\tRefreshToken *RefreshTokenClient\n\t// UserIdentity is the client for interacting with the UserIdentity builders.\n\tUserIdentity *UserIdentityClient\n}\n\n// NewClient creates a new client configured with the given options.\nfunc NewClient(opts ...Option) *Client {\n\tclient := &Client{config: newConfig(opts...)}\n\tclient.init()\n\treturn client\n}\n\nfunc (c *Client) init() {\n\tc.Schema = migrate.NewSchema(c.driver)\n\tc.AuthCode = NewAuthCodeClient(c.config)\n\tc.AuthRequest = NewAuthRequestClient(c.config)\n\tc.AuthSession = NewAuthSessionClient(c.config)\n\tc.Connector = NewConnectorClient(c.config)\n\tc.DeviceRequest = NewDeviceRequestClient(c.config)\n\tc.DeviceToken = NewDeviceTokenClient(c.config)\n\tc.Keys = NewKeysClient(c.config)\n\tc.OAuth2Client = NewOAuth2ClientClient(c.config)\n\tc.OfflineSession = NewOfflineSessionClient(c.config)\n\tc.Password = NewPasswordClient(c.config)\n\tc.RefreshToken = NewRefreshTokenClient(c.config)\n\tc.UserIdentity = NewUserIdentityClient(c.config)\n}\n\ntype (\n\t// config is the configuration for the client and its builder.\n\tconfig struct {\n\t\t// driver used for executing database requests.\n\t\tdriver dialect.Driver\n\t\t// debug enable a debug logging.\n\t\tdebug bool\n\t\t// log used for logging on debug mode.\n\t\tlog func(...any)\n\t\t// hooks to execute on mutations.\n\t\thooks *hooks\n\t\t// interceptors to execute on queries.\n\t\tinters *inters\n\t}\n\t// Option function to configure the client.\n\tOption func(*config)\n)\n\n// newConfig creates a new config for the client.\nfunc newConfig(opts ...Option) config {\n\tcfg := config{log: log.Println, hooks: &hooks{}, inters: &inters{}}\n\tcfg.options(opts...)\n\treturn cfg\n}\n\n// options applies the options on the config object.\nfunc (c *config) options(opts ...Option) {\n\tfor _, opt := range opts {\n\t\topt(c)\n\t}\n\tif c.debug {\n\t\tc.driver = dialect.Debug(c.driver, c.log)\n\t}\n}\n\n// Debug enables debug logging on the ent.Driver.\nfunc Debug() Option {\n\treturn func(c *config) {\n\t\tc.debug = true\n\t}\n}\n\n// Log sets the logging function for debug mode.\nfunc Log(fn func(...any)) Option {\n\treturn func(c *config) {\n\t\tc.log = fn\n\t}\n}\n\n// Driver configures the client driver.\nfunc Driver(driver dialect.Driver) Option {\n\treturn func(c *config) {\n\t\tc.driver = driver\n\t}\n}\n\n// Open opens a database/sql.DB specified by the driver name and\n// the data source name, and returns a new client attached to it.\n// Optional parameters can be added for configuring the client.\nfunc Open(driverName, dataSourceName string, options ...Option) (*Client, error) {\n\tswitch driverName {\n\tcase dialect.MySQL, dialect.Postgres, dialect.SQLite:\n\t\tdrv, err := sql.Open(driverName, dataSourceName)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn NewClient(append(options, Driver(drv))...), nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported driver: %q\", driverName)\n\t}\n}\n\n// ErrTxStarted is returned when trying to start a new transaction from a transactional client.\nvar ErrTxStarted = errors.New(\"db: cannot start a transaction within a transaction\")\n\n// Tx returns a new transactional client. The provided context\n// is used until the transaction is committed or rolled back.\nfunc (c *Client) Tx(ctx context.Context) (*Tx, error) {\n\tif _, ok := c.driver.(*txDriver); ok {\n\t\treturn nil, ErrTxStarted\n\t}\n\ttx, err := newTx(ctx, c.driver)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"db: starting a transaction: %w\", err)\n\t}\n\tcfg := c.config\n\tcfg.driver = tx\n\treturn &Tx{\n\t\tctx:            ctx,\n\t\tconfig:         cfg,\n\t\tAuthCode:       NewAuthCodeClient(cfg),\n\t\tAuthRequest:    NewAuthRequestClient(cfg),\n\t\tAuthSession:    NewAuthSessionClient(cfg),\n\t\tConnector:      NewConnectorClient(cfg),\n\t\tDeviceRequest:  NewDeviceRequestClient(cfg),\n\t\tDeviceToken:    NewDeviceTokenClient(cfg),\n\t\tKeys:           NewKeysClient(cfg),\n\t\tOAuth2Client:   NewOAuth2ClientClient(cfg),\n\t\tOfflineSession: NewOfflineSessionClient(cfg),\n\t\tPassword:       NewPasswordClient(cfg),\n\t\tRefreshToken:   NewRefreshTokenClient(cfg),\n\t\tUserIdentity:   NewUserIdentityClient(cfg),\n\t}, nil\n}\n\n// BeginTx returns a transactional client with specified options.\nfunc (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) {\n\tif _, ok := c.driver.(*txDriver); ok {\n\t\treturn nil, errors.New(\"ent: cannot start a transaction within a transaction\")\n\t}\n\ttx, err := c.driver.(interface {\n\t\tBeginTx(context.Context, *sql.TxOptions) (dialect.Tx, error)\n\t}).BeginTx(ctx, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"ent: starting a transaction: %w\", err)\n\t}\n\tcfg := c.config\n\tcfg.driver = &txDriver{tx: tx, drv: c.driver}\n\treturn &Tx{\n\t\tctx:            ctx,\n\t\tconfig:         cfg,\n\t\tAuthCode:       NewAuthCodeClient(cfg),\n\t\tAuthRequest:    NewAuthRequestClient(cfg),\n\t\tAuthSession:    NewAuthSessionClient(cfg),\n\t\tConnector:      NewConnectorClient(cfg),\n\t\tDeviceRequest:  NewDeviceRequestClient(cfg),\n\t\tDeviceToken:    NewDeviceTokenClient(cfg),\n\t\tKeys:           NewKeysClient(cfg),\n\t\tOAuth2Client:   NewOAuth2ClientClient(cfg),\n\t\tOfflineSession: NewOfflineSessionClient(cfg),\n\t\tPassword:       NewPasswordClient(cfg),\n\t\tRefreshToken:   NewRefreshTokenClient(cfg),\n\t\tUserIdentity:   NewUserIdentityClient(cfg),\n\t}, nil\n}\n\n// Debug returns a new debug-client. It's used to get verbose logging on specific operations.\n//\n//\tclient.Debug().\n//\t\tAuthCode.\n//\t\tQuery().\n//\t\tCount(ctx)\nfunc (c *Client) Debug() *Client {\n\tif c.debug {\n\t\treturn c\n\t}\n\tcfg := c.config\n\tcfg.driver = dialect.Debug(c.driver, c.log)\n\tclient := &Client{config: cfg}\n\tclient.init()\n\treturn client\n}\n\n// Close closes the database connection and prevents new queries from starting.\nfunc (c *Client) Close() error {\n\treturn c.driver.Close()\n}\n\n// Use adds the mutation hooks to all the entity clients.\n// In order to add hooks to a specific client, call: `client.Node.Use(...)`.\nfunc (c *Client) Use(hooks ...Hook) {\n\tfor _, n := range []interface{ Use(...Hook) }{\n\t\tc.AuthCode, c.AuthRequest, c.AuthSession, c.Connector, c.DeviceRequest,\n\t\tc.DeviceToken, c.Keys, c.OAuth2Client, c.OfflineSession, c.Password,\n\t\tc.RefreshToken, c.UserIdentity,\n\t} {\n\t\tn.Use(hooks...)\n\t}\n}\n\n// Intercept adds the query interceptors to all the entity clients.\n// In order to add interceptors to a specific client, call: `client.Node.Intercept(...)`.\nfunc (c *Client) Intercept(interceptors ...Interceptor) {\n\tfor _, n := range []interface{ Intercept(...Interceptor) }{\n\t\tc.AuthCode, c.AuthRequest, c.AuthSession, c.Connector, c.DeviceRequest,\n\t\tc.DeviceToken, c.Keys, c.OAuth2Client, c.OfflineSession, c.Password,\n\t\tc.RefreshToken, c.UserIdentity,\n\t} {\n\t\tn.Intercept(interceptors...)\n\t}\n}\n\n// Mutate implements the ent.Mutator interface.\nfunc (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) {\n\tswitch m := m.(type) {\n\tcase *AuthCodeMutation:\n\t\treturn c.AuthCode.mutate(ctx, m)\n\tcase *AuthRequestMutation:\n\t\treturn c.AuthRequest.mutate(ctx, m)\n\tcase *AuthSessionMutation:\n\t\treturn c.AuthSession.mutate(ctx, m)\n\tcase *ConnectorMutation:\n\t\treturn c.Connector.mutate(ctx, m)\n\tcase *DeviceRequestMutation:\n\t\treturn c.DeviceRequest.mutate(ctx, m)\n\tcase *DeviceTokenMutation:\n\t\treturn c.DeviceToken.mutate(ctx, m)\n\tcase *KeysMutation:\n\t\treturn c.Keys.mutate(ctx, m)\n\tcase *OAuth2ClientMutation:\n\t\treturn c.OAuth2Client.mutate(ctx, m)\n\tcase *OfflineSessionMutation:\n\t\treturn c.OfflineSession.mutate(ctx, m)\n\tcase *PasswordMutation:\n\t\treturn c.Password.mutate(ctx, m)\n\tcase *RefreshTokenMutation:\n\t\treturn c.RefreshToken.mutate(ctx, m)\n\tcase *UserIdentityMutation:\n\t\treturn c.UserIdentity.mutate(ctx, m)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown mutation type %T\", m)\n\t}\n}\n\n// AuthCodeClient is a client for the AuthCode schema.\ntype AuthCodeClient struct {\n\tconfig\n}\n\n// NewAuthCodeClient returns a client for the AuthCode from the given config.\nfunc NewAuthCodeClient(c config) *AuthCodeClient {\n\treturn &AuthCodeClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `authcode.Hooks(f(g(h())))`.\nfunc (c *AuthCodeClient) Use(hooks ...Hook) {\n\tc.hooks.AuthCode = append(c.hooks.AuthCode, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `authcode.Intercept(f(g(h())))`.\nfunc (c *AuthCodeClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.AuthCode = append(c.inters.AuthCode, interceptors...)\n}\n\n// Create returns a builder for creating a AuthCode entity.\nfunc (c *AuthCodeClient) Create() *AuthCodeCreate {\n\tmutation := newAuthCodeMutation(c.config, OpCreate)\n\treturn &AuthCodeCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of AuthCode entities.\nfunc (c *AuthCodeClient) CreateBulk(builders ...*AuthCodeCreate) *AuthCodeCreateBulk {\n\treturn &AuthCodeCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *AuthCodeClient) MapCreateBulk(slice any, setFunc func(*AuthCodeCreate, int)) *AuthCodeCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &AuthCodeCreateBulk{err: fmt.Errorf(\"calling to AuthCodeClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*AuthCodeCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &AuthCodeCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for AuthCode.\nfunc (c *AuthCodeClient) Update() *AuthCodeUpdate {\n\tmutation := newAuthCodeMutation(c.config, OpUpdate)\n\treturn &AuthCodeUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *AuthCodeClient) UpdateOne(_m *AuthCode) *AuthCodeUpdateOne {\n\tmutation := newAuthCodeMutation(c.config, OpUpdateOne, withAuthCode(_m))\n\treturn &AuthCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *AuthCodeClient) UpdateOneID(id string) *AuthCodeUpdateOne {\n\tmutation := newAuthCodeMutation(c.config, OpUpdateOne, withAuthCodeID(id))\n\treturn &AuthCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for AuthCode.\nfunc (c *AuthCodeClient) Delete() *AuthCodeDelete {\n\tmutation := newAuthCodeMutation(c.config, OpDelete)\n\treturn &AuthCodeDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *AuthCodeClient) DeleteOne(_m *AuthCode) *AuthCodeDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *AuthCodeClient) DeleteOneID(id string) *AuthCodeDeleteOne {\n\tbuilder := c.Delete().Where(authcode.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &AuthCodeDeleteOne{builder}\n}\n\n// Query returns a query builder for AuthCode.\nfunc (c *AuthCodeClient) Query() *AuthCodeQuery {\n\treturn &AuthCodeQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeAuthCode},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a AuthCode entity by its id.\nfunc (c *AuthCodeClient) Get(ctx context.Context, id string) (*AuthCode, error) {\n\treturn c.Query().Where(authcode.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *AuthCodeClient) GetX(ctx context.Context, id string) *AuthCode {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *AuthCodeClient) Hooks() []Hook {\n\treturn c.hooks.AuthCode\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *AuthCodeClient) Interceptors() []Interceptor {\n\treturn c.inters.AuthCode\n}\n\nfunc (c *AuthCodeClient) mutate(ctx context.Context, m *AuthCodeMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&AuthCodeCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&AuthCodeUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&AuthCodeUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&AuthCodeDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown AuthCode mutation op: %q\", m.Op())\n\t}\n}\n\n// AuthRequestClient is a client for the AuthRequest schema.\ntype AuthRequestClient struct {\n\tconfig\n}\n\n// NewAuthRequestClient returns a client for the AuthRequest from the given config.\nfunc NewAuthRequestClient(c config) *AuthRequestClient {\n\treturn &AuthRequestClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `authrequest.Hooks(f(g(h())))`.\nfunc (c *AuthRequestClient) Use(hooks ...Hook) {\n\tc.hooks.AuthRequest = append(c.hooks.AuthRequest, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `authrequest.Intercept(f(g(h())))`.\nfunc (c *AuthRequestClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.AuthRequest = append(c.inters.AuthRequest, interceptors...)\n}\n\n// Create returns a builder for creating a AuthRequest entity.\nfunc (c *AuthRequestClient) Create() *AuthRequestCreate {\n\tmutation := newAuthRequestMutation(c.config, OpCreate)\n\treturn &AuthRequestCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of AuthRequest entities.\nfunc (c *AuthRequestClient) CreateBulk(builders ...*AuthRequestCreate) *AuthRequestCreateBulk {\n\treturn &AuthRequestCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *AuthRequestClient) MapCreateBulk(slice any, setFunc func(*AuthRequestCreate, int)) *AuthRequestCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &AuthRequestCreateBulk{err: fmt.Errorf(\"calling to AuthRequestClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*AuthRequestCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &AuthRequestCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for AuthRequest.\nfunc (c *AuthRequestClient) Update() *AuthRequestUpdate {\n\tmutation := newAuthRequestMutation(c.config, OpUpdate)\n\treturn &AuthRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *AuthRequestClient) UpdateOne(_m *AuthRequest) *AuthRequestUpdateOne {\n\tmutation := newAuthRequestMutation(c.config, OpUpdateOne, withAuthRequest(_m))\n\treturn &AuthRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *AuthRequestClient) UpdateOneID(id string) *AuthRequestUpdateOne {\n\tmutation := newAuthRequestMutation(c.config, OpUpdateOne, withAuthRequestID(id))\n\treturn &AuthRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for AuthRequest.\nfunc (c *AuthRequestClient) Delete() *AuthRequestDelete {\n\tmutation := newAuthRequestMutation(c.config, OpDelete)\n\treturn &AuthRequestDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *AuthRequestClient) DeleteOne(_m *AuthRequest) *AuthRequestDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *AuthRequestClient) DeleteOneID(id string) *AuthRequestDeleteOne {\n\tbuilder := c.Delete().Where(authrequest.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &AuthRequestDeleteOne{builder}\n}\n\n// Query returns a query builder for AuthRequest.\nfunc (c *AuthRequestClient) Query() *AuthRequestQuery {\n\treturn &AuthRequestQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeAuthRequest},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a AuthRequest entity by its id.\nfunc (c *AuthRequestClient) Get(ctx context.Context, id string) (*AuthRequest, error) {\n\treturn c.Query().Where(authrequest.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *AuthRequestClient) GetX(ctx context.Context, id string) *AuthRequest {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *AuthRequestClient) Hooks() []Hook {\n\treturn c.hooks.AuthRequest\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *AuthRequestClient) Interceptors() []Interceptor {\n\treturn c.inters.AuthRequest\n}\n\nfunc (c *AuthRequestClient) mutate(ctx context.Context, m *AuthRequestMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&AuthRequestCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&AuthRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&AuthRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&AuthRequestDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown AuthRequest mutation op: %q\", m.Op())\n\t}\n}\n\n// AuthSessionClient is a client for the AuthSession schema.\ntype AuthSessionClient struct {\n\tconfig\n}\n\n// NewAuthSessionClient returns a client for the AuthSession from the given config.\nfunc NewAuthSessionClient(c config) *AuthSessionClient {\n\treturn &AuthSessionClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `authsession.Hooks(f(g(h())))`.\nfunc (c *AuthSessionClient) Use(hooks ...Hook) {\n\tc.hooks.AuthSession = append(c.hooks.AuthSession, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `authsession.Intercept(f(g(h())))`.\nfunc (c *AuthSessionClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.AuthSession = append(c.inters.AuthSession, interceptors...)\n}\n\n// Create returns a builder for creating a AuthSession entity.\nfunc (c *AuthSessionClient) Create() *AuthSessionCreate {\n\tmutation := newAuthSessionMutation(c.config, OpCreate)\n\treturn &AuthSessionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of AuthSession entities.\nfunc (c *AuthSessionClient) CreateBulk(builders ...*AuthSessionCreate) *AuthSessionCreateBulk {\n\treturn &AuthSessionCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *AuthSessionClient) MapCreateBulk(slice any, setFunc func(*AuthSessionCreate, int)) *AuthSessionCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &AuthSessionCreateBulk{err: fmt.Errorf(\"calling to AuthSessionClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*AuthSessionCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &AuthSessionCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for AuthSession.\nfunc (c *AuthSessionClient) Update() *AuthSessionUpdate {\n\tmutation := newAuthSessionMutation(c.config, OpUpdate)\n\treturn &AuthSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *AuthSessionClient) UpdateOne(_m *AuthSession) *AuthSessionUpdateOne {\n\tmutation := newAuthSessionMutation(c.config, OpUpdateOne, withAuthSession(_m))\n\treturn &AuthSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *AuthSessionClient) UpdateOneID(id string) *AuthSessionUpdateOne {\n\tmutation := newAuthSessionMutation(c.config, OpUpdateOne, withAuthSessionID(id))\n\treturn &AuthSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for AuthSession.\nfunc (c *AuthSessionClient) Delete() *AuthSessionDelete {\n\tmutation := newAuthSessionMutation(c.config, OpDelete)\n\treturn &AuthSessionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *AuthSessionClient) DeleteOne(_m *AuthSession) *AuthSessionDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *AuthSessionClient) DeleteOneID(id string) *AuthSessionDeleteOne {\n\tbuilder := c.Delete().Where(authsession.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &AuthSessionDeleteOne{builder}\n}\n\n// Query returns a query builder for AuthSession.\nfunc (c *AuthSessionClient) Query() *AuthSessionQuery {\n\treturn &AuthSessionQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeAuthSession},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a AuthSession entity by its id.\nfunc (c *AuthSessionClient) Get(ctx context.Context, id string) (*AuthSession, error) {\n\treturn c.Query().Where(authsession.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *AuthSessionClient) GetX(ctx context.Context, id string) *AuthSession {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *AuthSessionClient) Hooks() []Hook {\n\treturn c.hooks.AuthSession\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *AuthSessionClient) Interceptors() []Interceptor {\n\treturn c.inters.AuthSession\n}\n\nfunc (c *AuthSessionClient) mutate(ctx context.Context, m *AuthSessionMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&AuthSessionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&AuthSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&AuthSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&AuthSessionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown AuthSession mutation op: %q\", m.Op())\n\t}\n}\n\n// ConnectorClient is a client for the Connector schema.\ntype ConnectorClient struct {\n\tconfig\n}\n\n// NewConnectorClient returns a client for the Connector from the given config.\nfunc NewConnectorClient(c config) *ConnectorClient {\n\treturn &ConnectorClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `connector.Hooks(f(g(h())))`.\nfunc (c *ConnectorClient) Use(hooks ...Hook) {\n\tc.hooks.Connector = append(c.hooks.Connector, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `connector.Intercept(f(g(h())))`.\nfunc (c *ConnectorClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.Connector = append(c.inters.Connector, interceptors...)\n}\n\n// Create returns a builder for creating a Connector entity.\nfunc (c *ConnectorClient) Create() *ConnectorCreate {\n\tmutation := newConnectorMutation(c.config, OpCreate)\n\treturn &ConnectorCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of Connector entities.\nfunc (c *ConnectorClient) CreateBulk(builders ...*ConnectorCreate) *ConnectorCreateBulk {\n\treturn &ConnectorCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *ConnectorClient) MapCreateBulk(slice any, setFunc func(*ConnectorCreate, int)) *ConnectorCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &ConnectorCreateBulk{err: fmt.Errorf(\"calling to ConnectorClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*ConnectorCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &ConnectorCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for Connector.\nfunc (c *ConnectorClient) Update() *ConnectorUpdate {\n\tmutation := newConnectorMutation(c.config, OpUpdate)\n\treturn &ConnectorUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *ConnectorClient) UpdateOne(_m *Connector) *ConnectorUpdateOne {\n\tmutation := newConnectorMutation(c.config, OpUpdateOne, withConnector(_m))\n\treturn &ConnectorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *ConnectorClient) UpdateOneID(id string) *ConnectorUpdateOne {\n\tmutation := newConnectorMutation(c.config, OpUpdateOne, withConnectorID(id))\n\treturn &ConnectorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for Connector.\nfunc (c *ConnectorClient) Delete() *ConnectorDelete {\n\tmutation := newConnectorMutation(c.config, OpDelete)\n\treturn &ConnectorDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *ConnectorClient) DeleteOne(_m *Connector) *ConnectorDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *ConnectorClient) DeleteOneID(id string) *ConnectorDeleteOne {\n\tbuilder := c.Delete().Where(connector.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &ConnectorDeleteOne{builder}\n}\n\n// Query returns a query builder for Connector.\nfunc (c *ConnectorClient) Query() *ConnectorQuery {\n\treturn &ConnectorQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeConnector},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a Connector entity by its id.\nfunc (c *ConnectorClient) Get(ctx context.Context, id string) (*Connector, error) {\n\treturn c.Query().Where(connector.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *ConnectorClient) GetX(ctx context.Context, id string) *Connector {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *ConnectorClient) Hooks() []Hook {\n\treturn c.hooks.Connector\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *ConnectorClient) Interceptors() []Interceptor {\n\treturn c.inters.Connector\n}\n\nfunc (c *ConnectorClient) mutate(ctx context.Context, m *ConnectorMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&ConnectorCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&ConnectorUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&ConnectorUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&ConnectorDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown Connector mutation op: %q\", m.Op())\n\t}\n}\n\n// DeviceRequestClient is a client for the DeviceRequest schema.\ntype DeviceRequestClient struct {\n\tconfig\n}\n\n// NewDeviceRequestClient returns a client for the DeviceRequest from the given config.\nfunc NewDeviceRequestClient(c config) *DeviceRequestClient {\n\treturn &DeviceRequestClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `devicerequest.Hooks(f(g(h())))`.\nfunc (c *DeviceRequestClient) Use(hooks ...Hook) {\n\tc.hooks.DeviceRequest = append(c.hooks.DeviceRequest, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `devicerequest.Intercept(f(g(h())))`.\nfunc (c *DeviceRequestClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.DeviceRequest = append(c.inters.DeviceRequest, interceptors...)\n}\n\n// Create returns a builder for creating a DeviceRequest entity.\nfunc (c *DeviceRequestClient) Create() *DeviceRequestCreate {\n\tmutation := newDeviceRequestMutation(c.config, OpCreate)\n\treturn &DeviceRequestCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of DeviceRequest entities.\nfunc (c *DeviceRequestClient) CreateBulk(builders ...*DeviceRequestCreate) *DeviceRequestCreateBulk {\n\treturn &DeviceRequestCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *DeviceRequestClient) MapCreateBulk(slice any, setFunc func(*DeviceRequestCreate, int)) *DeviceRequestCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &DeviceRequestCreateBulk{err: fmt.Errorf(\"calling to DeviceRequestClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*DeviceRequestCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &DeviceRequestCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for DeviceRequest.\nfunc (c *DeviceRequestClient) Update() *DeviceRequestUpdate {\n\tmutation := newDeviceRequestMutation(c.config, OpUpdate)\n\treturn &DeviceRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *DeviceRequestClient) UpdateOne(_m *DeviceRequest) *DeviceRequestUpdateOne {\n\tmutation := newDeviceRequestMutation(c.config, OpUpdateOne, withDeviceRequest(_m))\n\treturn &DeviceRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *DeviceRequestClient) UpdateOneID(id int) *DeviceRequestUpdateOne {\n\tmutation := newDeviceRequestMutation(c.config, OpUpdateOne, withDeviceRequestID(id))\n\treturn &DeviceRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for DeviceRequest.\nfunc (c *DeviceRequestClient) Delete() *DeviceRequestDelete {\n\tmutation := newDeviceRequestMutation(c.config, OpDelete)\n\treturn &DeviceRequestDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *DeviceRequestClient) DeleteOne(_m *DeviceRequest) *DeviceRequestDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *DeviceRequestClient) DeleteOneID(id int) *DeviceRequestDeleteOne {\n\tbuilder := c.Delete().Where(devicerequest.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &DeviceRequestDeleteOne{builder}\n}\n\n// Query returns a query builder for DeviceRequest.\nfunc (c *DeviceRequestClient) Query() *DeviceRequestQuery {\n\treturn &DeviceRequestQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeDeviceRequest},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a DeviceRequest entity by its id.\nfunc (c *DeviceRequestClient) Get(ctx context.Context, id int) (*DeviceRequest, error) {\n\treturn c.Query().Where(devicerequest.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *DeviceRequestClient) GetX(ctx context.Context, id int) *DeviceRequest {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *DeviceRequestClient) Hooks() []Hook {\n\treturn c.hooks.DeviceRequest\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *DeviceRequestClient) Interceptors() []Interceptor {\n\treturn c.inters.DeviceRequest\n}\n\nfunc (c *DeviceRequestClient) mutate(ctx context.Context, m *DeviceRequestMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&DeviceRequestCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&DeviceRequestUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&DeviceRequestUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&DeviceRequestDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown DeviceRequest mutation op: %q\", m.Op())\n\t}\n}\n\n// DeviceTokenClient is a client for the DeviceToken schema.\ntype DeviceTokenClient struct {\n\tconfig\n}\n\n// NewDeviceTokenClient returns a client for the DeviceToken from the given config.\nfunc NewDeviceTokenClient(c config) *DeviceTokenClient {\n\treturn &DeviceTokenClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `devicetoken.Hooks(f(g(h())))`.\nfunc (c *DeviceTokenClient) Use(hooks ...Hook) {\n\tc.hooks.DeviceToken = append(c.hooks.DeviceToken, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `devicetoken.Intercept(f(g(h())))`.\nfunc (c *DeviceTokenClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.DeviceToken = append(c.inters.DeviceToken, interceptors...)\n}\n\n// Create returns a builder for creating a DeviceToken entity.\nfunc (c *DeviceTokenClient) Create() *DeviceTokenCreate {\n\tmutation := newDeviceTokenMutation(c.config, OpCreate)\n\treturn &DeviceTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of DeviceToken entities.\nfunc (c *DeviceTokenClient) CreateBulk(builders ...*DeviceTokenCreate) *DeviceTokenCreateBulk {\n\treturn &DeviceTokenCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *DeviceTokenClient) MapCreateBulk(slice any, setFunc func(*DeviceTokenCreate, int)) *DeviceTokenCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &DeviceTokenCreateBulk{err: fmt.Errorf(\"calling to DeviceTokenClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*DeviceTokenCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &DeviceTokenCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for DeviceToken.\nfunc (c *DeviceTokenClient) Update() *DeviceTokenUpdate {\n\tmutation := newDeviceTokenMutation(c.config, OpUpdate)\n\treturn &DeviceTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *DeviceTokenClient) UpdateOne(_m *DeviceToken) *DeviceTokenUpdateOne {\n\tmutation := newDeviceTokenMutation(c.config, OpUpdateOne, withDeviceToken(_m))\n\treturn &DeviceTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *DeviceTokenClient) UpdateOneID(id int) *DeviceTokenUpdateOne {\n\tmutation := newDeviceTokenMutation(c.config, OpUpdateOne, withDeviceTokenID(id))\n\treturn &DeviceTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for DeviceToken.\nfunc (c *DeviceTokenClient) Delete() *DeviceTokenDelete {\n\tmutation := newDeviceTokenMutation(c.config, OpDelete)\n\treturn &DeviceTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *DeviceTokenClient) DeleteOne(_m *DeviceToken) *DeviceTokenDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *DeviceTokenClient) DeleteOneID(id int) *DeviceTokenDeleteOne {\n\tbuilder := c.Delete().Where(devicetoken.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &DeviceTokenDeleteOne{builder}\n}\n\n// Query returns a query builder for DeviceToken.\nfunc (c *DeviceTokenClient) Query() *DeviceTokenQuery {\n\treturn &DeviceTokenQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeDeviceToken},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a DeviceToken entity by its id.\nfunc (c *DeviceTokenClient) Get(ctx context.Context, id int) (*DeviceToken, error) {\n\treturn c.Query().Where(devicetoken.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *DeviceTokenClient) GetX(ctx context.Context, id int) *DeviceToken {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *DeviceTokenClient) Hooks() []Hook {\n\treturn c.hooks.DeviceToken\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *DeviceTokenClient) Interceptors() []Interceptor {\n\treturn c.inters.DeviceToken\n}\n\nfunc (c *DeviceTokenClient) mutate(ctx context.Context, m *DeviceTokenMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&DeviceTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&DeviceTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&DeviceTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&DeviceTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown DeviceToken mutation op: %q\", m.Op())\n\t}\n}\n\n// KeysClient is a client for the Keys schema.\ntype KeysClient struct {\n\tconfig\n}\n\n// NewKeysClient returns a client for the Keys from the given config.\nfunc NewKeysClient(c config) *KeysClient {\n\treturn &KeysClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `keys.Hooks(f(g(h())))`.\nfunc (c *KeysClient) Use(hooks ...Hook) {\n\tc.hooks.Keys = append(c.hooks.Keys, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `keys.Intercept(f(g(h())))`.\nfunc (c *KeysClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.Keys = append(c.inters.Keys, interceptors...)\n}\n\n// Create returns a builder for creating a Keys entity.\nfunc (c *KeysClient) Create() *KeysCreate {\n\tmutation := newKeysMutation(c.config, OpCreate)\n\treturn &KeysCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of Keys entities.\nfunc (c *KeysClient) CreateBulk(builders ...*KeysCreate) *KeysCreateBulk {\n\treturn &KeysCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *KeysClient) MapCreateBulk(slice any, setFunc func(*KeysCreate, int)) *KeysCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &KeysCreateBulk{err: fmt.Errorf(\"calling to KeysClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*KeysCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &KeysCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for Keys.\nfunc (c *KeysClient) Update() *KeysUpdate {\n\tmutation := newKeysMutation(c.config, OpUpdate)\n\treturn &KeysUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *KeysClient) UpdateOne(_m *Keys) *KeysUpdateOne {\n\tmutation := newKeysMutation(c.config, OpUpdateOne, withKeys(_m))\n\treturn &KeysUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *KeysClient) UpdateOneID(id string) *KeysUpdateOne {\n\tmutation := newKeysMutation(c.config, OpUpdateOne, withKeysID(id))\n\treturn &KeysUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for Keys.\nfunc (c *KeysClient) Delete() *KeysDelete {\n\tmutation := newKeysMutation(c.config, OpDelete)\n\treturn &KeysDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *KeysClient) DeleteOne(_m *Keys) *KeysDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *KeysClient) DeleteOneID(id string) *KeysDeleteOne {\n\tbuilder := c.Delete().Where(keys.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &KeysDeleteOne{builder}\n}\n\n// Query returns a query builder for Keys.\nfunc (c *KeysClient) Query() *KeysQuery {\n\treturn &KeysQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeKeys},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a Keys entity by its id.\nfunc (c *KeysClient) Get(ctx context.Context, id string) (*Keys, error) {\n\treturn c.Query().Where(keys.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *KeysClient) GetX(ctx context.Context, id string) *Keys {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *KeysClient) Hooks() []Hook {\n\treturn c.hooks.Keys\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *KeysClient) Interceptors() []Interceptor {\n\treturn c.inters.Keys\n}\n\nfunc (c *KeysClient) mutate(ctx context.Context, m *KeysMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&KeysCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&KeysUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&KeysUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&KeysDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown Keys mutation op: %q\", m.Op())\n\t}\n}\n\n// OAuth2ClientClient is a client for the OAuth2Client schema.\ntype OAuth2ClientClient struct {\n\tconfig\n}\n\n// NewOAuth2ClientClient returns a client for the OAuth2Client from the given config.\nfunc NewOAuth2ClientClient(c config) *OAuth2ClientClient {\n\treturn &OAuth2ClientClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `oauth2client.Hooks(f(g(h())))`.\nfunc (c *OAuth2ClientClient) Use(hooks ...Hook) {\n\tc.hooks.OAuth2Client = append(c.hooks.OAuth2Client, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `oauth2client.Intercept(f(g(h())))`.\nfunc (c *OAuth2ClientClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.OAuth2Client = append(c.inters.OAuth2Client, interceptors...)\n}\n\n// Create returns a builder for creating a OAuth2Client entity.\nfunc (c *OAuth2ClientClient) Create() *OAuth2ClientCreate {\n\tmutation := newOAuth2ClientMutation(c.config, OpCreate)\n\treturn &OAuth2ClientCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of OAuth2Client entities.\nfunc (c *OAuth2ClientClient) CreateBulk(builders ...*OAuth2ClientCreate) *OAuth2ClientCreateBulk {\n\treturn &OAuth2ClientCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *OAuth2ClientClient) MapCreateBulk(slice any, setFunc func(*OAuth2ClientCreate, int)) *OAuth2ClientCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &OAuth2ClientCreateBulk{err: fmt.Errorf(\"calling to OAuth2ClientClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*OAuth2ClientCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &OAuth2ClientCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for OAuth2Client.\nfunc (c *OAuth2ClientClient) Update() *OAuth2ClientUpdate {\n\tmutation := newOAuth2ClientMutation(c.config, OpUpdate)\n\treturn &OAuth2ClientUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *OAuth2ClientClient) UpdateOne(_m *OAuth2Client) *OAuth2ClientUpdateOne {\n\tmutation := newOAuth2ClientMutation(c.config, OpUpdateOne, withOAuth2Client(_m))\n\treturn &OAuth2ClientUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *OAuth2ClientClient) UpdateOneID(id string) *OAuth2ClientUpdateOne {\n\tmutation := newOAuth2ClientMutation(c.config, OpUpdateOne, withOAuth2ClientID(id))\n\treturn &OAuth2ClientUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for OAuth2Client.\nfunc (c *OAuth2ClientClient) Delete() *OAuth2ClientDelete {\n\tmutation := newOAuth2ClientMutation(c.config, OpDelete)\n\treturn &OAuth2ClientDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *OAuth2ClientClient) DeleteOne(_m *OAuth2Client) *OAuth2ClientDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *OAuth2ClientClient) DeleteOneID(id string) *OAuth2ClientDeleteOne {\n\tbuilder := c.Delete().Where(oauth2client.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &OAuth2ClientDeleteOne{builder}\n}\n\n// Query returns a query builder for OAuth2Client.\nfunc (c *OAuth2ClientClient) Query() *OAuth2ClientQuery {\n\treturn &OAuth2ClientQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeOAuth2Client},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a OAuth2Client entity by its id.\nfunc (c *OAuth2ClientClient) Get(ctx context.Context, id string) (*OAuth2Client, error) {\n\treturn c.Query().Where(oauth2client.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *OAuth2ClientClient) GetX(ctx context.Context, id string) *OAuth2Client {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *OAuth2ClientClient) Hooks() []Hook {\n\treturn c.hooks.OAuth2Client\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *OAuth2ClientClient) Interceptors() []Interceptor {\n\treturn c.inters.OAuth2Client\n}\n\nfunc (c *OAuth2ClientClient) mutate(ctx context.Context, m *OAuth2ClientMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&OAuth2ClientCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&OAuth2ClientUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&OAuth2ClientUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&OAuth2ClientDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown OAuth2Client mutation op: %q\", m.Op())\n\t}\n}\n\n// OfflineSessionClient is a client for the OfflineSession schema.\ntype OfflineSessionClient struct {\n\tconfig\n}\n\n// NewOfflineSessionClient returns a client for the OfflineSession from the given config.\nfunc NewOfflineSessionClient(c config) *OfflineSessionClient {\n\treturn &OfflineSessionClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `offlinesession.Hooks(f(g(h())))`.\nfunc (c *OfflineSessionClient) Use(hooks ...Hook) {\n\tc.hooks.OfflineSession = append(c.hooks.OfflineSession, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `offlinesession.Intercept(f(g(h())))`.\nfunc (c *OfflineSessionClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.OfflineSession = append(c.inters.OfflineSession, interceptors...)\n}\n\n// Create returns a builder for creating a OfflineSession entity.\nfunc (c *OfflineSessionClient) Create() *OfflineSessionCreate {\n\tmutation := newOfflineSessionMutation(c.config, OpCreate)\n\treturn &OfflineSessionCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of OfflineSession entities.\nfunc (c *OfflineSessionClient) CreateBulk(builders ...*OfflineSessionCreate) *OfflineSessionCreateBulk {\n\treturn &OfflineSessionCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *OfflineSessionClient) MapCreateBulk(slice any, setFunc func(*OfflineSessionCreate, int)) *OfflineSessionCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &OfflineSessionCreateBulk{err: fmt.Errorf(\"calling to OfflineSessionClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*OfflineSessionCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &OfflineSessionCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for OfflineSession.\nfunc (c *OfflineSessionClient) Update() *OfflineSessionUpdate {\n\tmutation := newOfflineSessionMutation(c.config, OpUpdate)\n\treturn &OfflineSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *OfflineSessionClient) UpdateOne(_m *OfflineSession) *OfflineSessionUpdateOne {\n\tmutation := newOfflineSessionMutation(c.config, OpUpdateOne, withOfflineSession(_m))\n\treturn &OfflineSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *OfflineSessionClient) UpdateOneID(id string) *OfflineSessionUpdateOne {\n\tmutation := newOfflineSessionMutation(c.config, OpUpdateOne, withOfflineSessionID(id))\n\treturn &OfflineSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for OfflineSession.\nfunc (c *OfflineSessionClient) Delete() *OfflineSessionDelete {\n\tmutation := newOfflineSessionMutation(c.config, OpDelete)\n\treturn &OfflineSessionDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *OfflineSessionClient) DeleteOne(_m *OfflineSession) *OfflineSessionDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *OfflineSessionClient) DeleteOneID(id string) *OfflineSessionDeleteOne {\n\tbuilder := c.Delete().Where(offlinesession.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &OfflineSessionDeleteOne{builder}\n}\n\n// Query returns a query builder for OfflineSession.\nfunc (c *OfflineSessionClient) Query() *OfflineSessionQuery {\n\treturn &OfflineSessionQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeOfflineSession},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a OfflineSession entity by its id.\nfunc (c *OfflineSessionClient) Get(ctx context.Context, id string) (*OfflineSession, error) {\n\treturn c.Query().Where(offlinesession.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *OfflineSessionClient) GetX(ctx context.Context, id string) *OfflineSession {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *OfflineSessionClient) Hooks() []Hook {\n\treturn c.hooks.OfflineSession\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *OfflineSessionClient) Interceptors() []Interceptor {\n\treturn c.inters.OfflineSession\n}\n\nfunc (c *OfflineSessionClient) mutate(ctx context.Context, m *OfflineSessionMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&OfflineSessionCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&OfflineSessionUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&OfflineSessionUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&OfflineSessionDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown OfflineSession mutation op: %q\", m.Op())\n\t}\n}\n\n// PasswordClient is a client for the Password schema.\ntype PasswordClient struct {\n\tconfig\n}\n\n// NewPasswordClient returns a client for the Password from the given config.\nfunc NewPasswordClient(c config) *PasswordClient {\n\treturn &PasswordClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `password.Hooks(f(g(h())))`.\nfunc (c *PasswordClient) Use(hooks ...Hook) {\n\tc.hooks.Password = append(c.hooks.Password, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `password.Intercept(f(g(h())))`.\nfunc (c *PasswordClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.Password = append(c.inters.Password, interceptors...)\n}\n\n// Create returns a builder for creating a Password entity.\nfunc (c *PasswordClient) Create() *PasswordCreate {\n\tmutation := newPasswordMutation(c.config, OpCreate)\n\treturn &PasswordCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of Password entities.\nfunc (c *PasswordClient) CreateBulk(builders ...*PasswordCreate) *PasswordCreateBulk {\n\treturn &PasswordCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *PasswordClient) MapCreateBulk(slice any, setFunc func(*PasswordCreate, int)) *PasswordCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &PasswordCreateBulk{err: fmt.Errorf(\"calling to PasswordClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*PasswordCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &PasswordCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for Password.\nfunc (c *PasswordClient) Update() *PasswordUpdate {\n\tmutation := newPasswordMutation(c.config, OpUpdate)\n\treturn &PasswordUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *PasswordClient) UpdateOne(_m *Password) *PasswordUpdateOne {\n\tmutation := newPasswordMutation(c.config, OpUpdateOne, withPassword(_m))\n\treturn &PasswordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *PasswordClient) UpdateOneID(id int) *PasswordUpdateOne {\n\tmutation := newPasswordMutation(c.config, OpUpdateOne, withPasswordID(id))\n\treturn &PasswordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for Password.\nfunc (c *PasswordClient) Delete() *PasswordDelete {\n\tmutation := newPasswordMutation(c.config, OpDelete)\n\treturn &PasswordDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *PasswordClient) DeleteOne(_m *Password) *PasswordDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *PasswordClient) DeleteOneID(id int) *PasswordDeleteOne {\n\tbuilder := c.Delete().Where(password.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &PasswordDeleteOne{builder}\n}\n\n// Query returns a query builder for Password.\nfunc (c *PasswordClient) Query() *PasswordQuery {\n\treturn &PasswordQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypePassword},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a Password entity by its id.\nfunc (c *PasswordClient) Get(ctx context.Context, id int) (*Password, error) {\n\treturn c.Query().Where(password.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *PasswordClient) GetX(ctx context.Context, id int) *Password {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *PasswordClient) Hooks() []Hook {\n\treturn c.hooks.Password\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *PasswordClient) Interceptors() []Interceptor {\n\treturn c.inters.Password\n}\n\nfunc (c *PasswordClient) mutate(ctx context.Context, m *PasswordMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&PasswordCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&PasswordUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&PasswordUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&PasswordDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown Password mutation op: %q\", m.Op())\n\t}\n}\n\n// RefreshTokenClient is a client for the RefreshToken schema.\ntype RefreshTokenClient struct {\n\tconfig\n}\n\n// NewRefreshTokenClient returns a client for the RefreshToken from the given config.\nfunc NewRefreshTokenClient(c config) *RefreshTokenClient {\n\treturn &RefreshTokenClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `refreshtoken.Hooks(f(g(h())))`.\nfunc (c *RefreshTokenClient) Use(hooks ...Hook) {\n\tc.hooks.RefreshToken = append(c.hooks.RefreshToken, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `refreshtoken.Intercept(f(g(h())))`.\nfunc (c *RefreshTokenClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.RefreshToken = append(c.inters.RefreshToken, interceptors...)\n}\n\n// Create returns a builder for creating a RefreshToken entity.\nfunc (c *RefreshTokenClient) Create() *RefreshTokenCreate {\n\tmutation := newRefreshTokenMutation(c.config, OpCreate)\n\treturn &RefreshTokenCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of RefreshToken entities.\nfunc (c *RefreshTokenClient) CreateBulk(builders ...*RefreshTokenCreate) *RefreshTokenCreateBulk {\n\treturn &RefreshTokenCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *RefreshTokenClient) MapCreateBulk(slice any, setFunc func(*RefreshTokenCreate, int)) *RefreshTokenCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &RefreshTokenCreateBulk{err: fmt.Errorf(\"calling to RefreshTokenClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*RefreshTokenCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &RefreshTokenCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for RefreshToken.\nfunc (c *RefreshTokenClient) Update() *RefreshTokenUpdate {\n\tmutation := newRefreshTokenMutation(c.config, OpUpdate)\n\treturn &RefreshTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *RefreshTokenClient) UpdateOne(_m *RefreshToken) *RefreshTokenUpdateOne {\n\tmutation := newRefreshTokenMutation(c.config, OpUpdateOne, withRefreshToken(_m))\n\treturn &RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *RefreshTokenClient) UpdateOneID(id string) *RefreshTokenUpdateOne {\n\tmutation := newRefreshTokenMutation(c.config, OpUpdateOne, withRefreshTokenID(id))\n\treturn &RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for RefreshToken.\nfunc (c *RefreshTokenClient) Delete() *RefreshTokenDelete {\n\tmutation := newRefreshTokenMutation(c.config, OpDelete)\n\treturn &RefreshTokenDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *RefreshTokenClient) DeleteOne(_m *RefreshToken) *RefreshTokenDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *RefreshTokenClient) DeleteOneID(id string) *RefreshTokenDeleteOne {\n\tbuilder := c.Delete().Where(refreshtoken.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &RefreshTokenDeleteOne{builder}\n}\n\n// Query returns a query builder for RefreshToken.\nfunc (c *RefreshTokenClient) Query() *RefreshTokenQuery {\n\treturn &RefreshTokenQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeRefreshToken},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a RefreshToken entity by its id.\nfunc (c *RefreshTokenClient) Get(ctx context.Context, id string) (*RefreshToken, error) {\n\treturn c.Query().Where(refreshtoken.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *RefreshTokenClient) GetX(ctx context.Context, id string) *RefreshToken {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *RefreshTokenClient) Hooks() []Hook {\n\treturn c.hooks.RefreshToken\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *RefreshTokenClient) Interceptors() []Interceptor {\n\treturn c.inters.RefreshToken\n}\n\nfunc (c *RefreshTokenClient) mutate(ctx context.Context, m *RefreshTokenMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&RefreshTokenCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&RefreshTokenUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&RefreshTokenUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&RefreshTokenDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown RefreshToken mutation op: %q\", m.Op())\n\t}\n}\n\n// UserIdentityClient is a client for the UserIdentity schema.\ntype UserIdentityClient struct {\n\tconfig\n}\n\n// NewUserIdentityClient returns a client for the UserIdentity from the given config.\nfunc NewUserIdentityClient(c config) *UserIdentityClient {\n\treturn &UserIdentityClient{config: c}\n}\n\n// Use adds a list of mutation hooks to the hooks stack.\n// A call to `Use(f, g, h)` equals to `useridentity.Hooks(f(g(h())))`.\nfunc (c *UserIdentityClient) Use(hooks ...Hook) {\n\tc.hooks.UserIdentity = append(c.hooks.UserIdentity, hooks...)\n}\n\n// Intercept adds a list of query interceptors to the interceptors stack.\n// A call to `Intercept(f, g, h)` equals to `useridentity.Intercept(f(g(h())))`.\nfunc (c *UserIdentityClient) Intercept(interceptors ...Interceptor) {\n\tc.inters.UserIdentity = append(c.inters.UserIdentity, interceptors...)\n}\n\n// Create returns a builder for creating a UserIdentity entity.\nfunc (c *UserIdentityClient) Create() *UserIdentityCreate {\n\tmutation := newUserIdentityMutation(c.config, OpCreate)\n\treturn &UserIdentityCreate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// CreateBulk returns a builder for creating a bulk of UserIdentity entities.\nfunc (c *UserIdentityClient) CreateBulk(builders ...*UserIdentityCreate) *UserIdentityCreateBulk {\n\treturn &UserIdentityCreateBulk{config: c.config, builders: builders}\n}\n\n// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates\n// a builder and applies setFunc on it.\nfunc (c *UserIdentityClient) MapCreateBulk(slice any, setFunc func(*UserIdentityCreate, int)) *UserIdentityCreateBulk {\n\trv := reflect.ValueOf(slice)\n\tif rv.Kind() != reflect.Slice {\n\t\treturn &UserIdentityCreateBulk{err: fmt.Errorf(\"calling to UserIdentityClient.MapCreateBulk with wrong type %T, need slice\", slice)}\n\t}\n\tbuilders := make([]*UserIdentityCreate, rv.Len())\n\tfor i := 0; i < rv.Len(); i++ {\n\t\tbuilders[i] = c.Create()\n\t\tsetFunc(builders[i], i)\n\t}\n\treturn &UserIdentityCreateBulk{config: c.config, builders: builders}\n}\n\n// Update returns an update builder for UserIdentity.\nfunc (c *UserIdentityClient) Update() *UserIdentityUpdate {\n\tmutation := newUserIdentityMutation(c.config, OpUpdate)\n\treturn &UserIdentityUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOne returns an update builder for the given entity.\nfunc (c *UserIdentityClient) UpdateOne(_m *UserIdentity) *UserIdentityUpdateOne {\n\tmutation := newUserIdentityMutation(c.config, OpUpdateOne, withUserIdentity(_m))\n\treturn &UserIdentityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// UpdateOneID returns an update builder for the given id.\nfunc (c *UserIdentityClient) UpdateOneID(id string) *UserIdentityUpdateOne {\n\tmutation := newUserIdentityMutation(c.config, OpUpdateOne, withUserIdentityID(id))\n\treturn &UserIdentityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// Delete returns a delete builder for UserIdentity.\nfunc (c *UserIdentityClient) Delete() *UserIdentityDelete {\n\tmutation := newUserIdentityMutation(c.config, OpDelete)\n\treturn &UserIdentityDelete{config: c.config, hooks: c.Hooks(), mutation: mutation}\n}\n\n// DeleteOne returns a builder for deleting the given entity.\nfunc (c *UserIdentityClient) DeleteOne(_m *UserIdentity) *UserIdentityDeleteOne {\n\treturn c.DeleteOneID(_m.ID)\n}\n\n// DeleteOneID returns a builder for deleting the given entity by its id.\nfunc (c *UserIdentityClient) DeleteOneID(id string) *UserIdentityDeleteOne {\n\tbuilder := c.Delete().Where(useridentity.ID(id))\n\tbuilder.mutation.id = &id\n\tbuilder.mutation.op = OpDeleteOne\n\treturn &UserIdentityDeleteOne{builder}\n}\n\n// Query returns a query builder for UserIdentity.\nfunc (c *UserIdentityClient) Query() *UserIdentityQuery {\n\treturn &UserIdentityQuery{\n\t\tconfig: c.config,\n\t\tctx:    &QueryContext{Type: TypeUserIdentity},\n\t\tinters: c.Interceptors(),\n\t}\n}\n\n// Get returns a UserIdentity entity by its id.\nfunc (c *UserIdentityClient) Get(ctx context.Context, id string) (*UserIdentity, error) {\n\treturn c.Query().Where(useridentity.ID(id)).Only(ctx)\n}\n\n// GetX is like Get, but panics if an error occurs.\nfunc (c *UserIdentityClient) GetX(ctx context.Context, id string) *UserIdentity {\n\tobj, err := c.Get(ctx, id)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn obj\n}\n\n// Hooks returns the client hooks.\nfunc (c *UserIdentityClient) Hooks() []Hook {\n\treturn c.hooks.UserIdentity\n}\n\n// Interceptors returns the client interceptors.\nfunc (c *UserIdentityClient) Interceptors() []Interceptor {\n\treturn c.inters.UserIdentity\n}\n\nfunc (c *UserIdentityClient) mutate(ctx context.Context, m *UserIdentityMutation) (Value, error) {\n\tswitch m.Op() {\n\tcase OpCreate:\n\t\treturn (&UserIdentityCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdate:\n\t\treturn (&UserIdentityUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpUpdateOne:\n\t\treturn (&UserIdentityUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx)\n\tcase OpDelete, OpDeleteOne:\n\t\treturn (&UserIdentityDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"db: unknown UserIdentity mutation op: %q\", m.Op())\n\t}\n}\n\n// hooks and interceptors per client, for fast access.\ntype (\n\thooks struct {\n\t\tAuthCode, AuthRequest, AuthSession, Connector, DeviceRequest, DeviceToken, Keys,\n\t\tOAuth2Client, OfflineSession, Password, RefreshToken, UserIdentity []ent.Hook\n\t}\n\tinters struct {\n\t\tAuthCode, AuthRequest, AuthSession, Connector, DeviceRequest, DeviceToken, Keys,\n\t\tOAuth2Client, OfflineSession, Password, RefreshToken,\n\t\tUserIdentity []ent.Interceptor\n\t}\n)\n"
  },
  {
    "path": "storage/ent/db/connector/connector.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage connector\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the connector type in the database.\n\tLabel = \"connector\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldType holds the string denoting the type field in the database.\n\tFieldType = \"type\"\n\t// FieldName holds the string denoting the name field in the database.\n\tFieldName = \"name\"\n\t// FieldResourceVersion holds the string denoting the resource_version field in the database.\n\tFieldResourceVersion = \"resource_version\"\n\t// FieldConfig holds the string denoting the config field in the database.\n\tFieldConfig = \"config\"\n\t// FieldGrantTypes holds the string denoting the grant_types field in the database.\n\tFieldGrantTypes = \"grant_types\"\n\t// Table holds the table name of the connector in the database.\n\tTable = \"connectors\"\n)\n\n// Columns holds all SQL columns for connector fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldType,\n\tFieldName,\n\tFieldResourceVersion,\n\tFieldConfig,\n\tFieldGrantTypes,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// TypeValidator is a validator for the \"type\" field. It is called by the builders before save.\n\tTypeValidator func(string) error\n\t// NameValidator is a validator for the \"name\" field. It is called by the builders before save.\n\tNameValidator func(string) error\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the Connector queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByType orders the results by the type field.\nfunc ByType(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldType, opts...).ToFunc()\n}\n\n// ByName orders the results by the name field.\nfunc ByName(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldName, opts...).ToFunc()\n}\n\n// ByResourceVersion orders the results by the resource_version field.\nfunc ByResourceVersion(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldResourceVersion, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/connector/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage connector\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldContainsFold(FieldID, id))\n}\n\n// Type applies equality check predicate on the \"type\" field. It's identical to TypeEQ.\nfunc Type(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldType, v))\n}\n\n// Name applies equality check predicate on the \"name\" field. It's identical to NameEQ.\nfunc Name(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldName, v))\n}\n\n// ResourceVersion applies equality check predicate on the \"resource_version\" field. It's identical to ResourceVersionEQ.\nfunc ResourceVersion(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldResourceVersion, v))\n}\n\n// Config applies equality check predicate on the \"config\" field. It's identical to ConfigEQ.\nfunc Config(v []byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldConfig, v))\n}\n\n// TypeEQ applies the EQ predicate on the \"type\" field.\nfunc TypeEQ(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldType, v))\n}\n\n// TypeNEQ applies the NEQ predicate on the \"type\" field.\nfunc TypeNEQ(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNEQ(FieldType, v))\n}\n\n// TypeIn applies the In predicate on the \"type\" field.\nfunc TypeIn(vs ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldIn(FieldType, vs...))\n}\n\n// TypeNotIn applies the NotIn predicate on the \"type\" field.\nfunc TypeNotIn(vs ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNotIn(FieldType, vs...))\n}\n\n// TypeGT applies the GT predicate on the \"type\" field.\nfunc TypeGT(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGT(FieldType, v))\n}\n\n// TypeGTE applies the GTE predicate on the \"type\" field.\nfunc TypeGTE(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGTE(FieldType, v))\n}\n\n// TypeLT applies the LT predicate on the \"type\" field.\nfunc TypeLT(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLT(FieldType, v))\n}\n\n// TypeLTE applies the LTE predicate on the \"type\" field.\nfunc TypeLTE(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLTE(FieldType, v))\n}\n\n// TypeContains applies the Contains predicate on the \"type\" field.\nfunc TypeContains(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldContains(FieldType, v))\n}\n\n// TypeHasPrefix applies the HasPrefix predicate on the \"type\" field.\nfunc TypeHasPrefix(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldHasPrefix(FieldType, v))\n}\n\n// TypeHasSuffix applies the HasSuffix predicate on the \"type\" field.\nfunc TypeHasSuffix(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldHasSuffix(FieldType, v))\n}\n\n// TypeEqualFold applies the EqualFold predicate on the \"type\" field.\nfunc TypeEqualFold(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEqualFold(FieldType, v))\n}\n\n// TypeContainsFold applies the ContainsFold predicate on the \"type\" field.\nfunc TypeContainsFold(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldContainsFold(FieldType, v))\n}\n\n// NameEQ applies the EQ predicate on the \"name\" field.\nfunc NameEQ(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldName, v))\n}\n\n// NameNEQ applies the NEQ predicate on the \"name\" field.\nfunc NameNEQ(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNEQ(FieldName, v))\n}\n\n// NameIn applies the In predicate on the \"name\" field.\nfunc NameIn(vs ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldIn(FieldName, vs...))\n}\n\n// NameNotIn applies the NotIn predicate on the \"name\" field.\nfunc NameNotIn(vs ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNotIn(FieldName, vs...))\n}\n\n// NameGT applies the GT predicate on the \"name\" field.\nfunc NameGT(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGT(FieldName, v))\n}\n\n// NameGTE applies the GTE predicate on the \"name\" field.\nfunc NameGTE(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGTE(FieldName, v))\n}\n\n// NameLT applies the LT predicate on the \"name\" field.\nfunc NameLT(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLT(FieldName, v))\n}\n\n// NameLTE applies the LTE predicate on the \"name\" field.\nfunc NameLTE(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLTE(FieldName, v))\n}\n\n// NameContains applies the Contains predicate on the \"name\" field.\nfunc NameContains(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldContains(FieldName, v))\n}\n\n// NameHasPrefix applies the HasPrefix predicate on the \"name\" field.\nfunc NameHasPrefix(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldHasPrefix(FieldName, v))\n}\n\n// NameHasSuffix applies the HasSuffix predicate on the \"name\" field.\nfunc NameHasSuffix(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldHasSuffix(FieldName, v))\n}\n\n// NameEqualFold applies the EqualFold predicate on the \"name\" field.\nfunc NameEqualFold(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEqualFold(FieldName, v))\n}\n\n// NameContainsFold applies the ContainsFold predicate on the \"name\" field.\nfunc NameContainsFold(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldContainsFold(FieldName, v))\n}\n\n// ResourceVersionEQ applies the EQ predicate on the \"resource_version\" field.\nfunc ResourceVersionEQ(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldResourceVersion, v))\n}\n\n// ResourceVersionNEQ applies the NEQ predicate on the \"resource_version\" field.\nfunc ResourceVersionNEQ(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNEQ(FieldResourceVersion, v))\n}\n\n// ResourceVersionIn applies the In predicate on the \"resource_version\" field.\nfunc ResourceVersionIn(vs ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldIn(FieldResourceVersion, vs...))\n}\n\n// ResourceVersionNotIn applies the NotIn predicate on the \"resource_version\" field.\nfunc ResourceVersionNotIn(vs ...string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNotIn(FieldResourceVersion, vs...))\n}\n\n// ResourceVersionGT applies the GT predicate on the \"resource_version\" field.\nfunc ResourceVersionGT(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGT(FieldResourceVersion, v))\n}\n\n// ResourceVersionGTE applies the GTE predicate on the \"resource_version\" field.\nfunc ResourceVersionGTE(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGTE(FieldResourceVersion, v))\n}\n\n// ResourceVersionLT applies the LT predicate on the \"resource_version\" field.\nfunc ResourceVersionLT(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLT(FieldResourceVersion, v))\n}\n\n// ResourceVersionLTE applies the LTE predicate on the \"resource_version\" field.\nfunc ResourceVersionLTE(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLTE(FieldResourceVersion, v))\n}\n\n// ResourceVersionContains applies the Contains predicate on the \"resource_version\" field.\nfunc ResourceVersionContains(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldContains(FieldResourceVersion, v))\n}\n\n// ResourceVersionHasPrefix applies the HasPrefix predicate on the \"resource_version\" field.\nfunc ResourceVersionHasPrefix(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldHasPrefix(FieldResourceVersion, v))\n}\n\n// ResourceVersionHasSuffix applies the HasSuffix predicate on the \"resource_version\" field.\nfunc ResourceVersionHasSuffix(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldHasSuffix(FieldResourceVersion, v))\n}\n\n// ResourceVersionEqualFold applies the EqualFold predicate on the \"resource_version\" field.\nfunc ResourceVersionEqualFold(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEqualFold(FieldResourceVersion, v))\n}\n\n// ResourceVersionContainsFold applies the ContainsFold predicate on the \"resource_version\" field.\nfunc ResourceVersionContainsFold(v string) predicate.Connector {\n\treturn predicate.Connector(sql.FieldContainsFold(FieldResourceVersion, v))\n}\n\n// ConfigEQ applies the EQ predicate on the \"config\" field.\nfunc ConfigEQ(v []byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldEQ(FieldConfig, v))\n}\n\n// ConfigNEQ applies the NEQ predicate on the \"config\" field.\nfunc ConfigNEQ(v []byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNEQ(FieldConfig, v))\n}\n\n// ConfigIn applies the In predicate on the \"config\" field.\nfunc ConfigIn(vs ...[]byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldIn(FieldConfig, vs...))\n}\n\n// ConfigNotIn applies the NotIn predicate on the \"config\" field.\nfunc ConfigNotIn(vs ...[]byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldNotIn(FieldConfig, vs...))\n}\n\n// ConfigGT applies the GT predicate on the \"config\" field.\nfunc ConfigGT(v []byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGT(FieldConfig, v))\n}\n\n// ConfigGTE applies the GTE predicate on the \"config\" field.\nfunc ConfigGTE(v []byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldGTE(FieldConfig, v))\n}\n\n// ConfigLT applies the LT predicate on the \"config\" field.\nfunc ConfigLT(v []byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLT(FieldConfig, v))\n}\n\n// ConfigLTE applies the LTE predicate on the \"config\" field.\nfunc ConfigLTE(v []byte) predicate.Connector {\n\treturn predicate.Connector(sql.FieldLTE(FieldConfig, v))\n}\n\n// GrantTypesIsNil applies the IsNil predicate on the \"grant_types\" field.\nfunc GrantTypesIsNil() predicate.Connector {\n\treturn predicate.Connector(sql.FieldIsNull(FieldGrantTypes))\n}\n\n// GrantTypesNotNil applies the NotNil predicate on the \"grant_types\" field.\nfunc GrantTypesNotNil() predicate.Connector {\n\treturn predicate.Connector(sql.FieldNotNull(FieldGrantTypes))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.Connector) predicate.Connector {\n\treturn predicate.Connector(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.Connector) predicate.Connector {\n\treturn predicate.Connector(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.Connector) predicate.Connector {\n\treturn predicate.Connector(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/connector.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n)\n\n// Connector is the model entity for the Connector schema.\ntype Connector struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// Type holds the value of the \"type\" field.\n\tType string `json:\"type,omitempty\"`\n\t// Name holds the value of the \"name\" field.\n\tName string `json:\"name,omitempty\"`\n\t// ResourceVersion holds the value of the \"resource_version\" field.\n\tResourceVersion string `json:\"resource_version,omitempty\"`\n\t// Config holds the value of the \"config\" field.\n\tConfig []byte `json:\"config,omitempty\"`\n\t// GrantTypes holds the value of the \"grant_types\" field.\n\tGrantTypes   []string `json:\"grant_types,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*Connector) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase connector.FieldConfig, connector.FieldGrantTypes:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase connector.FieldID, connector.FieldType, connector.FieldName, connector.FieldResourceVersion:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the Connector fields.\nfunc (_m *Connector) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase connector.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase connector.FieldType:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field type\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Type = value.String\n\t\t\t}\n\t\tcase connector.FieldName:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field name\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Name = value.String\n\t\t\t}\n\t\tcase connector.FieldResourceVersion:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field resource_version\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ResourceVersion = value.String\n\t\t\t}\n\t\tcase connector.FieldConfig:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field config\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.Config = *value\n\t\t\t}\n\t\tcase connector.FieldGrantTypes:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field grant_types\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.GrantTypes); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field grant_types: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the Connector.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *Connector) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this Connector.\n// Note that you need to call Connector.Unwrap() before calling this method if this Connector\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *Connector) Update() *ConnectorUpdateOne {\n\treturn NewConnectorClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the Connector entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *Connector) Unwrap() *Connector {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: Connector is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *Connector) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"Connector(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"type=\")\n\tbuilder.WriteString(_m.Type)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"name=\")\n\tbuilder.WriteString(_m.Name)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"resource_version=\")\n\tbuilder.WriteString(_m.ResourceVersion)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"config=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Config))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"grant_types=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.GrantTypes))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// Connectors is a parsable slice of Connector.\ntype Connectors []*Connector\n"
  },
  {
    "path": "storage/ent/db/connector_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n)\n\n// ConnectorCreate is the builder for creating a Connector entity.\ntype ConnectorCreate struct {\n\tconfig\n\tmutation *ConnectorMutation\n\thooks    []Hook\n}\n\n// SetType sets the \"type\" field.\nfunc (_c *ConnectorCreate) SetType(v string) *ConnectorCreate {\n\t_c.mutation.SetType(v)\n\treturn _c\n}\n\n// SetName sets the \"name\" field.\nfunc (_c *ConnectorCreate) SetName(v string) *ConnectorCreate {\n\t_c.mutation.SetName(v)\n\treturn _c\n}\n\n// SetResourceVersion sets the \"resource_version\" field.\nfunc (_c *ConnectorCreate) SetResourceVersion(v string) *ConnectorCreate {\n\t_c.mutation.SetResourceVersion(v)\n\treturn _c\n}\n\n// SetConfig sets the \"config\" field.\nfunc (_c *ConnectorCreate) SetConfig(v []byte) *ConnectorCreate {\n\t_c.mutation.SetConfig(v)\n\treturn _c\n}\n\n// SetGrantTypes sets the \"grant_types\" field.\nfunc (_c *ConnectorCreate) SetGrantTypes(v []string) *ConnectorCreate {\n\t_c.mutation.SetGrantTypes(v)\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *ConnectorCreate) SetID(v string) *ConnectorCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the ConnectorMutation object of the builder.\nfunc (_c *ConnectorCreate) Mutation() *ConnectorMutation {\n\treturn _c.mutation\n}\n\n// Save creates the Connector in the database.\nfunc (_c *ConnectorCreate) Save(ctx context.Context) (*Connector, error) {\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *ConnectorCreate) SaveX(ctx context.Context) *Connector {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *ConnectorCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *ConnectorCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *ConnectorCreate) check() error {\n\tif _, ok := _c.mutation.GetType(); !ok {\n\t\treturn &ValidationError{Name: \"type\", err: errors.New(`db: missing required field \"Connector.type\"`)}\n\t}\n\tif v, ok := _c.mutation.GetType(); ok {\n\t\tif err := connector.TypeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"type\", err: fmt.Errorf(`db: validator failed for field \"Connector.type\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Name(); !ok {\n\t\treturn &ValidationError{Name: \"name\", err: errors.New(`db: missing required field \"Connector.name\"`)}\n\t}\n\tif v, ok := _c.mutation.Name(); ok {\n\t\tif err := connector.NameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"name\", err: fmt.Errorf(`db: validator failed for field \"Connector.name\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ResourceVersion(); !ok {\n\t\treturn &ValidationError{Name: \"resource_version\", err: errors.New(`db: missing required field \"Connector.resource_version\"`)}\n\t}\n\tif _, ok := _c.mutation.Config(); !ok {\n\t\treturn &ValidationError{Name: \"config\", err: errors.New(`db: missing required field \"Connector.config\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := connector.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"Connector.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *ConnectorCreate) sqlSave(ctx context.Context) (*Connector, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected Connector.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *ConnectorCreate) createSpec() (*Connector, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &Connector{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(connector.Table, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.GetType(); ok {\n\t\t_spec.SetField(connector.FieldType, field.TypeString, value)\n\t\t_node.Type = value\n\t}\n\tif value, ok := _c.mutation.Name(); ok {\n\t\t_spec.SetField(connector.FieldName, field.TypeString, value)\n\t\t_node.Name = value\n\t}\n\tif value, ok := _c.mutation.ResourceVersion(); ok {\n\t\t_spec.SetField(connector.FieldResourceVersion, field.TypeString, value)\n\t\t_node.ResourceVersion = value\n\t}\n\tif value, ok := _c.mutation.Config(); ok {\n\t\t_spec.SetField(connector.FieldConfig, field.TypeBytes, value)\n\t\t_node.Config = value\n\t}\n\tif value, ok := _c.mutation.GrantTypes(); ok {\n\t\t_spec.SetField(connector.FieldGrantTypes, field.TypeJSON, value)\n\t\t_node.GrantTypes = value\n\t}\n\treturn _node, _spec\n}\n\n// ConnectorCreateBulk is the builder for creating many Connector entities in bulk.\ntype ConnectorCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*ConnectorCreate\n}\n\n// Save creates the Connector entities in the database.\nfunc (_c *ConnectorCreateBulk) Save(ctx context.Context) ([]*Connector, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*Connector, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*ConnectorMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *ConnectorCreateBulk) SaveX(ctx context.Context) []*Connector {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *ConnectorCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *ConnectorCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/connector_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ConnectorDelete is the builder for deleting a Connector entity.\ntype ConnectorDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *ConnectorMutation\n}\n\n// Where appends a list predicates to the ConnectorDelete builder.\nfunc (_d *ConnectorDelete) Where(ps ...predicate.Connector) *ConnectorDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *ConnectorDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *ConnectorDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *ConnectorDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(connector.Table, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// ConnectorDeleteOne is the builder for deleting a single Connector entity.\ntype ConnectorDeleteOne struct {\n\t_d *ConnectorDelete\n}\n\n// Where appends a list predicates to the ConnectorDelete builder.\nfunc (_d *ConnectorDeleteOne) Where(ps ...predicate.Connector) *ConnectorDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *ConnectorDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{connector.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *ConnectorDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/connector_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ConnectorQuery is the builder for querying Connector entities.\ntype ConnectorQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []connector.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.Connector\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the ConnectorQuery builder.\nfunc (_q *ConnectorQuery) Where(ps ...predicate.Connector) *ConnectorQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *ConnectorQuery) Limit(limit int) *ConnectorQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *ConnectorQuery) Offset(offset int) *ConnectorQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *ConnectorQuery) Unique(unique bool) *ConnectorQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *ConnectorQuery) Order(o ...connector.OrderOption) *ConnectorQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first Connector entity from the query.\n// Returns a *NotFoundError when no Connector was found.\nfunc (_q *ConnectorQuery) First(ctx context.Context) (*Connector, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{connector.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *ConnectorQuery) FirstX(ctx context.Context) *Connector {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first Connector ID from the query.\n// Returns a *NotFoundError when no Connector ID was found.\nfunc (_q *ConnectorQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{connector.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *ConnectorQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single Connector entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one Connector entity is found.\n// Returns a *NotFoundError when no Connector entities are found.\nfunc (_q *ConnectorQuery) Only(ctx context.Context) (*Connector, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{connector.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{connector.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *ConnectorQuery) OnlyX(ctx context.Context) *Connector {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only Connector ID in the query.\n// Returns a *NotSingularError when more than one Connector ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *ConnectorQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{connector.Label}\n\tdefault:\n\t\terr = &NotSingularError{connector.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *ConnectorQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of Connectors.\nfunc (_q *ConnectorQuery) All(ctx context.Context) ([]*Connector, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*Connector, *ConnectorQuery]()\n\treturn withInterceptors[[]*Connector](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *ConnectorQuery) AllX(ctx context.Context) []*Connector {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of Connector IDs.\nfunc (_q *ConnectorQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(connector.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *ConnectorQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *ConnectorQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*ConnectorQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *ConnectorQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *ConnectorQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *ConnectorQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the ConnectorQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *ConnectorQuery) Clone() *ConnectorQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &ConnectorQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]connector.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.Connector{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tType string `json:\"type,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.Connector.Query().\n//\t\tGroupBy(connector.FieldType).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *ConnectorQuery) GroupBy(field string, fields ...string) *ConnectorGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &ConnectorGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = connector.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tType string `json:\"type,omitempty\"`\n//\t}\n//\n//\tclient.Connector.Query().\n//\t\tSelect(connector.FieldType).\n//\t\tScan(ctx, &v)\nfunc (_q *ConnectorQuery) Select(fields ...string) *ConnectorSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &ConnectorSelect{ConnectorQuery: _q}\n\tsbuild.label = connector.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a ConnectorSelect configured with the given aggregations.\nfunc (_q *ConnectorQuery) Aggregate(fns ...AggregateFunc) *ConnectorSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *ConnectorQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !connector.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *ConnectorQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Connector, error) {\n\tvar (\n\t\tnodes = []*Connector{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*Connector).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &Connector{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *ConnectorQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *ConnectorQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(connector.Table, connector.Columns, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, connector.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != connector.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *ConnectorQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(connector.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = connector.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// ConnectorGroupBy is the group-by builder for Connector entities.\ntype ConnectorGroupBy struct {\n\tselector\n\tbuild *ConnectorQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *ConnectorGroupBy) Aggregate(fns ...AggregateFunc) *ConnectorGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *ConnectorGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*ConnectorQuery, *ConnectorGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *ConnectorGroupBy) sqlScan(ctx context.Context, root *ConnectorQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// ConnectorSelect is the builder for selecting fields of Connector entities.\ntype ConnectorSelect struct {\n\t*ConnectorQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *ConnectorSelect) Aggregate(fns ...AggregateFunc) *ConnectorSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *ConnectorSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*ConnectorQuery, *ConnectorSelect](ctx, _s.ConnectorQuery, _s, _s.inters, v)\n}\n\nfunc (_s *ConnectorSelect) sqlScan(ctx context.Context, root *ConnectorQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/connector_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ConnectorUpdate is the builder for updating Connector entities.\ntype ConnectorUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *ConnectorMutation\n}\n\n// Where appends a list predicates to the ConnectorUpdate builder.\nfunc (_u *ConnectorUpdate) Where(ps ...predicate.Connector) *ConnectorUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetType sets the \"type\" field.\nfunc (_u *ConnectorUpdate) SetType(v string) *ConnectorUpdate {\n\t_u.mutation.SetType(v)\n\treturn _u\n}\n\n// SetNillableType sets the \"type\" field if the given value is not nil.\nfunc (_u *ConnectorUpdate) SetNillableType(v *string) *ConnectorUpdate {\n\tif v != nil {\n\t\t_u.SetType(*v)\n\t}\n\treturn _u\n}\n\n// SetName sets the \"name\" field.\nfunc (_u *ConnectorUpdate) SetName(v string) *ConnectorUpdate {\n\t_u.mutation.SetName(v)\n\treturn _u\n}\n\n// SetNillableName sets the \"name\" field if the given value is not nil.\nfunc (_u *ConnectorUpdate) SetNillableName(v *string) *ConnectorUpdate {\n\tif v != nil {\n\t\t_u.SetName(*v)\n\t}\n\treturn _u\n}\n\n// SetResourceVersion sets the \"resource_version\" field.\nfunc (_u *ConnectorUpdate) SetResourceVersion(v string) *ConnectorUpdate {\n\t_u.mutation.SetResourceVersion(v)\n\treturn _u\n}\n\n// SetNillableResourceVersion sets the \"resource_version\" field if the given value is not nil.\nfunc (_u *ConnectorUpdate) SetNillableResourceVersion(v *string) *ConnectorUpdate {\n\tif v != nil {\n\t\t_u.SetResourceVersion(*v)\n\t}\n\treturn _u\n}\n\n// SetConfig sets the \"config\" field.\nfunc (_u *ConnectorUpdate) SetConfig(v []byte) *ConnectorUpdate {\n\t_u.mutation.SetConfig(v)\n\treturn _u\n}\n\n// SetGrantTypes sets the \"grant_types\" field.\nfunc (_u *ConnectorUpdate) SetGrantTypes(v []string) *ConnectorUpdate {\n\t_u.mutation.SetGrantTypes(v)\n\treturn _u\n}\n\n// AppendGrantTypes appends value to the \"grant_types\" field.\nfunc (_u *ConnectorUpdate) AppendGrantTypes(v []string) *ConnectorUpdate {\n\t_u.mutation.AppendGrantTypes(v)\n\treturn _u\n}\n\n// ClearGrantTypes clears the value of the \"grant_types\" field.\nfunc (_u *ConnectorUpdate) ClearGrantTypes() *ConnectorUpdate {\n\t_u.mutation.ClearGrantTypes()\n\treturn _u\n}\n\n// Mutation returns the ConnectorMutation object of the builder.\nfunc (_u *ConnectorUpdate) Mutation() *ConnectorMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *ConnectorUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *ConnectorUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *ConnectorUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *ConnectorUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *ConnectorUpdate) check() error {\n\tif v, ok := _u.mutation.GetType(); ok {\n\t\tif err := connector.TypeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"type\", err: fmt.Errorf(`db: validator failed for field \"Connector.type\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Name(); ok {\n\t\tif err := connector.NameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"name\", err: fmt.Errorf(`db: validator failed for field \"Connector.name\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *ConnectorUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(connector.Table, connector.Columns, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.GetType(); ok {\n\t\t_spec.SetField(connector.FieldType, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Name(); ok {\n\t\t_spec.SetField(connector.FieldName, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ResourceVersion(); ok {\n\t\t_spec.SetField(connector.FieldResourceVersion, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Config(); ok {\n\t\t_spec.SetField(connector.FieldConfig, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.GrantTypes(); ok {\n\t\t_spec.SetField(connector.FieldGrantTypes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedGrantTypes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, connector.FieldGrantTypes, value)\n\t\t})\n\t}\n\tif _u.mutation.GrantTypesCleared() {\n\t\t_spec.ClearField(connector.FieldGrantTypes, field.TypeJSON)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{connector.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// ConnectorUpdateOne is the builder for updating a single Connector entity.\ntype ConnectorUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *ConnectorMutation\n}\n\n// SetType sets the \"type\" field.\nfunc (_u *ConnectorUpdateOne) SetType(v string) *ConnectorUpdateOne {\n\t_u.mutation.SetType(v)\n\treturn _u\n}\n\n// SetNillableType sets the \"type\" field if the given value is not nil.\nfunc (_u *ConnectorUpdateOne) SetNillableType(v *string) *ConnectorUpdateOne {\n\tif v != nil {\n\t\t_u.SetType(*v)\n\t}\n\treturn _u\n}\n\n// SetName sets the \"name\" field.\nfunc (_u *ConnectorUpdateOne) SetName(v string) *ConnectorUpdateOne {\n\t_u.mutation.SetName(v)\n\treturn _u\n}\n\n// SetNillableName sets the \"name\" field if the given value is not nil.\nfunc (_u *ConnectorUpdateOne) SetNillableName(v *string) *ConnectorUpdateOne {\n\tif v != nil {\n\t\t_u.SetName(*v)\n\t}\n\treturn _u\n}\n\n// SetResourceVersion sets the \"resource_version\" field.\nfunc (_u *ConnectorUpdateOne) SetResourceVersion(v string) *ConnectorUpdateOne {\n\t_u.mutation.SetResourceVersion(v)\n\treturn _u\n}\n\n// SetNillableResourceVersion sets the \"resource_version\" field if the given value is not nil.\nfunc (_u *ConnectorUpdateOne) SetNillableResourceVersion(v *string) *ConnectorUpdateOne {\n\tif v != nil {\n\t\t_u.SetResourceVersion(*v)\n\t}\n\treturn _u\n}\n\n// SetConfig sets the \"config\" field.\nfunc (_u *ConnectorUpdateOne) SetConfig(v []byte) *ConnectorUpdateOne {\n\t_u.mutation.SetConfig(v)\n\treturn _u\n}\n\n// SetGrantTypes sets the \"grant_types\" field.\nfunc (_u *ConnectorUpdateOne) SetGrantTypes(v []string) *ConnectorUpdateOne {\n\t_u.mutation.SetGrantTypes(v)\n\treturn _u\n}\n\n// AppendGrantTypes appends value to the \"grant_types\" field.\nfunc (_u *ConnectorUpdateOne) AppendGrantTypes(v []string) *ConnectorUpdateOne {\n\t_u.mutation.AppendGrantTypes(v)\n\treturn _u\n}\n\n// ClearGrantTypes clears the value of the \"grant_types\" field.\nfunc (_u *ConnectorUpdateOne) ClearGrantTypes() *ConnectorUpdateOne {\n\t_u.mutation.ClearGrantTypes()\n\treturn _u\n}\n\n// Mutation returns the ConnectorMutation object of the builder.\nfunc (_u *ConnectorUpdateOne) Mutation() *ConnectorMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the ConnectorUpdate builder.\nfunc (_u *ConnectorUpdateOne) Where(ps ...predicate.Connector) *ConnectorUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *ConnectorUpdateOne) Select(field string, fields ...string) *ConnectorUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated Connector entity.\nfunc (_u *ConnectorUpdateOne) Save(ctx context.Context) (*Connector, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *ConnectorUpdateOne) SaveX(ctx context.Context) *Connector {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *ConnectorUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *ConnectorUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *ConnectorUpdateOne) check() error {\n\tif v, ok := _u.mutation.GetType(); ok {\n\t\tif err := connector.TypeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"type\", err: fmt.Errorf(`db: validator failed for field \"Connector.type\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Name(); ok {\n\t\tif err := connector.NameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"name\", err: fmt.Errorf(`db: validator failed for field \"Connector.name\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *ConnectorUpdateOne) sqlSave(ctx context.Context) (_node *Connector, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(connector.Table, connector.Columns, sqlgraph.NewFieldSpec(connector.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"Connector.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, connector.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !connector.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != connector.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.GetType(); ok {\n\t\t_spec.SetField(connector.FieldType, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Name(); ok {\n\t\t_spec.SetField(connector.FieldName, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ResourceVersion(); ok {\n\t\t_spec.SetField(connector.FieldResourceVersion, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Config(); ok {\n\t\t_spec.SetField(connector.FieldConfig, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.GrantTypes(); ok {\n\t\t_spec.SetField(connector.FieldGrantTypes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedGrantTypes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, connector.FieldGrantTypes, value)\n\t\t})\n\t}\n\tif _u.mutation.GrantTypesCleared() {\n\t\t_spec.ClearField(connector.FieldGrantTypes, field.TypeJSON)\n\t}\n\t_node = &Connector{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{connector.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/devicerequest/devicerequest.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage devicerequest\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the devicerequest type in the database.\n\tLabel = \"device_request\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldUserCode holds the string denoting the user_code field in the database.\n\tFieldUserCode = \"user_code\"\n\t// FieldDeviceCode holds the string denoting the device_code field in the database.\n\tFieldDeviceCode = \"device_code\"\n\t// FieldClientID holds the string denoting the client_id field in the database.\n\tFieldClientID = \"client_id\"\n\t// FieldClientSecret holds the string denoting the client_secret field in the database.\n\tFieldClientSecret = \"client_secret\"\n\t// FieldScopes holds the string denoting the scopes field in the database.\n\tFieldScopes = \"scopes\"\n\t// FieldExpiry holds the string denoting the expiry field in the database.\n\tFieldExpiry = \"expiry\"\n\t// Table holds the table name of the devicerequest in the database.\n\tTable = \"device_requests\"\n)\n\n// Columns holds all SQL columns for devicerequest fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldUserCode,\n\tFieldDeviceCode,\n\tFieldClientID,\n\tFieldClientSecret,\n\tFieldScopes,\n\tFieldExpiry,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// UserCodeValidator is a validator for the \"user_code\" field. It is called by the builders before save.\n\tUserCodeValidator func(string) error\n\t// DeviceCodeValidator is a validator for the \"device_code\" field. It is called by the builders before save.\n\tDeviceCodeValidator func(string) error\n\t// ClientIDValidator is a validator for the \"client_id\" field. It is called by the builders before save.\n\tClientIDValidator func(string) error\n\t// ClientSecretValidator is a validator for the \"client_secret\" field. It is called by the builders before save.\n\tClientSecretValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the DeviceRequest queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByUserCode orders the results by the user_code field.\nfunc ByUserCode(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldUserCode, opts...).ToFunc()\n}\n\n// ByDeviceCode orders the results by the device_code field.\nfunc ByDeviceCode(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldDeviceCode, opts...).ToFunc()\n}\n\n// ByClientID orders the results by the client_id field.\nfunc ByClientID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClientID, opts...).ToFunc()\n}\n\n// ByClientSecret orders the results by the client_secret field.\nfunc ByClientSecret(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClientSecret, opts...).ToFunc()\n}\n\n// ByExpiry orders the results by the expiry field.\nfunc ByExpiry(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldExpiry, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/devicerequest/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage devicerequest\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id int) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLTE(FieldID, id))\n}\n\n// UserCode applies equality check predicate on the \"user_code\" field. It's identical to UserCodeEQ.\nfunc UserCode(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldUserCode, v))\n}\n\n// DeviceCode applies equality check predicate on the \"device_code\" field. It's identical to DeviceCodeEQ.\nfunc DeviceCode(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldDeviceCode, v))\n}\n\n// ClientID applies equality check predicate on the \"client_id\" field. It's identical to ClientIDEQ.\nfunc ClientID(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldClientID, v))\n}\n\n// ClientSecret applies equality check predicate on the \"client_secret\" field. It's identical to ClientSecretEQ.\nfunc ClientSecret(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldClientSecret, v))\n}\n\n// Expiry applies equality check predicate on the \"expiry\" field. It's identical to ExpiryEQ.\nfunc Expiry(v time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldExpiry, v))\n}\n\n// UserCodeEQ applies the EQ predicate on the \"user_code\" field.\nfunc UserCodeEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldUserCode, v))\n}\n\n// UserCodeNEQ applies the NEQ predicate on the \"user_code\" field.\nfunc UserCodeNEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNEQ(FieldUserCode, v))\n}\n\n// UserCodeIn applies the In predicate on the \"user_code\" field.\nfunc UserCodeIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldIn(FieldUserCode, vs...))\n}\n\n// UserCodeNotIn applies the NotIn predicate on the \"user_code\" field.\nfunc UserCodeNotIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNotIn(FieldUserCode, vs...))\n}\n\n// UserCodeGT applies the GT predicate on the \"user_code\" field.\nfunc UserCodeGT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGT(FieldUserCode, v))\n}\n\n// UserCodeGTE applies the GTE predicate on the \"user_code\" field.\nfunc UserCodeGTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGTE(FieldUserCode, v))\n}\n\n// UserCodeLT applies the LT predicate on the \"user_code\" field.\nfunc UserCodeLT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLT(FieldUserCode, v))\n}\n\n// UserCodeLTE applies the LTE predicate on the \"user_code\" field.\nfunc UserCodeLTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLTE(FieldUserCode, v))\n}\n\n// UserCodeContains applies the Contains predicate on the \"user_code\" field.\nfunc UserCodeContains(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContains(FieldUserCode, v))\n}\n\n// UserCodeHasPrefix applies the HasPrefix predicate on the \"user_code\" field.\nfunc UserCodeHasPrefix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasPrefix(FieldUserCode, v))\n}\n\n// UserCodeHasSuffix applies the HasSuffix predicate on the \"user_code\" field.\nfunc UserCodeHasSuffix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasSuffix(FieldUserCode, v))\n}\n\n// UserCodeEqualFold applies the EqualFold predicate on the \"user_code\" field.\nfunc UserCodeEqualFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEqualFold(FieldUserCode, v))\n}\n\n// UserCodeContainsFold applies the ContainsFold predicate on the \"user_code\" field.\nfunc UserCodeContainsFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContainsFold(FieldUserCode, v))\n}\n\n// DeviceCodeEQ applies the EQ predicate on the \"device_code\" field.\nfunc DeviceCodeEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldDeviceCode, v))\n}\n\n// DeviceCodeNEQ applies the NEQ predicate on the \"device_code\" field.\nfunc DeviceCodeNEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNEQ(FieldDeviceCode, v))\n}\n\n// DeviceCodeIn applies the In predicate on the \"device_code\" field.\nfunc DeviceCodeIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldIn(FieldDeviceCode, vs...))\n}\n\n// DeviceCodeNotIn applies the NotIn predicate on the \"device_code\" field.\nfunc DeviceCodeNotIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNotIn(FieldDeviceCode, vs...))\n}\n\n// DeviceCodeGT applies the GT predicate on the \"device_code\" field.\nfunc DeviceCodeGT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGT(FieldDeviceCode, v))\n}\n\n// DeviceCodeGTE applies the GTE predicate on the \"device_code\" field.\nfunc DeviceCodeGTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGTE(FieldDeviceCode, v))\n}\n\n// DeviceCodeLT applies the LT predicate on the \"device_code\" field.\nfunc DeviceCodeLT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLT(FieldDeviceCode, v))\n}\n\n// DeviceCodeLTE applies the LTE predicate on the \"device_code\" field.\nfunc DeviceCodeLTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLTE(FieldDeviceCode, v))\n}\n\n// DeviceCodeContains applies the Contains predicate on the \"device_code\" field.\nfunc DeviceCodeContains(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContains(FieldDeviceCode, v))\n}\n\n// DeviceCodeHasPrefix applies the HasPrefix predicate on the \"device_code\" field.\nfunc DeviceCodeHasPrefix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasPrefix(FieldDeviceCode, v))\n}\n\n// DeviceCodeHasSuffix applies the HasSuffix predicate on the \"device_code\" field.\nfunc DeviceCodeHasSuffix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasSuffix(FieldDeviceCode, v))\n}\n\n// DeviceCodeEqualFold applies the EqualFold predicate on the \"device_code\" field.\nfunc DeviceCodeEqualFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEqualFold(FieldDeviceCode, v))\n}\n\n// DeviceCodeContainsFold applies the ContainsFold predicate on the \"device_code\" field.\nfunc DeviceCodeContainsFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContainsFold(FieldDeviceCode, v))\n}\n\n// ClientIDEQ applies the EQ predicate on the \"client_id\" field.\nfunc ClientIDEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldClientID, v))\n}\n\n// ClientIDNEQ applies the NEQ predicate on the \"client_id\" field.\nfunc ClientIDNEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNEQ(FieldClientID, v))\n}\n\n// ClientIDIn applies the In predicate on the \"client_id\" field.\nfunc ClientIDIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldIn(FieldClientID, vs...))\n}\n\n// ClientIDNotIn applies the NotIn predicate on the \"client_id\" field.\nfunc ClientIDNotIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNotIn(FieldClientID, vs...))\n}\n\n// ClientIDGT applies the GT predicate on the \"client_id\" field.\nfunc ClientIDGT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGT(FieldClientID, v))\n}\n\n// ClientIDGTE applies the GTE predicate on the \"client_id\" field.\nfunc ClientIDGTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGTE(FieldClientID, v))\n}\n\n// ClientIDLT applies the LT predicate on the \"client_id\" field.\nfunc ClientIDLT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLT(FieldClientID, v))\n}\n\n// ClientIDLTE applies the LTE predicate on the \"client_id\" field.\nfunc ClientIDLTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLTE(FieldClientID, v))\n}\n\n// ClientIDContains applies the Contains predicate on the \"client_id\" field.\nfunc ClientIDContains(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContains(FieldClientID, v))\n}\n\n// ClientIDHasPrefix applies the HasPrefix predicate on the \"client_id\" field.\nfunc ClientIDHasPrefix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasPrefix(FieldClientID, v))\n}\n\n// ClientIDHasSuffix applies the HasSuffix predicate on the \"client_id\" field.\nfunc ClientIDHasSuffix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasSuffix(FieldClientID, v))\n}\n\n// ClientIDEqualFold applies the EqualFold predicate on the \"client_id\" field.\nfunc ClientIDEqualFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEqualFold(FieldClientID, v))\n}\n\n// ClientIDContainsFold applies the ContainsFold predicate on the \"client_id\" field.\nfunc ClientIDContainsFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContainsFold(FieldClientID, v))\n}\n\n// ClientSecretEQ applies the EQ predicate on the \"client_secret\" field.\nfunc ClientSecretEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldClientSecret, v))\n}\n\n// ClientSecretNEQ applies the NEQ predicate on the \"client_secret\" field.\nfunc ClientSecretNEQ(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNEQ(FieldClientSecret, v))\n}\n\n// ClientSecretIn applies the In predicate on the \"client_secret\" field.\nfunc ClientSecretIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldIn(FieldClientSecret, vs...))\n}\n\n// ClientSecretNotIn applies the NotIn predicate on the \"client_secret\" field.\nfunc ClientSecretNotIn(vs ...string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNotIn(FieldClientSecret, vs...))\n}\n\n// ClientSecretGT applies the GT predicate on the \"client_secret\" field.\nfunc ClientSecretGT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGT(FieldClientSecret, v))\n}\n\n// ClientSecretGTE applies the GTE predicate on the \"client_secret\" field.\nfunc ClientSecretGTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGTE(FieldClientSecret, v))\n}\n\n// ClientSecretLT applies the LT predicate on the \"client_secret\" field.\nfunc ClientSecretLT(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLT(FieldClientSecret, v))\n}\n\n// ClientSecretLTE applies the LTE predicate on the \"client_secret\" field.\nfunc ClientSecretLTE(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLTE(FieldClientSecret, v))\n}\n\n// ClientSecretContains applies the Contains predicate on the \"client_secret\" field.\nfunc ClientSecretContains(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContains(FieldClientSecret, v))\n}\n\n// ClientSecretHasPrefix applies the HasPrefix predicate on the \"client_secret\" field.\nfunc ClientSecretHasPrefix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasPrefix(FieldClientSecret, v))\n}\n\n// ClientSecretHasSuffix applies the HasSuffix predicate on the \"client_secret\" field.\nfunc ClientSecretHasSuffix(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldHasSuffix(FieldClientSecret, v))\n}\n\n// ClientSecretEqualFold applies the EqualFold predicate on the \"client_secret\" field.\nfunc ClientSecretEqualFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEqualFold(FieldClientSecret, v))\n}\n\n// ClientSecretContainsFold applies the ContainsFold predicate on the \"client_secret\" field.\nfunc ClientSecretContainsFold(v string) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldContainsFold(FieldClientSecret, v))\n}\n\n// ScopesIsNil applies the IsNil predicate on the \"scopes\" field.\nfunc ScopesIsNil() predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldIsNull(FieldScopes))\n}\n\n// ScopesNotNil applies the NotNil predicate on the \"scopes\" field.\nfunc ScopesNotNil() predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNotNull(FieldScopes))\n}\n\n// ExpiryEQ applies the EQ predicate on the \"expiry\" field.\nfunc ExpiryEQ(v time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldEQ(FieldExpiry, v))\n}\n\n// ExpiryNEQ applies the NEQ predicate on the \"expiry\" field.\nfunc ExpiryNEQ(v time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNEQ(FieldExpiry, v))\n}\n\n// ExpiryIn applies the In predicate on the \"expiry\" field.\nfunc ExpiryIn(vs ...time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldIn(FieldExpiry, vs...))\n}\n\n// ExpiryNotIn applies the NotIn predicate on the \"expiry\" field.\nfunc ExpiryNotIn(vs ...time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldNotIn(FieldExpiry, vs...))\n}\n\n// ExpiryGT applies the GT predicate on the \"expiry\" field.\nfunc ExpiryGT(v time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGT(FieldExpiry, v))\n}\n\n// ExpiryGTE applies the GTE predicate on the \"expiry\" field.\nfunc ExpiryGTE(v time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldGTE(FieldExpiry, v))\n}\n\n// ExpiryLT applies the LT predicate on the \"expiry\" field.\nfunc ExpiryLT(v time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLT(FieldExpiry, v))\n}\n\n// ExpiryLTE applies the LTE predicate on the \"expiry\" field.\nfunc ExpiryLTE(v time.Time) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.FieldLTE(FieldExpiry, v))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.DeviceRequest) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.DeviceRequest) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.DeviceRequest) predicate.DeviceRequest {\n\treturn predicate.DeviceRequest(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/devicerequest.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n)\n\n// DeviceRequest is the model entity for the DeviceRequest schema.\ntype DeviceRequest struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID int `json:\"id,omitempty\"`\n\t// UserCode holds the value of the \"user_code\" field.\n\tUserCode string `json:\"user_code,omitempty\"`\n\t// DeviceCode holds the value of the \"device_code\" field.\n\tDeviceCode string `json:\"device_code,omitempty\"`\n\t// ClientID holds the value of the \"client_id\" field.\n\tClientID string `json:\"client_id,omitempty\"`\n\t// ClientSecret holds the value of the \"client_secret\" field.\n\tClientSecret string `json:\"client_secret,omitempty\"`\n\t// Scopes holds the value of the \"scopes\" field.\n\tScopes []string `json:\"scopes,omitempty\"`\n\t// Expiry holds the value of the \"expiry\" field.\n\tExpiry       time.Time `json:\"expiry,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*DeviceRequest) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase devicerequest.FieldScopes:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase devicerequest.FieldID:\n\t\t\tvalues[i] = new(sql.NullInt64)\n\t\tcase devicerequest.FieldUserCode, devicerequest.FieldDeviceCode, devicerequest.FieldClientID, devicerequest.FieldClientSecret:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase devicerequest.FieldExpiry:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the DeviceRequest fields.\nfunc (_m *DeviceRequest) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase devicerequest.FieldID:\n\t\t\tvalue, ok := values[i].(*sql.NullInt64)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", value)\n\t\t\t}\n\t\t\t_m.ID = int(value.Int64)\n\t\tcase devicerequest.FieldUserCode:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field user_code\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.UserCode = value.String\n\t\t\t}\n\t\tcase devicerequest.FieldDeviceCode:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field device_code\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.DeviceCode = value.String\n\t\t\t}\n\t\tcase devicerequest.FieldClientID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field client_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClientID = value.String\n\t\t\t}\n\t\tcase devicerequest.FieldClientSecret:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field client_secret\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClientSecret = value.String\n\t\t\t}\n\t\tcase devicerequest.FieldScopes:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field scopes\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.Scopes); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field scopes: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase devicerequest.FieldExpiry:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field expiry\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Expiry = value.Time\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the DeviceRequest.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *DeviceRequest) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this DeviceRequest.\n// Note that you need to call DeviceRequest.Unwrap() before calling this method if this DeviceRequest\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *DeviceRequest) Update() *DeviceRequestUpdateOne {\n\treturn NewDeviceRequestClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the DeviceRequest entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *DeviceRequest) Unwrap() *DeviceRequest {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: DeviceRequest is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *DeviceRequest) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"DeviceRequest(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"user_code=\")\n\tbuilder.WriteString(_m.UserCode)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"device_code=\")\n\tbuilder.WriteString(_m.DeviceCode)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"client_id=\")\n\tbuilder.WriteString(_m.ClientID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"client_secret=\")\n\tbuilder.WriteString(_m.ClientSecret)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"scopes=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Scopes))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"expiry=\")\n\tbuilder.WriteString(_m.Expiry.Format(time.ANSIC))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// DeviceRequests is a parsable slice of DeviceRequest.\ntype DeviceRequests []*DeviceRequest\n"
  },
  {
    "path": "storage/ent/db/devicerequest_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n)\n\n// DeviceRequestCreate is the builder for creating a DeviceRequest entity.\ntype DeviceRequestCreate struct {\n\tconfig\n\tmutation *DeviceRequestMutation\n\thooks    []Hook\n}\n\n// SetUserCode sets the \"user_code\" field.\nfunc (_c *DeviceRequestCreate) SetUserCode(v string) *DeviceRequestCreate {\n\t_c.mutation.SetUserCode(v)\n\treturn _c\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (_c *DeviceRequestCreate) SetDeviceCode(v string) *DeviceRequestCreate {\n\t_c.mutation.SetDeviceCode(v)\n\treturn _c\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_c *DeviceRequestCreate) SetClientID(v string) *DeviceRequestCreate {\n\t_c.mutation.SetClientID(v)\n\treturn _c\n}\n\n// SetClientSecret sets the \"client_secret\" field.\nfunc (_c *DeviceRequestCreate) SetClientSecret(v string) *DeviceRequestCreate {\n\t_c.mutation.SetClientSecret(v)\n\treturn _c\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_c *DeviceRequestCreate) SetScopes(v []string) *DeviceRequestCreate {\n\t_c.mutation.SetScopes(v)\n\treturn _c\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_c *DeviceRequestCreate) SetExpiry(v time.Time) *DeviceRequestCreate {\n\t_c.mutation.SetExpiry(v)\n\treturn _c\n}\n\n// Mutation returns the DeviceRequestMutation object of the builder.\nfunc (_c *DeviceRequestCreate) Mutation() *DeviceRequestMutation {\n\treturn _c.mutation\n}\n\n// Save creates the DeviceRequest in the database.\nfunc (_c *DeviceRequestCreate) Save(ctx context.Context) (*DeviceRequest, error) {\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *DeviceRequestCreate) SaveX(ctx context.Context) *DeviceRequest {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *DeviceRequestCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *DeviceRequestCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *DeviceRequestCreate) check() error {\n\tif _, ok := _c.mutation.UserCode(); !ok {\n\t\treturn &ValidationError{Name: \"user_code\", err: errors.New(`db: missing required field \"DeviceRequest.user_code\"`)}\n\t}\n\tif v, ok := _c.mutation.UserCode(); ok {\n\t\tif err := devicerequest.UserCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.user_code\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.DeviceCode(); !ok {\n\t\treturn &ValidationError{Name: \"device_code\", err: errors.New(`db: missing required field \"DeviceRequest.device_code\"`)}\n\t}\n\tif v, ok := _c.mutation.DeviceCode(); ok {\n\t\tif err := devicerequest.DeviceCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"device_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.device_code\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClientID(); !ok {\n\t\treturn &ValidationError{Name: \"client_id\", err: errors.New(`db: missing required field \"DeviceRequest.client_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ClientID(); ok {\n\t\tif err := devicerequest.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClientSecret(); !ok {\n\t\treturn &ValidationError{Name: \"client_secret\", err: errors.New(`db: missing required field \"DeviceRequest.client_secret\"`)}\n\t}\n\tif v, ok := _c.mutation.ClientSecret(); ok {\n\t\tif err := devicerequest.ClientSecretValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_secret\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.client_secret\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Expiry(); !ok {\n\t\treturn &ValidationError{Name: \"expiry\", err: errors.New(`db: missing required field \"DeviceRequest.expiry\"`)}\n\t}\n\treturn nil\n}\n\nfunc (_c *DeviceRequestCreate) sqlSave(ctx context.Context) (*DeviceRequest, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tid := _spec.ID.Value.(int64)\n\t_node.ID = int(id)\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *DeviceRequestCreate) createSpec() (*DeviceRequest, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &DeviceRequest{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(devicerequest.Table, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt))\n\t)\n\tif value, ok := _c.mutation.UserCode(); ok {\n\t\t_spec.SetField(devicerequest.FieldUserCode, field.TypeString, value)\n\t\t_node.UserCode = value\n\t}\n\tif value, ok := _c.mutation.DeviceCode(); ok {\n\t\t_spec.SetField(devicerequest.FieldDeviceCode, field.TypeString, value)\n\t\t_node.DeviceCode = value\n\t}\n\tif value, ok := _c.mutation.ClientID(); ok {\n\t\t_spec.SetField(devicerequest.FieldClientID, field.TypeString, value)\n\t\t_node.ClientID = value\n\t}\n\tif value, ok := _c.mutation.ClientSecret(); ok {\n\t\t_spec.SetField(devicerequest.FieldClientSecret, field.TypeString, value)\n\t\t_node.ClientSecret = value\n\t}\n\tif value, ok := _c.mutation.Scopes(); ok {\n\t\t_spec.SetField(devicerequest.FieldScopes, field.TypeJSON, value)\n\t\t_node.Scopes = value\n\t}\n\tif value, ok := _c.mutation.Expiry(); ok {\n\t\t_spec.SetField(devicerequest.FieldExpiry, field.TypeTime, value)\n\t\t_node.Expiry = value\n\t}\n\treturn _node, _spec\n}\n\n// DeviceRequestCreateBulk is the builder for creating many DeviceRequest entities in bulk.\ntype DeviceRequestCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*DeviceRequestCreate\n}\n\n// Save creates the DeviceRequest entities in the database.\nfunc (_c *DeviceRequestCreateBulk) Save(ctx context.Context) ([]*DeviceRequest, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*DeviceRequest, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*DeviceRequestMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tif specs[i].ID.Value != nil {\n\t\t\t\t\tid := specs[i].ID.Value.(int64)\n\t\t\t\t\tnodes[i].ID = int(id)\n\t\t\t\t}\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *DeviceRequestCreateBulk) SaveX(ctx context.Context) []*DeviceRequest {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *DeviceRequestCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *DeviceRequestCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/devicerequest_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// DeviceRequestDelete is the builder for deleting a DeviceRequest entity.\ntype DeviceRequestDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *DeviceRequestMutation\n}\n\n// Where appends a list predicates to the DeviceRequestDelete builder.\nfunc (_d *DeviceRequestDelete) Where(ps ...predicate.DeviceRequest) *DeviceRequestDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *DeviceRequestDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *DeviceRequestDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *DeviceRequestDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(devicerequest.Table, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// DeviceRequestDeleteOne is the builder for deleting a single DeviceRequest entity.\ntype DeviceRequestDeleteOne struct {\n\t_d *DeviceRequestDelete\n}\n\n// Where appends a list predicates to the DeviceRequestDelete builder.\nfunc (_d *DeviceRequestDeleteOne) Where(ps ...predicate.DeviceRequest) *DeviceRequestDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *DeviceRequestDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{devicerequest.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *DeviceRequestDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/devicerequest_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// DeviceRequestQuery is the builder for querying DeviceRequest entities.\ntype DeviceRequestQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []devicerequest.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.DeviceRequest\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the DeviceRequestQuery builder.\nfunc (_q *DeviceRequestQuery) Where(ps ...predicate.DeviceRequest) *DeviceRequestQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *DeviceRequestQuery) Limit(limit int) *DeviceRequestQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *DeviceRequestQuery) Offset(offset int) *DeviceRequestQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *DeviceRequestQuery) Unique(unique bool) *DeviceRequestQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *DeviceRequestQuery) Order(o ...devicerequest.OrderOption) *DeviceRequestQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first DeviceRequest entity from the query.\n// Returns a *NotFoundError when no DeviceRequest was found.\nfunc (_q *DeviceRequestQuery) First(ctx context.Context) (*DeviceRequest, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{devicerequest.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) FirstX(ctx context.Context) *DeviceRequest {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first DeviceRequest ID from the query.\n// Returns a *NotFoundError when no DeviceRequest ID was found.\nfunc (_q *DeviceRequestQuery) FirstID(ctx context.Context) (id int, err error) {\n\tvar ids []int\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{devicerequest.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) FirstIDX(ctx context.Context) int {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single DeviceRequest entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one DeviceRequest entity is found.\n// Returns a *NotFoundError when no DeviceRequest entities are found.\nfunc (_q *DeviceRequestQuery) Only(ctx context.Context) (*DeviceRequest, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{devicerequest.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{devicerequest.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) OnlyX(ctx context.Context) *DeviceRequest {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only DeviceRequest ID in the query.\n// Returns a *NotSingularError when more than one DeviceRequest ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *DeviceRequestQuery) OnlyID(ctx context.Context) (id int, err error) {\n\tvar ids []int\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{devicerequest.Label}\n\tdefault:\n\t\terr = &NotSingularError{devicerequest.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) OnlyIDX(ctx context.Context) int {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of DeviceRequests.\nfunc (_q *DeviceRequestQuery) All(ctx context.Context) ([]*DeviceRequest, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*DeviceRequest, *DeviceRequestQuery]()\n\treturn withInterceptors[[]*DeviceRequest](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) AllX(ctx context.Context) []*DeviceRequest {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of DeviceRequest IDs.\nfunc (_q *DeviceRequestQuery) IDs(ctx context.Context) (ids []int, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(devicerequest.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) IDsX(ctx context.Context) []int {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *DeviceRequestQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*DeviceRequestQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *DeviceRequestQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *DeviceRequestQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the DeviceRequestQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *DeviceRequestQuery) Clone() *DeviceRequestQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &DeviceRequestQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]devicerequest.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.DeviceRequest{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserCode string `json:\"user_code,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.DeviceRequest.Query().\n//\t\tGroupBy(devicerequest.FieldUserCode).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *DeviceRequestQuery) GroupBy(field string, fields ...string) *DeviceRequestGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &DeviceRequestGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = devicerequest.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserCode string `json:\"user_code,omitempty\"`\n//\t}\n//\n//\tclient.DeviceRequest.Query().\n//\t\tSelect(devicerequest.FieldUserCode).\n//\t\tScan(ctx, &v)\nfunc (_q *DeviceRequestQuery) Select(fields ...string) *DeviceRequestSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &DeviceRequestSelect{DeviceRequestQuery: _q}\n\tsbuild.label = devicerequest.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a DeviceRequestSelect configured with the given aggregations.\nfunc (_q *DeviceRequestQuery) Aggregate(fns ...AggregateFunc) *DeviceRequestSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *DeviceRequestQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !devicerequest.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *DeviceRequestQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*DeviceRequest, error) {\n\tvar (\n\t\tnodes = []*DeviceRequest{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*DeviceRequest).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &DeviceRequest{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *DeviceRequestQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *DeviceRequestQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(devicerequest.Table, devicerequest.Columns, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, devicerequest.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != devicerequest.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *DeviceRequestQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(devicerequest.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = devicerequest.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// DeviceRequestGroupBy is the group-by builder for DeviceRequest entities.\ntype DeviceRequestGroupBy struct {\n\tselector\n\tbuild *DeviceRequestQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *DeviceRequestGroupBy) Aggregate(fns ...AggregateFunc) *DeviceRequestGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *DeviceRequestGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*DeviceRequestQuery, *DeviceRequestGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *DeviceRequestGroupBy) sqlScan(ctx context.Context, root *DeviceRequestQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// DeviceRequestSelect is the builder for selecting fields of DeviceRequest entities.\ntype DeviceRequestSelect struct {\n\t*DeviceRequestQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *DeviceRequestSelect) Aggregate(fns ...AggregateFunc) *DeviceRequestSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *DeviceRequestSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*DeviceRequestQuery, *DeviceRequestSelect](ctx, _s.DeviceRequestQuery, _s, _s.inters, v)\n}\n\nfunc (_s *DeviceRequestSelect) sqlScan(ctx context.Context, root *DeviceRequestQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/devicerequest_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// DeviceRequestUpdate is the builder for updating DeviceRequest entities.\ntype DeviceRequestUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *DeviceRequestMutation\n}\n\n// Where appends a list predicates to the DeviceRequestUpdate builder.\nfunc (_u *DeviceRequestUpdate) Where(ps ...predicate.DeviceRequest) *DeviceRequestUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetUserCode sets the \"user_code\" field.\nfunc (_u *DeviceRequestUpdate) SetUserCode(v string) *DeviceRequestUpdate {\n\t_u.mutation.SetUserCode(v)\n\treturn _u\n}\n\n// SetNillableUserCode sets the \"user_code\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdate) SetNillableUserCode(v *string) *DeviceRequestUpdate {\n\tif v != nil {\n\t\t_u.SetUserCode(*v)\n\t}\n\treturn _u\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (_u *DeviceRequestUpdate) SetDeviceCode(v string) *DeviceRequestUpdate {\n\t_u.mutation.SetDeviceCode(v)\n\treturn _u\n}\n\n// SetNillableDeviceCode sets the \"device_code\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdate) SetNillableDeviceCode(v *string) *DeviceRequestUpdate {\n\tif v != nil {\n\t\t_u.SetDeviceCode(*v)\n\t}\n\treturn _u\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *DeviceRequestUpdate) SetClientID(v string) *DeviceRequestUpdate {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdate) SetNillableClientID(v *string) *DeviceRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetClientSecret sets the \"client_secret\" field.\nfunc (_u *DeviceRequestUpdate) SetClientSecret(v string) *DeviceRequestUpdate {\n\t_u.mutation.SetClientSecret(v)\n\treturn _u\n}\n\n// SetNillableClientSecret sets the \"client_secret\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdate) SetNillableClientSecret(v *string) *DeviceRequestUpdate {\n\tif v != nil {\n\t\t_u.SetClientSecret(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *DeviceRequestUpdate) SetScopes(v []string) *DeviceRequestUpdate {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *DeviceRequestUpdate) AppendScopes(v []string) *DeviceRequestUpdate {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *DeviceRequestUpdate) ClearScopes() *DeviceRequestUpdate {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *DeviceRequestUpdate) SetExpiry(v time.Time) *DeviceRequestUpdate {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdate) SetNillableExpiry(v *time.Time) *DeviceRequestUpdate {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the DeviceRequestMutation object of the builder.\nfunc (_u *DeviceRequestUpdate) Mutation() *DeviceRequestMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *DeviceRequestUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *DeviceRequestUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *DeviceRequestUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *DeviceRequestUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *DeviceRequestUpdate) check() error {\n\tif v, ok := _u.mutation.UserCode(); ok {\n\t\tif err := devicerequest.UserCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.user_code\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.DeviceCode(); ok {\n\t\tif err := devicerequest.DeviceCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"device_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.device_code\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClientID(); ok {\n\t\tif err := devicerequest.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClientSecret(); ok {\n\t\tif err := devicerequest.ClientSecretValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_secret\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.client_secret\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *DeviceRequestUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(devicerequest.Table, devicerequest.Columns, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserCode(); ok {\n\t\t_spec.SetField(devicerequest.FieldUserCode, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.DeviceCode(); ok {\n\t\t_spec.SetField(devicerequest.FieldDeviceCode, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(devicerequest.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClientSecret(); ok {\n\t\t_spec.SetField(devicerequest.FieldClientSecret, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(devicerequest.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, devicerequest.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(devicerequest.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(devicerequest.FieldExpiry, field.TypeTime, value)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{devicerequest.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// DeviceRequestUpdateOne is the builder for updating a single DeviceRequest entity.\ntype DeviceRequestUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *DeviceRequestMutation\n}\n\n// SetUserCode sets the \"user_code\" field.\nfunc (_u *DeviceRequestUpdateOne) SetUserCode(v string) *DeviceRequestUpdateOne {\n\t_u.mutation.SetUserCode(v)\n\treturn _u\n}\n\n// SetNillableUserCode sets the \"user_code\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdateOne) SetNillableUserCode(v *string) *DeviceRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetUserCode(*v)\n\t}\n\treturn _u\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (_u *DeviceRequestUpdateOne) SetDeviceCode(v string) *DeviceRequestUpdateOne {\n\t_u.mutation.SetDeviceCode(v)\n\treturn _u\n}\n\n// SetNillableDeviceCode sets the \"device_code\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdateOne) SetNillableDeviceCode(v *string) *DeviceRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetDeviceCode(*v)\n\t}\n\treturn _u\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *DeviceRequestUpdateOne) SetClientID(v string) *DeviceRequestUpdateOne {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdateOne) SetNillableClientID(v *string) *DeviceRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetClientSecret sets the \"client_secret\" field.\nfunc (_u *DeviceRequestUpdateOne) SetClientSecret(v string) *DeviceRequestUpdateOne {\n\t_u.mutation.SetClientSecret(v)\n\treturn _u\n}\n\n// SetNillableClientSecret sets the \"client_secret\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdateOne) SetNillableClientSecret(v *string) *DeviceRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetClientSecret(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *DeviceRequestUpdateOne) SetScopes(v []string) *DeviceRequestUpdateOne {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *DeviceRequestUpdateOne) AppendScopes(v []string) *DeviceRequestUpdateOne {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *DeviceRequestUpdateOne) ClearScopes() *DeviceRequestUpdateOne {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *DeviceRequestUpdateOne) SetExpiry(v time.Time) *DeviceRequestUpdateOne {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *DeviceRequestUpdateOne) SetNillableExpiry(v *time.Time) *DeviceRequestUpdateOne {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the DeviceRequestMutation object of the builder.\nfunc (_u *DeviceRequestUpdateOne) Mutation() *DeviceRequestMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the DeviceRequestUpdate builder.\nfunc (_u *DeviceRequestUpdateOne) Where(ps ...predicate.DeviceRequest) *DeviceRequestUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *DeviceRequestUpdateOne) Select(field string, fields ...string) *DeviceRequestUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated DeviceRequest entity.\nfunc (_u *DeviceRequestUpdateOne) Save(ctx context.Context) (*DeviceRequest, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *DeviceRequestUpdateOne) SaveX(ctx context.Context) *DeviceRequest {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *DeviceRequestUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *DeviceRequestUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *DeviceRequestUpdateOne) check() error {\n\tif v, ok := _u.mutation.UserCode(); ok {\n\t\tif err := devicerequest.UserCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.user_code\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.DeviceCode(); ok {\n\t\tif err := devicerequest.DeviceCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"device_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.device_code\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClientID(); ok {\n\t\tif err := devicerequest.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClientSecret(); ok {\n\t\tif err := devicerequest.ClientSecretValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_secret\", err: fmt.Errorf(`db: validator failed for field \"DeviceRequest.client_secret\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *DeviceRequestUpdateOne) sqlSave(ctx context.Context) (_node *DeviceRequest, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(devicerequest.Table, devicerequest.Columns, sqlgraph.NewFieldSpec(devicerequest.FieldID, field.TypeInt))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"DeviceRequest.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, devicerequest.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !devicerequest.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != devicerequest.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserCode(); ok {\n\t\t_spec.SetField(devicerequest.FieldUserCode, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.DeviceCode(); ok {\n\t\t_spec.SetField(devicerequest.FieldDeviceCode, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(devicerequest.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClientSecret(); ok {\n\t\t_spec.SetField(devicerequest.FieldClientSecret, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(devicerequest.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, devicerequest.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(devicerequest.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(devicerequest.FieldExpiry, field.TypeTime, value)\n\t}\n\t_node = &DeviceRequest{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{devicerequest.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/devicetoken/devicetoken.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage devicetoken\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the devicetoken type in the database.\n\tLabel = \"device_token\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldDeviceCode holds the string denoting the device_code field in the database.\n\tFieldDeviceCode = \"device_code\"\n\t// FieldStatus holds the string denoting the status field in the database.\n\tFieldStatus = \"status\"\n\t// FieldToken holds the string denoting the token field in the database.\n\tFieldToken = \"token\"\n\t// FieldExpiry holds the string denoting the expiry field in the database.\n\tFieldExpiry = \"expiry\"\n\t// FieldLastRequest holds the string denoting the last_request field in the database.\n\tFieldLastRequest = \"last_request\"\n\t// FieldPollInterval holds the string denoting the poll_interval field in the database.\n\tFieldPollInterval = \"poll_interval\"\n\t// FieldCodeChallenge holds the string denoting the code_challenge field in the database.\n\tFieldCodeChallenge = \"code_challenge\"\n\t// FieldCodeChallengeMethod holds the string denoting the code_challenge_method field in the database.\n\tFieldCodeChallengeMethod = \"code_challenge_method\"\n\t// Table holds the table name of the devicetoken in the database.\n\tTable = \"device_tokens\"\n)\n\n// Columns holds all SQL columns for devicetoken fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldDeviceCode,\n\tFieldStatus,\n\tFieldToken,\n\tFieldExpiry,\n\tFieldLastRequest,\n\tFieldPollInterval,\n\tFieldCodeChallenge,\n\tFieldCodeChallengeMethod,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// DeviceCodeValidator is a validator for the \"device_code\" field. It is called by the builders before save.\n\tDeviceCodeValidator func(string) error\n\t// StatusValidator is a validator for the \"status\" field. It is called by the builders before save.\n\tStatusValidator func(string) error\n\t// DefaultCodeChallenge holds the default value on creation for the \"code_challenge\" field.\n\tDefaultCodeChallenge string\n\t// DefaultCodeChallengeMethod holds the default value on creation for the \"code_challenge_method\" field.\n\tDefaultCodeChallengeMethod string\n)\n\n// OrderOption defines the ordering options for the DeviceToken queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByDeviceCode orders the results by the device_code field.\nfunc ByDeviceCode(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldDeviceCode, opts...).ToFunc()\n}\n\n// ByStatus orders the results by the status field.\nfunc ByStatus(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldStatus, opts...).ToFunc()\n}\n\n// ByExpiry orders the results by the expiry field.\nfunc ByExpiry(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldExpiry, opts...).ToFunc()\n}\n\n// ByLastRequest orders the results by the last_request field.\nfunc ByLastRequest(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldLastRequest, opts...).ToFunc()\n}\n\n// ByPollInterval orders the results by the poll_interval field.\nfunc ByPollInterval(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldPollInterval, opts...).ToFunc()\n}\n\n// ByCodeChallenge orders the results by the code_challenge field.\nfunc ByCodeChallenge(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCodeChallenge, opts...).ToFunc()\n}\n\n// ByCodeChallengeMethod orders the results by the code_challenge_method field.\nfunc ByCodeChallengeMethod(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCodeChallengeMethod, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/devicetoken/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage devicetoken\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldID, id))\n}\n\n// DeviceCode applies equality check predicate on the \"device_code\" field. It's identical to DeviceCodeEQ.\nfunc DeviceCode(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldDeviceCode, v))\n}\n\n// Status applies equality check predicate on the \"status\" field. It's identical to StatusEQ.\nfunc Status(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldStatus, v))\n}\n\n// Token applies equality check predicate on the \"token\" field. It's identical to TokenEQ.\nfunc Token(v []byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldToken, v))\n}\n\n// Expiry applies equality check predicate on the \"expiry\" field. It's identical to ExpiryEQ.\nfunc Expiry(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldExpiry, v))\n}\n\n// LastRequest applies equality check predicate on the \"last_request\" field. It's identical to LastRequestEQ.\nfunc LastRequest(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldLastRequest, v))\n}\n\n// PollInterval applies equality check predicate on the \"poll_interval\" field. It's identical to PollIntervalEQ.\nfunc PollInterval(v int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldPollInterval, v))\n}\n\n// CodeChallenge applies equality check predicate on the \"code_challenge\" field. It's identical to CodeChallengeEQ.\nfunc CodeChallenge(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeMethod applies equality check predicate on the \"code_challenge_method\" field. It's identical to CodeChallengeMethodEQ.\nfunc CodeChallengeMethod(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldCodeChallengeMethod, v))\n}\n\n// DeviceCodeEQ applies the EQ predicate on the \"device_code\" field.\nfunc DeviceCodeEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldDeviceCode, v))\n}\n\n// DeviceCodeNEQ applies the NEQ predicate on the \"device_code\" field.\nfunc DeviceCodeNEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldDeviceCode, v))\n}\n\n// DeviceCodeIn applies the In predicate on the \"device_code\" field.\nfunc DeviceCodeIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldDeviceCode, vs...))\n}\n\n// DeviceCodeNotIn applies the NotIn predicate on the \"device_code\" field.\nfunc DeviceCodeNotIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldDeviceCode, vs...))\n}\n\n// DeviceCodeGT applies the GT predicate on the \"device_code\" field.\nfunc DeviceCodeGT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldDeviceCode, v))\n}\n\n// DeviceCodeGTE applies the GTE predicate on the \"device_code\" field.\nfunc DeviceCodeGTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldDeviceCode, v))\n}\n\n// DeviceCodeLT applies the LT predicate on the \"device_code\" field.\nfunc DeviceCodeLT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldDeviceCode, v))\n}\n\n// DeviceCodeLTE applies the LTE predicate on the \"device_code\" field.\nfunc DeviceCodeLTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldDeviceCode, v))\n}\n\n// DeviceCodeContains applies the Contains predicate on the \"device_code\" field.\nfunc DeviceCodeContains(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContains(FieldDeviceCode, v))\n}\n\n// DeviceCodeHasPrefix applies the HasPrefix predicate on the \"device_code\" field.\nfunc DeviceCodeHasPrefix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasPrefix(FieldDeviceCode, v))\n}\n\n// DeviceCodeHasSuffix applies the HasSuffix predicate on the \"device_code\" field.\nfunc DeviceCodeHasSuffix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasSuffix(FieldDeviceCode, v))\n}\n\n// DeviceCodeEqualFold applies the EqualFold predicate on the \"device_code\" field.\nfunc DeviceCodeEqualFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEqualFold(FieldDeviceCode, v))\n}\n\n// DeviceCodeContainsFold applies the ContainsFold predicate on the \"device_code\" field.\nfunc DeviceCodeContainsFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContainsFold(FieldDeviceCode, v))\n}\n\n// StatusEQ applies the EQ predicate on the \"status\" field.\nfunc StatusEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldStatus, v))\n}\n\n// StatusNEQ applies the NEQ predicate on the \"status\" field.\nfunc StatusNEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldStatus, v))\n}\n\n// StatusIn applies the In predicate on the \"status\" field.\nfunc StatusIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldStatus, vs...))\n}\n\n// StatusNotIn applies the NotIn predicate on the \"status\" field.\nfunc StatusNotIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldStatus, vs...))\n}\n\n// StatusGT applies the GT predicate on the \"status\" field.\nfunc StatusGT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldStatus, v))\n}\n\n// StatusGTE applies the GTE predicate on the \"status\" field.\nfunc StatusGTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldStatus, v))\n}\n\n// StatusLT applies the LT predicate on the \"status\" field.\nfunc StatusLT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldStatus, v))\n}\n\n// StatusLTE applies the LTE predicate on the \"status\" field.\nfunc StatusLTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldStatus, v))\n}\n\n// StatusContains applies the Contains predicate on the \"status\" field.\nfunc StatusContains(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContains(FieldStatus, v))\n}\n\n// StatusHasPrefix applies the HasPrefix predicate on the \"status\" field.\nfunc StatusHasPrefix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasPrefix(FieldStatus, v))\n}\n\n// StatusHasSuffix applies the HasSuffix predicate on the \"status\" field.\nfunc StatusHasSuffix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasSuffix(FieldStatus, v))\n}\n\n// StatusEqualFold applies the EqualFold predicate on the \"status\" field.\nfunc StatusEqualFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEqualFold(FieldStatus, v))\n}\n\n// StatusContainsFold applies the ContainsFold predicate on the \"status\" field.\nfunc StatusContainsFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContainsFold(FieldStatus, v))\n}\n\n// TokenEQ applies the EQ predicate on the \"token\" field.\nfunc TokenEQ(v []byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldToken, v))\n}\n\n// TokenNEQ applies the NEQ predicate on the \"token\" field.\nfunc TokenNEQ(v []byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldToken, v))\n}\n\n// TokenIn applies the In predicate on the \"token\" field.\nfunc TokenIn(vs ...[]byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldToken, vs...))\n}\n\n// TokenNotIn applies the NotIn predicate on the \"token\" field.\nfunc TokenNotIn(vs ...[]byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldToken, vs...))\n}\n\n// TokenGT applies the GT predicate on the \"token\" field.\nfunc TokenGT(v []byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldToken, v))\n}\n\n// TokenGTE applies the GTE predicate on the \"token\" field.\nfunc TokenGTE(v []byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldToken, v))\n}\n\n// TokenLT applies the LT predicate on the \"token\" field.\nfunc TokenLT(v []byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldToken, v))\n}\n\n// TokenLTE applies the LTE predicate on the \"token\" field.\nfunc TokenLTE(v []byte) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldToken, v))\n}\n\n// TokenIsNil applies the IsNil predicate on the \"token\" field.\nfunc TokenIsNil() predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIsNull(FieldToken))\n}\n\n// TokenNotNil applies the NotNil predicate on the \"token\" field.\nfunc TokenNotNil() predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotNull(FieldToken))\n}\n\n// ExpiryEQ applies the EQ predicate on the \"expiry\" field.\nfunc ExpiryEQ(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldExpiry, v))\n}\n\n// ExpiryNEQ applies the NEQ predicate on the \"expiry\" field.\nfunc ExpiryNEQ(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldExpiry, v))\n}\n\n// ExpiryIn applies the In predicate on the \"expiry\" field.\nfunc ExpiryIn(vs ...time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldExpiry, vs...))\n}\n\n// ExpiryNotIn applies the NotIn predicate on the \"expiry\" field.\nfunc ExpiryNotIn(vs ...time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldExpiry, vs...))\n}\n\n// ExpiryGT applies the GT predicate on the \"expiry\" field.\nfunc ExpiryGT(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldExpiry, v))\n}\n\n// ExpiryGTE applies the GTE predicate on the \"expiry\" field.\nfunc ExpiryGTE(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldExpiry, v))\n}\n\n// ExpiryLT applies the LT predicate on the \"expiry\" field.\nfunc ExpiryLT(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldExpiry, v))\n}\n\n// ExpiryLTE applies the LTE predicate on the \"expiry\" field.\nfunc ExpiryLTE(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldExpiry, v))\n}\n\n// LastRequestEQ applies the EQ predicate on the \"last_request\" field.\nfunc LastRequestEQ(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldLastRequest, v))\n}\n\n// LastRequestNEQ applies the NEQ predicate on the \"last_request\" field.\nfunc LastRequestNEQ(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldLastRequest, v))\n}\n\n// LastRequestIn applies the In predicate on the \"last_request\" field.\nfunc LastRequestIn(vs ...time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldLastRequest, vs...))\n}\n\n// LastRequestNotIn applies the NotIn predicate on the \"last_request\" field.\nfunc LastRequestNotIn(vs ...time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldLastRequest, vs...))\n}\n\n// LastRequestGT applies the GT predicate on the \"last_request\" field.\nfunc LastRequestGT(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldLastRequest, v))\n}\n\n// LastRequestGTE applies the GTE predicate on the \"last_request\" field.\nfunc LastRequestGTE(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldLastRequest, v))\n}\n\n// LastRequestLT applies the LT predicate on the \"last_request\" field.\nfunc LastRequestLT(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldLastRequest, v))\n}\n\n// LastRequestLTE applies the LTE predicate on the \"last_request\" field.\nfunc LastRequestLTE(v time.Time) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldLastRequest, v))\n}\n\n// PollIntervalEQ applies the EQ predicate on the \"poll_interval\" field.\nfunc PollIntervalEQ(v int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldPollInterval, v))\n}\n\n// PollIntervalNEQ applies the NEQ predicate on the \"poll_interval\" field.\nfunc PollIntervalNEQ(v int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldPollInterval, v))\n}\n\n// PollIntervalIn applies the In predicate on the \"poll_interval\" field.\nfunc PollIntervalIn(vs ...int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldPollInterval, vs...))\n}\n\n// PollIntervalNotIn applies the NotIn predicate on the \"poll_interval\" field.\nfunc PollIntervalNotIn(vs ...int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldPollInterval, vs...))\n}\n\n// PollIntervalGT applies the GT predicate on the \"poll_interval\" field.\nfunc PollIntervalGT(v int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldPollInterval, v))\n}\n\n// PollIntervalGTE applies the GTE predicate on the \"poll_interval\" field.\nfunc PollIntervalGTE(v int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldPollInterval, v))\n}\n\n// PollIntervalLT applies the LT predicate on the \"poll_interval\" field.\nfunc PollIntervalLT(v int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldPollInterval, v))\n}\n\n// PollIntervalLTE applies the LTE predicate on the \"poll_interval\" field.\nfunc PollIntervalLTE(v int) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldPollInterval, v))\n}\n\n// CodeChallengeEQ applies the EQ predicate on the \"code_challenge\" field.\nfunc CodeChallengeEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeNEQ applies the NEQ predicate on the \"code_challenge\" field.\nfunc CodeChallengeNEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldCodeChallenge, v))\n}\n\n// CodeChallengeIn applies the In predicate on the \"code_challenge\" field.\nfunc CodeChallengeIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldCodeChallenge, vs...))\n}\n\n// CodeChallengeNotIn applies the NotIn predicate on the \"code_challenge\" field.\nfunc CodeChallengeNotIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldCodeChallenge, vs...))\n}\n\n// CodeChallengeGT applies the GT predicate on the \"code_challenge\" field.\nfunc CodeChallengeGT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldCodeChallenge, v))\n}\n\n// CodeChallengeGTE applies the GTE predicate on the \"code_challenge\" field.\nfunc CodeChallengeGTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldCodeChallenge, v))\n}\n\n// CodeChallengeLT applies the LT predicate on the \"code_challenge\" field.\nfunc CodeChallengeLT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldCodeChallenge, v))\n}\n\n// CodeChallengeLTE applies the LTE predicate on the \"code_challenge\" field.\nfunc CodeChallengeLTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldCodeChallenge, v))\n}\n\n// CodeChallengeContains applies the Contains predicate on the \"code_challenge\" field.\nfunc CodeChallengeContains(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContains(FieldCodeChallenge, v))\n}\n\n// CodeChallengeHasPrefix applies the HasPrefix predicate on the \"code_challenge\" field.\nfunc CodeChallengeHasPrefix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasPrefix(FieldCodeChallenge, v))\n}\n\n// CodeChallengeHasSuffix applies the HasSuffix predicate on the \"code_challenge\" field.\nfunc CodeChallengeHasSuffix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasSuffix(FieldCodeChallenge, v))\n}\n\n// CodeChallengeEqualFold applies the EqualFold predicate on the \"code_challenge\" field.\nfunc CodeChallengeEqualFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEqualFold(FieldCodeChallenge, v))\n}\n\n// CodeChallengeContainsFold applies the ContainsFold predicate on the \"code_challenge\" field.\nfunc CodeChallengeContainsFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContainsFold(FieldCodeChallenge, v))\n}\n\n// CodeChallengeMethodEQ applies the EQ predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEQ(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodNEQ applies the NEQ predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodNEQ(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNEQ(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodIn applies the In predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldIn(FieldCodeChallengeMethod, vs...))\n}\n\n// CodeChallengeMethodNotIn applies the NotIn predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodNotIn(vs ...string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldNotIn(FieldCodeChallengeMethod, vs...))\n}\n\n// CodeChallengeMethodGT applies the GT predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodGT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGT(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodGTE applies the GTE predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodGTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldGTE(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodLT applies the LT predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodLT(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLT(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodLTE applies the LTE predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodLTE(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldLTE(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodContains applies the Contains predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodContains(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContains(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodHasPrefix applies the HasPrefix predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodHasPrefix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasPrefix(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodHasSuffix applies the HasSuffix predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodHasSuffix(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldHasSuffix(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodEqualFold applies the EqualFold predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodEqualFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldEqualFold(FieldCodeChallengeMethod, v))\n}\n\n// CodeChallengeMethodContainsFold applies the ContainsFold predicate on the \"code_challenge_method\" field.\nfunc CodeChallengeMethodContainsFold(v string) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.FieldContainsFold(FieldCodeChallengeMethod, v))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.DeviceToken) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.DeviceToken) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.DeviceToken) predicate.DeviceToken {\n\treturn predicate.DeviceToken(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/devicetoken.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n)\n\n// DeviceToken is the model entity for the DeviceToken schema.\ntype DeviceToken struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID int `json:\"id,omitempty\"`\n\t// DeviceCode holds the value of the \"device_code\" field.\n\tDeviceCode string `json:\"device_code,omitempty\"`\n\t// Status holds the value of the \"status\" field.\n\tStatus string `json:\"status,omitempty\"`\n\t// Token holds the value of the \"token\" field.\n\tToken *[]byte `json:\"token,omitempty\"`\n\t// Expiry holds the value of the \"expiry\" field.\n\tExpiry time.Time `json:\"expiry,omitempty\"`\n\t// LastRequest holds the value of the \"last_request\" field.\n\tLastRequest time.Time `json:\"last_request,omitempty\"`\n\t// PollInterval holds the value of the \"poll_interval\" field.\n\tPollInterval int `json:\"poll_interval,omitempty\"`\n\t// CodeChallenge holds the value of the \"code_challenge\" field.\n\tCodeChallenge string `json:\"code_challenge,omitempty\"`\n\t// CodeChallengeMethod holds the value of the \"code_challenge_method\" field.\n\tCodeChallengeMethod string `json:\"code_challenge_method,omitempty\"`\n\tselectValues        sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*DeviceToken) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase devicetoken.FieldToken:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase devicetoken.FieldID, devicetoken.FieldPollInterval:\n\t\t\tvalues[i] = new(sql.NullInt64)\n\t\tcase devicetoken.FieldDeviceCode, devicetoken.FieldStatus, devicetoken.FieldCodeChallenge, devicetoken.FieldCodeChallengeMethod:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase devicetoken.FieldExpiry, devicetoken.FieldLastRequest:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the DeviceToken fields.\nfunc (_m *DeviceToken) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase devicetoken.FieldID:\n\t\t\tvalue, ok := values[i].(*sql.NullInt64)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", value)\n\t\t\t}\n\t\t\t_m.ID = int(value.Int64)\n\t\tcase devicetoken.FieldDeviceCode:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field device_code\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.DeviceCode = value.String\n\t\t\t}\n\t\tcase devicetoken.FieldStatus:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field status\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Status = value.String\n\t\t\t}\n\t\tcase devicetoken.FieldToken:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field token\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.Token = value\n\t\t\t}\n\t\tcase devicetoken.FieldExpiry:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field expiry\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Expiry = value.Time\n\t\t\t}\n\t\tcase devicetoken.FieldLastRequest:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field last_request\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.LastRequest = value.Time\n\t\t\t}\n\t\tcase devicetoken.FieldPollInterval:\n\t\t\tif value, ok := values[i].(*sql.NullInt64); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field poll_interval\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.PollInterval = int(value.Int64)\n\t\t\t}\n\t\tcase devicetoken.FieldCodeChallenge:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field code_challenge\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CodeChallenge = value.String\n\t\t\t}\n\t\tcase devicetoken.FieldCodeChallengeMethod:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field code_challenge_method\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CodeChallengeMethod = value.String\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the DeviceToken.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *DeviceToken) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this DeviceToken.\n// Note that you need to call DeviceToken.Unwrap() before calling this method if this DeviceToken\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *DeviceToken) Update() *DeviceTokenUpdateOne {\n\treturn NewDeviceTokenClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the DeviceToken entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *DeviceToken) Unwrap() *DeviceToken {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: DeviceToken is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *DeviceToken) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"DeviceToken(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"device_code=\")\n\tbuilder.WriteString(_m.DeviceCode)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"status=\")\n\tbuilder.WriteString(_m.Status)\n\tbuilder.WriteString(\", \")\n\tif v := _m.Token; v != nil {\n\t\tbuilder.WriteString(\"token=\")\n\t\tbuilder.WriteString(fmt.Sprintf(\"%v\", *v))\n\t}\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"expiry=\")\n\tbuilder.WriteString(_m.Expiry.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"last_request=\")\n\tbuilder.WriteString(_m.LastRequest.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"poll_interval=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.PollInterval))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"code_challenge=\")\n\tbuilder.WriteString(_m.CodeChallenge)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"code_challenge_method=\")\n\tbuilder.WriteString(_m.CodeChallengeMethod)\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// DeviceTokens is a parsable slice of DeviceToken.\ntype DeviceTokens []*DeviceToken\n"
  },
  {
    "path": "storage/ent/db/devicetoken_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n)\n\n// DeviceTokenCreate is the builder for creating a DeviceToken entity.\ntype DeviceTokenCreate struct {\n\tconfig\n\tmutation *DeviceTokenMutation\n\thooks    []Hook\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (_c *DeviceTokenCreate) SetDeviceCode(v string) *DeviceTokenCreate {\n\t_c.mutation.SetDeviceCode(v)\n\treturn _c\n}\n\n// SetStatus sets the \"status\" field.\nfunc (_c *DeviceTokenCreate) SetStatus(v string) *DeviceTokenCreate {\n\t_c.mutation.SetStatus(v)\n\treturn _c\n}\n\n// SetToken sets the \"token\" field.\nfunc (_c *DeviceTokenCreate) SetToken(v []byte) *DeviceTokenCreate {\n\t_c.mutation.SetToken(v)\n\treturn _c\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_c *DeviceTokenCreate) SetExpiry(v time.Time) *DeviceTokenCreate {\n\t_c.mutation.SetExpiry(v)\n\treturn _c\n}\n\n// SetLastRequest sets the \"last_request\" field.\nfunc (_c *DeviceTokenCreate) SetLastRequest(v time.Time) *DeviceTokenCreate {\n\t_c.mutation.SetLastRequest(v)\n\treturn _c\n}\n\n// SetPollInterval sets the \"poll_interval\" field.\nfunc (_c *DeviceTokenCreate) SetPollInterval(v int) *DeviceTokenCreate {\n\t_c.mutation.SetPollInterval(v)\n\treturn _c\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_c *DeviceTokenCreate) SetCodeChallenge(v string) *DeviceTokenCreate {\n\t_c.mutation.SetCodeChallenge(v)\n\treturn _c\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_c *DeviceTokenCreate) SetNillableCodeChallenge(v *string) *DeviceTokenCreate {\n\tif v != nil {\n\t\t_c.SetCodeChallenge(*v)\n\t}\n\treturn _c\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_c *DeviceTokenCreate) SetCodeChallengeMethod(v string) *DeviceTokenCreate {\n\t_c.mutation.SetCodeChallengeMethod(v)\n\treturn _c\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_c *DeviceTokenCreate) SetNillableCodeChallengeMethod(v *string) *DeviceTokenCreate {\n\tif v != nil {\n\t\t_c.SetCodeChallengeMethod(*v)\n\t}\n\treturn _c\n}\n\n// Mutation returns the DeviceTokenMutation object of the builder.\nfunc (_c *DeviceTokenCreate) Mutation() *DeviceTokenMutation {\n\treturn _c.mutation\n}\n\n// Save creates the DeviceToken in the database.\nfunc (_c *DeviceTokenCreate) Save(ctx context.Context) (*DeviceToken, error) {\n\t_c.defaults()\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *DeviceTokenCreate) SaveX(ctx context.Context) *DeviceToken {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *DeviceTokenCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *DeviceTokenCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// defaults sets the default values of the builder before save.\nfunc (_c *DeviceTokenCreate) defaults() {\n\tif _, ok := _c.mutation.CodeChallenge(); !ok {\n\t\tv := devicetoken.DefaultCodeChallenge\n\t\t_c.mutation.SetCodeChallenge(v)\n\t}\n\tif _, ok := _c.mutation.CodeChallengeMethod(); !ok {\n\t\tv := devicetoken.DefaultCodeChallengeMethod\n\t\t_c.mutation.SetCodeChallengeMethod(v)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *DeviceTokenCreate) check() error {\n\tif _, ok := _c.mutation.DeviceCode(); !ok {\n\t\treturn &ValidationError{Name: \"device_code\", err: errors.New(`db: missing required field \"DeviceToken.device_code\"`)}\n\t}\n\tif v, ok := _c.mutation.DeviceCode(); ok {\n\t\tif err := devicetoken.DeviceCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"device_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceToken.device_code\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Status(); !ok {\n\t\treturn &ValidationError{Name: \"status\", err: errors.New(`db: missing required field \"DeviceToken.status\"`)}\n\t}\n\tif v, ok := _c.mutation.Status(); ok {\n\t\tif err := devicetoken.StatusValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"status\", err: fmt.Errorf(`db: validator failed for field \"DeviceToken.status\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Expiry(); !ok {\n\t\treturn &ValidationError{Name: \"expiry\", err: errors.New(`db: missing required field \"DeviceToken.expiry\"`)}\n\t}\n\tif _, ok := _c.mutation.LastRequest(); !ok {\n\t\treturn &ValidationError{Name: \"last_request\", err: errors.New(`db: missing required field \"DeviceToken.last_request\"`)}\n\t}\n\tif _, ok := _c.mutation.PollInterval(); !ok {\n\t\treturn &ValidationError{Name: \"poll_interval\", err: errors.New(`db: missing required field \"DeviceToken.poll_interval\"`)}\n\t}\n\tif _, ok := _c.mutation.CodeChallenge(); !ok {\n\t\treturn &ValidationError{Name: \"code_challenge\", err: errors.New(`db: missing required field \"DeviceToken.code_challenge\"`)}\n\t}\n\tif _, ok := _c.mutation.CodeChallengeMethod(); !ok {\n\t\treturn &ValidationError{Name: \"code_challenge_method\", err: errors.New(`db: missing required field \"DeviceToken.code_challenge_method\"`)}\n\t}\n\treturn nil\n}\n\nfunc (_c *DeviceTokenCreate) sqlSave(ctx context.Context) (*DeviceToken, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tid := _spec.ID.Value.(int64)\n\t_node.ID = int(id)\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *DeviceTokenCreate) createSpec() (*DeviceToken, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &DeviceToken{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(devicetoken.Table, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt))\n\t)\n\tif value, ok := _c.mutation.DeviceCode(); ok {\n\t\t_spec.SetField(devicetoken.FieldDeviceCode, field.TypeString, value)\n\t\t_node.DeviceCode = value\n\t}\n\tif value, ok := _c.mutation.Status(); ok {\n\t\t_spec.SetField(devicetoken.FieldStatus, field.TypeString, value)\n\t\t_node.Status = value\n\t}\n\tif value, ok := _c.mutation.Token(); ok {\n\t\t_spec.SetField(devicetoken.FieldToken, field.TypeBytes, value)\n\t\t_node.Token = &value\n\t}\n\tif value, ok := _c.mutation.Expiry(); ok {\n\t\t_spec.SetField(devicetoken.FieldExpiry, field.TypeTime, value)\n\t\t_node.Expiry = value\n\t}\n\tif value, ok := _c.mutation.LastRequest(); ok {\n\t\t_spec.SetField(devicetoken.FieldLastRequest, field.TypeTime, value)\n\t\t_node.LastRequest = value\n\t}\n\tif value, ok := _c.mutation.PollInterval(); ok {\n\t\t_spec.SetField(devicetoken.FieldPollInterval, field.TypeInt, value)\n\t\t_node.PollInterval = value\n\t}\n\tif value, ok := _c.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(devicetoken.FieldCodeChallenge, field.TypeString, value)\n\t\t_node.CodeChallenge = value\n\t}\n\tif value, ok := _c.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(devicetoken.FieldCodeChallengeMethod, field.TypeString, value)\n\t\t_node.CodeChallengeMethod = value\n\t}\n\treturn _node, _spec\n}\n\n// DeviceTokenCreateBulk is the builder for creating many DeviceToken entities in bulk.\ntype DeviceTokenCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*DeviceTokenCreate\n}\n\n// Save creates the DeviceToken entities in the database.\nfunc (_c *DeviceTokenCreateBulk) Save(ctx context.Context) ([]*DeviceToken, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*DeviceToken, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tbuilder.defaults()\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*DeviceTokenMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tif specs[i].ID.Value != nil {\n\t\t\t\t\tid := specs[i].ID.Value.(int64)\n\t\t\t\t\tnodes[i].ID = int(id)\n\t\t\t\t}\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *DeviceTokenCreateBulk) SaveX(ctx context.Context) []*DeviceToken {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *DeviceTokenCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *DeviceTokenCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/devicetoken_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// DeviceTokenDelete is the builder for deleting a DeviceToken entity.\ntype DeviceTokenDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *DeviceTokenMutation\n}\n\n// Where appends a list predicates to the DeviceTokenDelete builder.\nfunc (_d *DeviceTokenDelete) Where(ps ...predicate.DeviceToken) *DeviceTokenDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *DeviceTokenDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *DeviceTokenDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *DeviceTokenDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(devicetoken.Table, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// DeviceTokenDeleteOne is the builder for deleting a single DeviceToken entity.\ntype DeviceTokenDeleteOne struct {\n\t_d *DeviceTokenDelete\n}\n\n// Where appends a list predicates to the DeviceTokenDelete builder.\nfunc (_d *DeviceTokenDeleteOne) Where(ps ...predicate.DeviceToken) *DeviceTokenDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *DeviceTokenDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{devicetoken.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *DeviceTokenDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/devicetoken_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// DeviceTokenQuery is the builder for querying DeviceToken entities.\ntype DeviceTokenQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []devicetoken.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.DeviceToken\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the DeviceTokenQuery builder.\nfunc (_q *DeviceTokenQuery) Where(ps ...predicate.DeviceToken) *DeviceTokenQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *DeviceTokenQuery) Limit(limit int) *DeviceTokenQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *DeviceTokenQuery) Offset(offset int) *DeviceTokenQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *DeviceTokenQuery) Unique(unique bool) *DeviceTokenQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *DeviceTokenQuery) Order(o ...devicetoken.OrderOption) *DeviceTokenQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first DeviceToken entity from the query.\n// Returns a *NotFoundError when no DeviceToken was found.\nfunc (_q *DeviceTokenQuery) First(ctx context.Context) (*DeviceToken, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{devicetoken.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) FirstX(ctx context.Context) *DeviceToken {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first DeviceToken ID from the query.\n// Returns a *NotFoundError when no DeviceToken ID was found.\nfunc (_q *DeviceTokenQuery) FirstID(ctx context.Context) (id int, err error) {\n\tvar ids []int\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{devicetoken.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) FirstIDX(ctx context.Context) int {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single DeviceToken entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one DeviceToken entity is found.\n// Returns a *NotFoundError when no DeviceToken entities are found.\nfunc (_q *DeviceTokenQuery) Only(ctx context.Context) (*DeviceToken, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{devicetoken.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{devicetoken.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) OnlyX(ctx context.Context) *DeviceToken {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only DeviceToken ID in the query.\n// Returns a *NotSingularError when more than one DeviceToken ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *DeviceTokenQuery) OnlyID(ctx context.Context) (id int, err error) {\n\tvar ids []int\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{devicetoken.Label}\n\tdefault:\n\t\terr = &NotSingularError{devicetoken.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) OnlyIDX(ctx context.Context) int {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of DeviceTokens.\nfunc (_q *DeviceTokenQuery) All(ctx context.Context) ([]*DeviceToken, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*DeviceToken, *DeviceTokenQuery]()\n\treturn withInterceptors[[]*DeviceToken](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) AllX(ctx context.Context) []*DeviceToken {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of DeviceToken IDs.\nfunc (_q *DeviceTokenQuery) IDs(ctx context.Context) (ids []int, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(devicetoken.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) IDsX(ctx context.Context) []int {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *DeviceTokenQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*DeviceTokenQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *DeviceTokenQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *DeviceTokenQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the DeviceTokenQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *DeviceTokenQuery) Clone() *DeviceTokenQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &DeviceTokenQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]devicetoken.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.DeviceToken{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tDeviceCode string `json:\"device_code,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.DeviceToken.Query().\n//\t\tGroupBy(devicetoken.FieldDeviceCode).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *DeviceTokenQuery) GroupBy(field string, fields ...string) *DeviceTokenGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &DeviceTokenGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = devicetoken.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tDeviceCode string `json:\"device_code,omitempty\"`\n//\t}\n//\n//\tclient.DeviceToken.Query().\n//\t\tSelect(devicetoken.FieldDeviceCode).\n//\t\tScan(ctx, &v)\nfunc (_q *DeviceTokenQuery) Select(fields ...string) *DeviceTokenSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &DeviceTokenSelect{DeviceTokenQuery: _q}\n\tsbuild.label = devicetoken.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a DeviceTokenSelect configured with the given aggregations.\nfunc (_q *DeviceTokenQuery) Aggregate(fns ...AggregateFunc) *DeviceTokenSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *DeviceTokenQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !devicetoken.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *DeviceTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*DeviceToken, error) {\n\tvar (\n\t\tnodes = []*DeviceToken{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*DeviceToken).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &DeviceToken{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *DeviceTokenQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *DeviceTokenQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(devicetoken.Table, devicetoken.Columns, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, devicetoken.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != devicetoken.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *DeviceTokenQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(devicetoken.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = devicetoken.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// DeviceTokenGroupBy is the group-by builder for DeviceToken entities.\ntype DeviceTokenGroupBy struct {\n\tselector\n\tbuild *DeviceTokenQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *DeviceTokenGroupBy) Aggregate(fns ...AggregateFunc) *DeviceTokenGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *DeviceTokenGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*DeviceTokenQuery, *DeviceTokenGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *DeviceTokenGroupBy) sqlScan(ctx context.Context, root *DeviceTokenQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// DeviceTokenSelect is the builder for selecting fields of DeviceToken entities.\ntype DeviceTokenSelect struct {\n\t*DeviceTokenQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *DeviceTokenSelect) Aggregate(fns ...AggregateFunc) *DeviceTokenSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *DeviceTokenSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*DeviceTokenQuery, *DeviceTokenSelect](ctx, _s.DeviceTokenQuery, _s, _s.inters, v)\n}\n\nfunc (_s *DeviceTokenSelect) sqlScan(ctx context.Context, root *DeviceTokenQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/devicetoken_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// DeviceTokenUpdate is the builder for updating DeviceToken entities.\ntype DeviceTokenUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *DeviceTokenMutation\n}\n\n// Where appends a list predicates to the DeviceTokenUpdate builder.\nfunc (_u *DeviceTokenUpdate) Where(ps ...predicate.DeviceToken) *DeviceTokenUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (_u *DeviceTokenUpdate) SetDeviceCode(v string) *DeviceTokenUpdate {\n\t_u.mutation.SetDeviceCode(v)\n\treturn _u\n}\n\n// SetNillableDeviceCode sets the \"device_code\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdate) SetNillableDeviceCode(v *string) *DeviceTokenUpdate {\n\tif v != nil {\n\t\t_u.SetDeviceCode(*v)\n\t}\n\treturn _u\n}\n\n// SetStatus sets the \"status\" field.\nfunc (_u *DeviceTokenUpdate) SetStatus(v string) *DeviceTokenUpdate {\n\t_u.mutation.SetStatus(v)\n\treturn _u\n}\n\n// SetNillableStatus sets the \"status\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdate) SetNillableStatus(v *string) *DeviceTokenUpdate {\n\tif v != nil {\n\t\t_u.SetStatus(*v)\n\t}\n\treturn _u\n}\n\n// SetToken sets the \"token\" field.\nfunc (_u *DeviceTokenUpdate) SetToken(v []byte) *DeviceTokenUpdate {\n\t_u.mutation.SetToken(v)\n\treturn _u\n}\n\n// ClearToken clears the value of the \"token\" field.\nfunc (_u *DeviceTokenUpdate) ClearToken() *DeviceTokenUpdate {\n\t_u.mutation.ClearToken()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *DeviceTokenUpdate) SetExpiry(v time.Time) *DeviceTokenUpdate {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdate) SetNillableExpiry(v *time.Time) *DeviceTokenUpdate {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetLastRequest sets the \"last_request\" field.\nfunc (_u *DeviceTokenUpdate) SetLastRequest(v time.Time) *DeviceTokenUpdate {\n\t_u.mutation.SetLastRequest(v)\n\treturn _u\n}\n\n// SetNillableLastRequest sets the \"last_request\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdate) SetNillableLastRequest(v *time.Time) *DeviceTokenUpdate {\n\tif v != nil {\n\t\t_u.SetLastRequest(*v)\n\t}\n\treturn _u\n}\n\n// SetPollInterval sets the \"poll_interval\" field.\nfunc (_u *DeviceTokenUpdate) SetPollInterval(v int) *DeviceTokenUpdate {\n\t_u.mutation.ResetPollInterval()\n\t_u.mutation.SetPollInterval(v)\n\treturn _u\n}\n\n// SetNillablePollInterval sets the \"poll_interval\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdate) SetNillablePollInterval(v *int) *DeviceTokenUpdate {\n\tif v != nil {\n\t\t_u.SetPollInterval(*v)\n\t}\n\treturn _u\n}\n\n// AddPollInterval adds value to the \"poll_interval\" field.\nfunc (_u *DeviceTokenUpdate) AddPollInterval(v int) *DeviceTokenUpdate {\n\t_u.mutation.AddPollInterval(v)\n\treturn _u\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_u *DeviceTokenUpdate) SetCodeChallenge(v string) *DeviceTokenUpdate {\n\t_u.mutation.SetCodeChallenge(v)\n\treturn _u\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdate) SetNillableCodeChallenge(v *string) *DeviceTokenUpdate {\n\tif v != nil {\n\t\t_u.SetCodeChallenge(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_u *DeviceTokenUpdate) SetCodeChallengeMethod(v string) *DeviceTokenUpdate {\n\t_u.mutation.SetCodeChallengeMethod(v)\n\treturn _u\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdate) SetNillableCodeChallengeMethod(v *string) *DeviceTokenUpdate {\n\tif v != nil {\n\t\t_u.SetCodeChallengeMethod(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the DeviceTokenMutation object of the builder.\nfunc (_u *DeviceTokenUpdate) Mutation() *DeviceTokenMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *DeviceTokenUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *DeviceTokenUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *DeviceTokenUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *DeviceTokenUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *DeviceTokenUpdate) check() error {\n\tif v, ok := _u.mutation.DeviceCode(); ok {\n\t\tif err := devicetoken.DeviceCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"device_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceToken.device_code\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Status(); ok {\n\t\tif err := devicetoken.StatusValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"status\", err: fmt.Errorf(`db: validator failed for field \"DeviceToken.status\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *DeviceTokenUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(devicetoken.Table, devicetoken.Columns, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.DeviceCode(); ok {\n\t\t_spec.SetField(devicetoken.FieldDeviceCode, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Status(); ok {\n\t\t_spec.SetField(devicetoken.FieldStatus, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Token(); ok {\n\t\t_spec.SetField(devicetoken.FieldToken, field.TypeBytes, value)\n\t}\n\tif _u.mutation.TokenCleared() {\n\t\t_spec.ClearField(devicetoken.FieldToken, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(devicetoken.FieldExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastRequest(); ok {\n\t\t_spec.SetField(devicetoken.FieldLastRequest, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.PollInterval(); ok {\n\t\t_spec.SetField(devicetoken.FieldPollInterval, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.AddedPollInterval(); ok {\n\t\t_spec.AddField(devicetoken.FieldPollInterval, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(devicetoken.FieldCodeChallenge, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(devicetoken.FieldCodeChallengeMethod, field.TypeString, value)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{devicetoken.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// DeviceTokenUpdateOne is the builder for updating a single DeviceToken entity.\ntype DeviceTokenUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *DeviceTokenMutation\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (_u *DeviceTokenUpdateOne) SetDeviceCode(v string) *DeviceTokenUpdateOne {\n\t_u.mutation.SetDeviceCode(v)\n\treturn _u\n}\n\n// SetNillableDeviceCode sets the \"device_code\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdateOne) SetNillableDeviceCode(v *string) *DeviceTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetDeviceCode(*v)\n\t}\n\treturn _u\n}\n\n// SetStatus sets the \"status\" field.\nfunc (_u *DeviceTokenUpdateOne) SetStatus(v string) *DeviceTokenUpdateOne {\n\t_u.mutation.SetStatus(v)\n\treturn _u\n}\n\n// SetNillableStatus sets the \"status\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdateOne) SetNillableStatus(v *string) *DeviceTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetStatus(*v)\n\t}\n\treturn _u\n}\n\n// SetToken sets the \"token\" field.\nfunc (_u *DeviceTokenUpdateOne) SetToken(v []byte) *DeviceTokenUpdateOne {\n\t_u.mutation.SetToken(v)\n\treturn _u\n}\n\n// ClearToken clears the value of the \"token\" field.\nfunc (_u *DeviceTokenUpdateOne) ClearToken() *DeviceTokenUpdateOne {\n\t_u.mutation.ClearToken()\n\treturn _u\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (_u *DeviceTokenUpdateOne) SetExpiry(v time.Time) *DeviceTokenUpdateOne {\n\t_u.mutation.SetExpiry(v)\n\treturn _u\n}\n\n// SetNillableExpiry sets the \"expiry\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdateOne) SetNillableExpiry(v *time.Time) *DeviceTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetExpiry(*v)\n\t}\n\treturn _u\n}\n\n// SetLastRequest sets the \"last_request\" field.\nfunc (_u *DeviceTokenUpdateOne) SetLastRequest(v time.Time) *DeviceTokenUpdateOne {\n\t_u.mutation.SetLastRequest(v)\n\treturn _u\n}\n\n// SetNillableLastRequest sets the \"last_request\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdateOne) SetNillableLastRequest(v *time.Time) *DeviceTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetLastRequest(*v)\n\t}\n\treturn _u\n}\n\n// SetPollInterval sets the \"poll_interval\" field.\nfunc (_u *DeviceTokenUpdateOne) SetPollInterval(v int) *DeviceTokenUpdateOne {\n\t_u.mutation.ResetPollInterval()\n\t_u.mutation.SetPollInterval(v)\n\treturn _u\n}\n\n// SetNillablePollInterval sets the \"poll_interval\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdateOne) SetNillablePollInterval(v *int) *DeviceTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetPollInterval(*v)\n\t}\n\treturn _u\n}\n\n// AddPollInterval adds value to the \"poll_interval\" field.\nfunc (_u *DeviceTokenUpdateOne) AddPollInterval(v int) *DeviceTokenUpdateOne {\n\t_u.mutation.AddPollInterval(v)\n\treturn _u\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (_u *DeviceTokenUpdateOne) SetCodeChallenge(v string) *DeviceTokenUpdateOne {\n\t_u.mutation.SetCodeChallenge(v)\n\treturn _u\n}\n\n// SetNillableCodeChallenge sets the \"code_challenge\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdateOne) SetNillableCodeChallenge(v *string) *DeviceTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetCodeChallenge(*v)\n\t}\n\treturn _u\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (_u *DeviceTokenUpdateOne) SetCodeChallengeMethod(v string) *DeviceTokenUpdateOne {\n\t_u.mutation.SetCodeChallengeMethod(v)\n\treturn _u\n}\n\n// SetNillableCodeChallengeMethod sets the \"code_challenge_method\" field if the given value is not nil.\nfunc (_u *DeviceTokenUpdateOne) SetNillableCodeChallengeMethod(v *string) *DeviceTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetCodeChallengeMethod(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the DeviceTokenMutation object of the builder.\nfunc (_u *DeviceTokenUpdateOne) Mutation() *DeviceTokenMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the DeviceTokenUpdate builder.\nfunc (_u *DeviceTokenUpdateOne) Where(ps ...predicate.DeviceToken) *DeviceTokenUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *DeviceTokenUpdateOne) Select(field string, fields ...string) *DeviceTokenUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated DeviceToken entity.\nfunc (_u *DeviceTokenUpdateOne) Save(ctx context.Context) (*DeviceToken, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *DeviceTokenUpdateOne) SaveX(ctx context.Context) *DeviceToken {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *DeviceTokenUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *DeviceTokenUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *DeviceTokenUpdateOne) check() error {\n\tif v, ok := _u.mutation.DeviceCode(); ok {\n\t\tif err := devicetoken.DeviceCodeValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"device_code\", err: fmt.Errorf(`db: validator failed for field \"DeviceToken.device_code\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Status(); ok {\n\t\tif err := devicetoken.StatusValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"status\", err: fmt.Errorf(`db: validator failed for field \"DeviceToken.status\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *DeviceTokenUpdateOne) sqlSave(ctx context.Context) (_node *DeviceToken, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(devicetoken.Table, devicetoken.Columns, sqlgraph.NewFieldSpec(devicetoken.FieldID, field.TypeInt))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"DeviceToken.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, devicetoken.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !devicetoken.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != devicetoken.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.DeviceCode(); ok {\n\t\t_spec.SetField(devicetoken.FieldDeviceCode, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Status(); ok {\n\t\t_spec.SetField(devicetoken.FieldStatus, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Token(); ok {\n\t\t_spec.SetField(devicetoken.FieldToken, field.TypeBytes, value)\n\t}\n\tif _u.mutation.TokenCleared() {\n\t\t_spec.ClearField(devicetoken.FieldToken, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Expiry(); ok {\n\t\t_spec.SetField(devicetoken.FieldExpiry, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastRequest(); ok {\n\t\t_spec.SetField(devicetoken.FieldLastRequest, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.PollInterval(); ok {\n\t\t_spec.SetField(devicetoken.FieldPollInterval, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.AddedPollInterval(); ok {\n\t\t_spec.AddField(devicetoken.FieldPollInterval, field.TypeInt, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallenge(); ok {\n\t\t_spec.SetField(devicetoken.FieldCodeChallenge, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CodeChallengeMethod(); ok {\n\t\t_spec.SetField(devicetoken.FieldCodeChallengeMethod, field.TypeString, value)\n\t}\n\t_node = &DeviceToken{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{devicetoken.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/ent.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"sync\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n)\n\n// ent aliases to avoid import conflicts in user's code.\ntype (\n\tOp            = ent.Op\n\tHook          = ent.Hook\n\tValue         = ent.Value\n\tQuery         = ent.Query\n\tQueryContext  = ent.QueryContext\n\tQuerier       = ent.Querier\n\tQuerierFunc   = ent.QuerierFunc\n\tInterceptor   = ent.Interceptor\n\tInterceptFunc = ent.InterceptFunc\n\tTraverser     = ent.Traverser\n\tTraverseFunc  = ent.TraverseFunc\n\tPolicy        = ent.Policy\n\tMutator       = ent.Mutator\n\tMutation      = ent.Mutation\n\tMutateFunc    = ent.MutateFunc\n)\n\ntype clientCtxKey struct{}\n\n// FromContext returns a Client stored inside a context, or nil if there isn't one.\nfunc FromContext(ctx context.Context) *Client {\n\tc, _ := ctx.Value(clientCtxKey{}).(*Client)\n\treturn c\n}\n\n// NewContext returns a new context with the given Client attached.\nfunc NewContext(parent context.Context, c *Client) context.Context {\n\treturn context.WithValue(parent, clientCtxKey{}, c)\n}\n\ntype txCtxKey struct{}\n\n// TxFromContext returns a Tx stored inside a context, or nil if there isn't one.\nfunc TxFromContext(ctx context.Context) *Tx {\n\ttx, _ := ctx.Value(txCtxKey{}).(*Tx)\n\treturn tx\n}\n\n// NewTxContext returns a new context with the given Tx attached.\nfunc NewTxContext(parent context.Context, tx *Tx) context.Context {\n\treturn context.WithValue(parent, txCtxKey{}, tx)\n}\n\n// OrderFunc applies an ordering on the sql selector.\n// Deprecated: Use Asc/Desc functions or the package builders instead.\ntype OrderFunc func(*sql.Selector)\n\nvar (\n\tinitCheck   sync.Once\n\tcolumnCheck sql.ColumnCheck\n)\n\n// checkColumn checks if the column exists in the given table.\nfunc checkColumn(t, c string) error {\n\tinitCheck.Do(func() {\n\t\tcolumnCheck = sql.NewColumnCheck(map[string]func(string) bool{\n\t\t\tauthcode.Table:       authcode.ValidColumn,\n\t\t\tauthrequest.Table:    authrequest.ValidColumn,\n\t\t\tauthsession.Table:    authsession.ValidColumn,\n\t\t\tconnector.Table:      connector.ValidColumn,\n\t\t\tdevicerequest.Table:  devicerequest.ValidColumn,\n\t\t\tdevicetoken.Table:    devicetoken.ValidColumn,\n\t\t\tkeys.Table:           keys.ValidColumn,\n\t\t\toauth2client.Table:   oauth2client.ValidColumn,\n\t\t\tofflinesession.Table: offlinesession.ValidColumn,\n\t\t\tpassword.Table:       password.ValidColumn,\n\t\t\trefreshtoken.Table:   refreshtoken.ValidColumn,\n\t\t\tuseridentity.Table:   useridentity.ValidColumn,\n\t\t})\n\t})\n\treturn columnCheck(t, c)\n}\n\n// Asc applies the given fields in ASC order.\nfunc Asc(fields ...string) func(*sql.Selector) {\n\treturn func(s *sql.Selector) {\n\t\tfor _, f := range fields {\n\t\t\tif err := checkColumn(s.TableName(), f); err != nil {\n\t\t\t\ts.AddError(&ValidationError{Name: f, err: fmt.Errorf(\"db: %w\", err)})\n\t\t\t}\n\t\t\ts.OrderBy(sql.Asc(s.C(f)))\n\t\t}\n\t}\n}\n\n// Desc applies the given fields in DESC order.\nfunc Desc(fields ...string) func(*sql.Selector) {\n\treturn func(s *sql.Selector) {\n\t\tfor _, f := range fields {\n\t\t\tif err := checkColumn(s.TableName(), f); err != nil {\n\t\t\t\ts.AddError(&ValidationError{Name: f, err: fmt.Errorf(\"db: %w\", err)})\n\t\t\t}\n\t\t\ts.OrderBy(sql.Desc(s.C(f)))\n\t\t}\n\t}\n}\n\n// AggregateFunc applies an aggregation step on the group-by traversal/selector.\ntype AggregateFunc func(*sql.Selector) string\n\n// As is a pseudo aggregation function for renaming another other functions with custom names. For example:\n//\n//\tGroupBy(field1, field2).\n//\tAggregate(db.As(db.Sum(field1), \"sum_field1\"), (db.As(db.Sum(field2), \"sum_field2\")).\n//\tScan(ctx, &v)\nfunc As(fn AggregateFunc, end string) AggregateFunc {\n\treturn func(s *sql.Selector) string {\n\t\treturn sql.As(fn(s), end)\n\t}\n}\n\n// Count applies the \"count\" aggregation function on each group.\nfunc Count() AggregateFunc {\n\treturn func(s *sql.Selector) string {\n\t\treturn sql.Count(\"*\")\n\t}\n}\n\n// Max applies the \"max\" aggregation function on the given field of each group.\nfunc Max(field string) AggregateFunc {\n\treturn func(s *sql.Selector) string {\n\t\tif err := checkColumn(s.TableName(), field); err != nil {\n\t\t\ts.AddError(&ValidationError{Name: field, err: fmt.Errorf(\"db: %w\", err)})\n\t\t\treturn \"\"\n\t\t}\n\t\treturn sql.Max(s.C(field))\n\t}\n}\n\n// Mean applies the \"mean\" aggregation function on the given field of each group.\nfunc Mean(field string) AggregateFunc {\n\treturn func(s *sql.Selector) string {\n\t\tif err := checkColumn(s.TableName(), field); err != nil {\n\t\t\ts.AddError(&ValidationError{Name: field, err: fmt.Errorf(\"db: %w\", err)})\n\t\t\treturn \"\"\n\t\t}\n\t\treturn sql.Avg(s.C(field))\n\t}\n}\n\n// Min applies the \"min\" aggregation function on the given field of each group.\nfunc Min(field string) AggregateFunc {\n\treturn func(s *sql.Selector) string {\n\t\tif err := checkColumn(s.TableName(), field); err != nil {\n\t\t\ts.AddError(&ValidationError{Name: field, err: fmt.Errorf(\"db: %w\", err)})\n\t\t\treturn \"\"\n\t\t}\n\t\treturn sql.Min(s.C(field))\n\t}\n}\n\n// Sum applies the \"sum\" aggregation function on the given field of each group.\nfunc Sum(field string) AggregateFunc {\n\treturn func(s *sql.Selector) string {\n\t\tif err := checkColumn(s.TableName(), field); err != nil {\n\t\t\ts.AddError(&ValidationError{Name: field, err: fmt.Errorf(\"db: %w\", err)})\n\t\t\treturn \"\"\n\t\t}\n\t\treturn sql.Sum(s.C(field))\n\t}\n}\n\n// ValidationError returns when validating a field or edge fails.\ntype ValidationError struct {\n\tName string // Field or edge name.\n\terr  error\n}\n\n// Error implements the error interface.\nfunc (e *ValidationError) Error() string {\n\treturn e.err.Error()\n}\n\n// Unwrap implements the errors.Wrapper interface.\nfunc (e *ValidationError) Unwrap() error {\n\treturn e.err\n}\n\n// IsValidationError returns a boolean indicating whether the error is a validation error.\nfunc IsValidationError(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tvar e *ValidationError\n\treturn errors.As(err, &e)\n}\n\n// NotFoundError returns when trying to fetch a specific entity and it was not found in the database.\ntype NotFoundError struct {\n\tlabel string\n}\n\n// Error implements the error interface.\nfunc (e *NotFoundError) Error() string {\n\treturn \"db: \" + e.label + \" not found\"\n}\n\n// IsNotFound returns a boolean indicating whether the error is a not found error.\nfunc IsNotFound(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tvar e *NotFoundError\n\treturn errors.As(err, &e)\n}\n\n// MaskNotFound masks not found error.\nfunc MaskNotFound(err error) error {\n\tif IsNotFound(err) {\n\t\treturn nil\n\t}\n\treturn err\n}\n\n// NotSingularError returns when trying to fetch a singular entity and more then one was found in the database.\ntype NotSingularError struct {\n\tlabel string\n}\n\n// Error implements the error interface.\nfunc (e *NotSingularError) Error() string {\n\treturn \"db: \" + e.label + \" not singular\"\n}\n\n// IsNotSingular returns a boolean indicating whether the error is a not singular error.\nfunc IsNotSingular(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tvar e *NotSingularError\n\treturn errors.As(err, &e)\n}\n\n// NotLoadedError returns when trying to get a node that was not loaded by the query.\ntype NotLoadedError struct {\n\tedge string\n}\n\n// Error implements the error interface.\nfunc (e *NotLoadedError) Error() string {\n\treturn \"db: \" + e.edge + \" edge was not loaded\"\n}\n\n// IsNotLoaded returns a boolean indicating whether the error is a not loaded error.\nfunc IsNotLoaded(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tvar e *NotLoadedError\n\treturn errors.As(err, &e)\n}\n\n// ConstraintError returns when trying to create/update one or more entities and\n// one or more of their constraints failed. For example, violation of edge or\n// field uniqueness.\ntype ConstraintError struct {\n\tmsg  string\n\twrap error\n}\n\n// Error implements the error interface.\nfunc (e ConstraintError) Error() string {\n\treturn \"db: constraint failed: \" + e.msg\n}\n\n// Unwrap implements the errors.Wrapper interface.\nfunc (e *ConstraintError) Unwrap() error {\n\treturn e.wrap\n}\n\n// IsConstraintError returns a boolean indicating whether the error is a constraint failure.\nfunc IsConstraintError(err error) bool {\n\tif err == nil {\n\t\treturn false\n\t}\n\tvar e *ConstraintError\n\treturn errors.As(err, &e)\n}\n\n// selector embedded by the different Select/GroupBy builders.\ntype selector struct {\n\tlabel string\n\tflds  *[]string\n\tfns   []AggregateFunc\n\tscan  func(context.Context, any) error\n}\n\n// ScanX is like Scan, but panics if an error occurs.\nfunc (s *selector) ScanX(ctx context.Context, v any) {\n\tif err := s.scan(ctx, v); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// Strings returns list of strings from a selector. It is only allowed when selecting one field.\nfunc (s *selector) Strings(ctx context.Context) ([]string, error) {\n\tif len(*s.flds) > 1 {\n\t\treturn nil, errors.New(\"db: Strings is not achievable when selecting more than 1 field\")\n\t}\n\tvar v []string\n\tif err := s.scan(ctx, &v); err != nil {\n\t\treturn nil, err\n\t}\n\treturn v, nil\n}\n\n// StringsX is like Strings, but panics if an error occurs.\nfunc (s *selector) StringsX(ctx context.Context) []string {\n\tv, err := s.Strings(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// String returns a single string from a selector. It is only allowed when selecting one field.\nfunc (s *selector) String(ctx context.Context) (_ string, err error) {\n\tvar v []string\n\tif v, err = s.Strings(ctx); err != nil {\n\t\treturn\n\t}\n\tswitch len(v) {\n\tcase 1:\n\t\treturn v[0], nil\n\tcase 0:\n\t\terr = &NotFoundError{s.label}\n\tdefault:\n\t\terr = fmt.Errorf(\"db: Strings returned %d results when one was expected\", len(v))\n\t}\n\treturn\n}\n\n// StringX is like String, but panics if an error occurs.\nfunc (s *selector) StringX(ctx context.Context) string {\n\tv, err := s.String(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Ints returns list of ints from a selector. It is only allowed when selecting one field.\nfunc (s *selector) Ints(ctx context.Context) ([]int, error) {\n\tif len(*s.flds) > 1 {\n\t\treturn nil, errors.New(\"db: Ints is not achievable when selecting more than 1 field\")\n\t}\n\tvar v []int\n\tif err := s.scan(ctx, &v); err != nil {\n\t\treturn nil, err\n\t}\n\treturn v, nil\n}\n\n// IntsX is like Ints, but panics if an error occurs.\nfunc (s *selector) IntsX(ctx context.Context) []int {\n\tv, err := s.Ints(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Int returns a single int from a selector. It is only allowed when selecting one field.\nfunc (s *selector) Int(ctx context.Context) (_ int, err error) {\n\tvar v []int\n\tif v, err = s.Ints(ctx); err != nil {\n\t\treturn\n\t}\n\tswitch len(v) {\n\tcase 1:\n\t\treturn v[0], nil\n\tcase 0:\n\t\terr = &NotFoundError{s.label}\n\tdefault:\n\t\terr = fmt.Errorf(\"db: Ints returned %d results when one was expected\", len(v))\n\t}\n\treturn\n}\n\n// IntX is like Int, but panics if an error occurs.\nfunc (s *selector) IntX(ctx context.Context) int {\n\tv, err := s.Int(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Float64s returns list of float64s from a selector. It is only allowed when selecting one field.\nfunc (s *selector) Float64s(ctx context.Context) ([]float64, error) {\n\tif len(*s.flds) > 1 {\n\t\treturn nil, errors.New(\"db: Float64s is not achievable when selecting more than 1 field\")\n\t}\n\tvar v []float64\n\tif err := s.scan(ctx, &v); err != nil {\n\t\treturn nil, err\n\t}\n\treturn v, nil\n}\n\n// Float64sX is like Float64s, but panics if an error occurs.\nfunc (s *selector) Float64sX(ctx context.Context) []float64 {\n\tv, err := s.Float64s(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Float64 returns a single float64 from a selector. It is only allowed when selecting one field.\nfunc (s *selector) Float64(ctx context.Context) (_ float64, err error) {\n\tvar v []float64\n\tif v, err = s.Float64s(ctx); err != nil {\n\t\treturn\n\t}\n\tswitch len(v) {\n\tcase 1:\n\t\treturn v[0], nil\n\tcase 0:\n\t\terr = &NotFoundError{s.label}\n\tdefault:\n\t\terr = fmt.Errorf(\"db: Float64s returned %d results when one was expected\", len(v))\n\t}\n\treturn\n}\n\n// Float64X is like Float64, but panics if an error occurs.\nfunc (s *selector) Float64X(ctx context.Context) float64 {\n\tv, err := s.Float64(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Bools returns list of bools from a selector. It is only allowed when selecting one field.\nfunc (s *selector) Bools(ctx context.Context) ([]bool, error) {\n\tif len(*s.flds) > 1 {\n\t\treturn nil, errors.New(\"db: Bools is not achievable when selecting more than 1 field\")\n\t}\n\tvar v []bool\n\tif err := s.scan(ctx, &v); err != nil {\n\t\treturn nil, err\n\t}\n\treturn v, nil\n}\n\n// BoolsX is like Bools, but panics if an error occurs.\nfunc (s *selector) BoolsX(ctx context.Context) []bool {\n\tv, err := s.Bools(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Bool returns a single bool from a selector. It is only allowed when selecting one field.\nfunc (s *selector) Bool(ctx context.Context) (_ bool, err error) {\n\tvar v []bool\n\tif v, err = s.Bools(ctx); err != nil {\n\t\treturn\n\t}\n\tswitch len(v) {\n\tcase 1:\n\t\treturn v[0], nil\n\tcase 0:\n\t\terr = &NotFoundError{s.label}\n\tdefault:\n\t\terr = fmt.Errorf(\"db: Bools returned %d results when one was expected\", len(v))\n\t}\n\treturn\n}\n\n// BoolX is like Bool, but panics if an error occurs.\nfunc (s *selector) BoolX(ctx context.Context) bool {\n\tv, err := s.Bool(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// withHooks invokes the builder operation with the given hooks, if any.\nfunc withHooks[V Value, M any, PM interface {\n\t*M\n\tMutation\n}](ctx context.Context, exec func(context.Context) (V, error), mutation PM, hooks []Hook) (value V, err error) {\n\tif len(hooks) == 0 {\n\t\treturn exec(ctx)\n\t}\n\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\tmutationT, ok := any(m).(PM)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t}\n\t\t// Set the mutation to the builder.\n\t\t*mutation = *mutationT\n\t\treturn exec(ctx)\n\t})\n\tfor i := len(hooks) - 1; i >= 0; i-- {\n\t\tif hooks[i] == nil {\n\t\t\treturn value, fmt.Errorf(\"ent: uninitialized hook (forgotten import ent/runtime?)\")\n\t\t}\n\t\tmut = hooks[i](mut)\n\t}\n\tv, err := mut.Mutate(ctx, mutation)\n\tif err != nil {\n\t\treturn value, err\n\t}\n\tnv, ok := v.(V)\n\tif !ok {\n\t\treturn value, fmt.Errorf(\"unexpected node type %T returned from %T\", v, mutation)\n\t}\n\treturn nv, nil\n}\n\n// setContextOp returns a new context with the given QueryContext attached (including its op) in case it does not exist.\nfunc setContextOp(ctx context.Context, qc *QueryContext, op string) context.Context {\n\tif ent.QueryFromContext(ctx) == nil {\n\t\tqc.Op = op\n\t\tctx = ent.NewQueryContext(ctx, qc)\n\t}\n\treturn ctx\n}\n\nfunc querierAll[V Value, Q interface {\n\tsqlAll(context.Context, ...queryHook) (V, error)\n}]() Querier {\n\treturn QuerierFunc(func(ctx context.Context, q Query) (Value, error) {\n\t\tquery, ok := q.(Q)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unexpected query type %T\", q)\n\t\t}\n\t\treturn query.sqlAll(ctx)\n\t})\n}\n\nfunc querierCount[Q interface {\n\tsqlCount(context.Context) (int, error)\n}]() Querier {\n\treturn QuerierFunc(func(ctx context.Context, q Query) (Value, error) {\n\t\tquery, ok := q.(Q)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unexpected query type %T\", q)\n\t\t}\n\t\treturn query.sqlCount(ctx)\n\t})\n}\n\nfunc withInterceptors[V Value](ctx context.Context, q Query, qr Querier, inters []Interceptor) (v V, err error) {\n\tfor i := len(inters) - 1; i >= 0; i-- {\n\t\tqr = inters[i].Intercept(qr)\n\t}\n\trv, err := qr.Query(ctx, q)\n\tif err != nil {\n\t\treturn v, err\n\t}\n\tvt, ok := rv.(V)\n\tif !ok {\n\t\treturn v, fmt.Errorf(\"unexpected type %T returned from %T. expected type: %T\", vt, q, v)\n\t}\n\treturn vt, nil\n}\n\nfunc scanWithInterceptors[Q1 ent.Query, Q2 interface {\n\tsqlScan(context.Context, Q1, any) error\n}](ctx context.Context, rootQuery Q1, selectOrGroup Q2, inters []Interceptor, v any) error {\n\trv := reflect.ValueOf(v)\n\tvar qr Querier = QuerierFunc(func(ctx context.Context, q Query) (Value, error) {\n\t\tquery, ok := q.(Q1)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"unexpected query type %T\", q)\n\t\t}\n\t\tif err := selectOrGroup.sqlScan(ctx, query, v); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tif k := rv.Kind(); k == reflect.Pointer && rv.Elem().CanInterface() {\n\t\t\treturn rv.Elem().Interface(), nil\n\t\t}\n\t\treturn v, nil\n\t})\n\tfor i := len(inters) - 1; i >= 0; i-- {\n\t\tqr = inters[i].Intercept(qr)\n\t}\n\tvv, err := qr.Query(ctx, rootQuery)\n\tif err != nil {\n\t\treturn err\n\t}\n\tswitch rv2 := reflect.ValueOf(vv); {\n\tcase rv.IsNil(), rv2.IsNil(), rv.Kind() != reflect.Pointer:\n\tcase rv.Type() == rv2.Type():\n\t\trv.Elem().Set(rv2.Elem())\n\tcase rv.Elem().Type() == rv2.Type():\n\t\trv.Elem().Set(rv2)\n\t}\n\treturn nil\n}\n\n// queryHook describes an internal hook for the different sqlAll methods.\ntype queryHook func(context.Context, *sqlgraph.QuerySpec)\n"
  },
  {
    "path": "storage/ent/db/enttest/enttest.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage enttest\n\nimport (\n\t\"context\"\n\n\t\"github.com/dexidp/dex/storage/ent/db\"\n\t// required by schema hooks.\n\t_ \"github.com/dexidp/dex/storage/ent/db/runtime\"\n\n\t\"entgo.io/ent/dialect/sql/schema\"\n\t\"github.com/dexidp/dex/storage/ent/db/migrate\"\n)\n\ntype (\n\t// TestingT is the interface that is shared between\n\t// testing.T and testing.B and used by enttest.\n\tTestingT interface {\n\t\tFailNow()\n\t\tError(...any)\n\t}\n\n\t// Option configures client creation.\n\tOption func(*options)\n\n\toptions struct {\n\t\topts        []db.Option\n\t\tmigrateOpts []schema.MigrateOption\n\t}\n)\n\n// WithOptions forwards options to client creation.\nfunc WithOptions(opts ...db.Option) Option {\n\treturn func(o *options) {\n\t\to.opts = append(o.opts, opts...)\n\t}\n}\n\n// WithMigrateOptions forwards options to auto migration.\nfunc WithMigrateOptions(opts ...schema.MigrateOption) Option {\n\treturn func(o *options) {\n\t\to.migrateOpts = append(o.migrateOpts, opts...)\n\t}\n}\n\nfunc newOptions(opts []Option) *options {\n\to := &options{}\n\tfor _, opt := range opts {\n\t\topt(o)\n\t}\n\treturn o\n}\n\n// Open calls db.Open and auto-run migration.\nfunc Open(t TestingT, driverName, dataSourceName string, opts ...Option) *db.Client {\n\to := newOptions(opts)\n\tc, err := db.Open(driverName, dataSourceName, o.opts...)\n\tif err != nil {\n\t\tt.Error(err)\n\t\tt.FailNow()\n\t}\n\tmigrateSchema(t, c, o)\n\treturn c\n}\n\n// NewClient calls db.NewClient and auto-run migration.\nfunc NewClient(t TestingT, opts ...Option) *db.Client {\n\to := newOptions(opts)\n\tc := db.NewClient(o.opts...)\n\tmigrateSchema(t, c, o)\n\treturn c\n}\nfunc migrateSchema(t TestingT, c *db.Client, o *options) {\n\ttables, err := schema.CopyTables(migrate.Tables)\n\tif err != nil {\n\t\tt.Error(err)\n\t\tt.FailNow()\n\t}\n\tif err := migrate.Create(context.Background(), c.Schema, tables, o.migrateOpts...); err != nil {\n\t\tt.Error(err)\n\t\tt.FailNow()\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/hook/hook.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage hook\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/dexidp/dex/storage/ent/db\"\n)\n\n// The AuthCodeFunc type is an adapter to allow the use of ordinary\n// function as AuthCode mutator.\ntype AuthCodeFunc func(context.Context, *db.AuthCodeMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f AuthCodeFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.AuthCodeMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.AuthCodeMutation\", m)\n}\n\n// The AuthRequestFunc type is an adapter to allow the use of ordinary\n// function as AuthRequest mutator.\ntype AuthRequestFunc func(context.Context, *db.AuthRequestMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f AuthRequestFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.AuthRequestMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.AuthRequestMutation\", m)\n}\n\n// The AuthSessionFunc type is an adapter to allow the use of ordinary\n// function as AuthSession mutator.\ntype AuthSessionFunc func(context.Context, *db.AuthSessionMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f AuthSessionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.AuthSessionMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.AuthSessionMutation\", m)\n}\n\n// The ConnectorFunc type is an adapter to allow the use of ordinary\n// function as Connector mutator.\ntype ConnectorFunc func(context.Context, *db.ConnectorMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f ConnectorFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.ConnectorMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.ConnectorMutation\", m)\n}\n\n// The DeviceRequestFunc type is an adapter to allow the use of ordinary\n// function as DeviceRequest mutator.\ntype DeviceRequestFunc func(context.Context, *db.DeviceRequestMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f DeviceRequestFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.DeviceRequestMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.DeviceRequestMutation\", m)\n}\n\n// The DeviceTokenFunc type is an adapter to allow the use of ordinary\n// function as DeviceToken mutator.\ntype DeviceTokenFunc func(context.Context, *db.DeviceTokenMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f DeviceTokenFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.DeviceTokenMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.DeviceTokenMutation\", m)\n}\n\n// The KeysFunc type is an adapter to allow the use of ordinary\n// function as Keys mutator.\ntype KeysFunc func(context.Context, *db.KeysMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f KeysFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.KeysMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.KeysMutation\", m)\n}\n\n// The OAuth2ClientFunc type is an adapter to allow the use of ordinary\n// function as OAuth2Client mutator.\ntype OAuth2ClientFunc func(context.Context, *db.OAuth2ClientMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f OAuth2ClientFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.OAuth2ClientMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.OAuth2ClientMutation\", m)\n}\n\n// The OfflineSessionFunc type is an adapter to allow the use of ordinary\n// function as OfflineSession mutator.\ntype OfflineSessionFunc func(context.Context, *db.OfflineSessionMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f OfflineSessionFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.OfflineSessionMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.OfflineSessionMutation\", m)\n}\n\n// The PasswordFunc type is an adapter to allow the use of ordinary\n// function as Password mutator.\ntype PasswordFunc func(context.Context, *db.PasswordMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f PasswordFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.PasswordMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.PasswordMutation\", m)\n}\n\n// The RefreshTokenFunc type is an adapter to allow the use of ordinary\n// function as RefreshToken mutator.\ntype RefreshTokenFunc func(context.Context, *db.RefreshTokenMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f RefreshTokenFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.RefreshTokenMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.RefreshTokenMutation\", m)\n}\n\n// The UserIdentityFunc type is an adapter to allow the use of ordinary\n// function as UserIdentity mutator.\ntype UserIdentityFunc func(context.Context, *db.UserIdentityMutation) (db.Value, error)\n\n// Mutate calls f(ctx, m).\nfunc (f UserIdentityFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) {\n\tif mv, ok := m.(*db.UserIdentityMutation); ok {\n\t\treturn f(ctx, mv)\n\t}\n\treturn nil, fmt.Errorf(\"unexpected mutation type %T. expect *db.UserIdentityMutation\", m)\n}\n\n// Condition is a hook condition function.\ntype Condition func(context.Context, db.Mutation) bool\n\n// And groups conditions with the AND operator.\nfunc And(first, second Condition, rest ...Condition) Condition {\n\treturn func(ctx context.Context, m db.Mutation) bool {\n\t\tif !first(ctx, m) || !second(ctx, m) {\n\t\t\treturn false\n\t\t}\n\t\tfor _, cond := range rest {\n\t\t\tif !cond(ctx, m) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n}\n\n// Or groups conditions with the OR operator.\nfunc Or(first, second Condition, rest ...Condition) Condition {\n\treturn func(ctx context.Context, m db.Mutation) bool {\n\t\tif first(ctx, m) || second(ctx, m) {\n\t\t\treturn true\n\t\t}\n\t\tfor _, cond := range rest {\n\t\t\tif cond(ctx, m) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t\treturn false\n\t}\n}\n\n// Not negates a given condition.\nfunc Not(cond Condition) Condition {\n\treturn func(ctx context.Context, m db.Mutation) bool {\n\t\treturn !cond(ctx, m)\n\t}\n}\n\n// HasOp is a condition testing mutation operation.\nfunc HasOp(op db.Op) Condition {\n\treturn func(_ context.Context, m db.Mutation) bool {\n\t\treturn m.Op().Is(op)\n\t}\n}\n\n// HasAddedFields is a condition validating `.AddedField` on fields.\nfunc HasAddedFields(field string, fields ...string) Condition {\n\treturn func(_ context.Context, m db.Mutation) bool {\n\t\tif _, exists := m.AddedField(field); !exists {\n\t\t\treturn false\n\t\t}\n\t\tfor _, field := range fields {\n\t\t\tif _, exists := m.AddedField(field); !exists {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n}\n\n// HasClearedFields is a condition validating `.FieldCleared` on fields.\nfunc HasClearedFields(field string, fields ...string) Condition {\n\treturn func(_ context.Context, m db.Mutation) bool {\n\t\tif exists := m.FieldCleared(field); !exists {\n\t\t\treturn false\n\t\t}\n\t\tfor _, field := range fields {\n\t\t\tif exists := m.FieldCleared(field); !exists {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n}\n\n// HasFields is a condition validating `.Field` on fields.\nfunc HasFields(field string, fields ...string) Condition {\n\treturn func(_ context.Context, m db.Mutation) bool {\n\t\tif _, exists := m.Field(field); !exists {\n\t\t\treturn false\n\t\t}\n\t\tfor _, field := range fields {\n\t\t\tif _, exists := m.Field(field); !exists {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n}\n\n// If executes the given hook under condition.\n//\n//\thook.If(ComputeAverage, And(HasFields(...), HasAddedFields(...)))\nfunc If(hk db.Hook, cond Condition) db.Hook {\n\treturn func(next db.Mutator) db.Mutator {\n\t\treturn db.MutateFunc(func(ctx context.Context, m db.Mutation) (db.Value, error) {\n\t\t\tif cond(ctx, m) {\n\t\t\t\treturn hk(next).Mutate(ctx, m)\n\t\t\t}\n\t\t\treturn next.Mutate(ctx, m)\n\t\t})\n\t}\n}\n\n// On executes the given hook only for the given operation.\n//\n//\thook.On(Log, db.Delete|db.Create)\nfunc On(hk db.Hook, op db.Op) db.Hook {\n\treturn If(hk, HasOp(op))\n}\n\n// Unless skips the given hook only for the given operation.\n//\n//\thook.Unless(Log, db.Update|db.UpdateOne)\nfunc Unless(hk db.Hook, op db.Op) db.Hook {\n\treturn If(hk, Not(HasOp(op)))\n}\n\n// FixedError is a hook returning a fixed error.\nfunc FixedError(err error) db.Hook {\n\treturn func(db.Mutator) db.Mutator {\n\t\treturn db.MutateFunc(func(context.Context, db.Mutation) (db.Value, error) {\n\t\t\treturn nil, err\n\t\t})\n\t}\n}\n\n// Reject returns a hook that rejects all operations that match op.\n//\n//\tfunc (T) Hooks() []db.Hook {\n//\t\treturn []db.Hook{\n//\t\t\tReject(db.Delete|db.Update),\n//\t\t}\n//\t}\nfunc Reject(op db.Op) db.Hook {\n\thk := FixedError(fmt.Errorf(\"%s operation is not allowed\", op))\n\treturn On(hk, op)\n}\n\n// Chain acts as a list of hooks and is effectively immutable.\n// Once created, it will always hold the same set of hooks in the same order.\ntype Chain struct {\n\thooks []db.Hook\n}\n\n// NewChain creates a new chain of hooks.\nfunc NewChain(hooks ...db.Hook) Chain {\n\treturn Chain{append([]db.Hook(nil), hooks...)}\n}\n\n// Hook chains the list of hooks and returns the final hook.\nfunc (c Chain) Hook() db.Hook {\n\treturn func(mutator db.Mutator) db.Mutator {\n\t\tfor i := len(c.hooks) - 1; i >= 0; i-- {\n\t\t\tmutator = c.hooks[i](mutator)\n\t\t}\n\t\treturn mutator\n\t}\n}\n\n// Append extends a chain, adding the specified hook\n// as the last ones in the mutation flow.\nfunc (c Chain) Append(hooks ...db.Hook) Chain {\n\tnewHooks := make([]db.Hook, 0, len(c.hooks)+len(hooks))\n\tnewHooks = append(newHooks, c.hooks...)\n\tnewHooks = append(newHooks, hooks...)\n\treturn Chain{newHooks}\n}\n\n// Extend extends a chain, adding the specified chain\n// as the last ones in the mutation flow.\nfunc (c Chain) Extend(chain Chain) Chain {\n\treturn c.Append(chain.hooks...)\n}\n"
  },
  {
    "path": "storage/ent/db/keys/keys.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage keys\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the keys type in the database.\n\tLabel = \"keys\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldVerificationKeys holds the string denoting the verification_keys field in the database.\n\tFieldVerificationKeys = \"verification_keys\"\n\t// FieldSigningKey holds the string denoting the signing_key field in the database.\n\tFieldSigningKey = \"signing_key\"\n\t// FieldSigningKeyPub holds the string denoting the signing_key_pub field in the database.\n\tFieldSigningKeyPub = \"signing_key_pub\"\n\t// FieldNextRotation holds the string denoting the next_rotation field in the database.\n\tFieldNextRotation = \"next_rotation\"\n\t// Table holds the table name of the keys in the database.\n\tTable = \"keys\"\n)\n\n// Columns holds all SQL columns for keys fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldVerificationKeys,\n\tFieldSigningKey,\n\tFieldSigningKeyPub,\n\tFieldNextRotation,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the Keys queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByNextRotation orders the results by the next_rotation field.\nfunc ByNextRotation(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldNextRotation, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/keys/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage keys\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.Keys {\n\treturn predicate.Keys(sql.FieldContainsFold(FieldID, id))\n}\n\n// NextRotation applies equality check predicate on the \"next_rotation\" field. It's identical to NextRotationEQ.\nfunc NextRotation(v time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldEQ(FieldNextRotation, v))\n}\n\n// NextRotationEQ applies the EQ predicate on the \"next_rotation\" field.\nfunc NextRotationEQ(v time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldEQ(FieldNextRotation, v))\n}\n\n// NextRotationNEQ applies the NEQ predicate on the \"next_rotation\" field.\nfunc NextRotationNEQ(v time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldNEQ(FieldNextRotation, v))\n}\n\n// NextRotationIn applies the In predicate on the \"next_rotation\" field.\nfunc NextRotationIn(vs ...time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldIn(FieldNextRotation, vs...))\n}\n\n// NextRotationNotIn applies the NotIn predicate on the \"next_rotation\" field.\nfunc NextRotationNotIn(vs ...time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldNotIn(FieldNextRotation, vs...))\n}\n\n// NextRotationGT applies the GT predicate on the \"next_rotation\" field.\nfunc NextRotationGT(v time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldGT(FieldNextRotation, v))\n}\n\n// NextRotationGTE applies the GTE predicate on the \"next_rotation\" field.\nfunc NextRotationGTE(v time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldGTE(FieldNextRotation, v))\n}\n\n// NextRotationLT applies the LT predicate on the \"next_rotation\" field.\nfunc NextRotationLT(v time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldLT(FieldNextRotation, v))\n}\n\n// NextRotationLTE applies the LTE predicate on the \"next_rotation\" field.\nfunc NextRotationLTE(v time.Time) predicate.Keys {\n\treturn predicate.Keys(sql.FieldLTE(FieldNextRotation, v))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.Keys) predicate.Keys {\n\treturn predicate.Keys(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.Keys) predicate.Keys {\n\treturn predicate.Keys(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.Keys) predicate.Keys {\n\treturn predicate.Keys(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/keys.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\tjose \"github.com/go-jose/go-jose/v4\"\n)\n\n// Keys is the model entity for the Keys schema.\ntype Keys struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// VerificationKeys holds the value of the \"verification_keys\" field.\n\tVerificationKeys []storage.VerificationKey `json:\"verification_keys,omitempty\"`\n\t// SigningKey holds the value of the \"signing_key\" field.\n\tSigningKey jose.JSONWebKey `json:\"signing_key,omitempty\"`\n\t// SigningKeyPub holds the value of the \"signing_key_pub\" field.\n\tSigningKeyPub jose.JSONWebKey `json:\"signing_key_pub,omitempty\"`\n\t// NextRotation holds the value of the \"next_rotation\" field.\n\tNextRotation time.Time `json:\"next_rotation,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*Keys) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase keys.FieldVerificationKeys, keys.FieldSigningKey, keys.FieldSigningKeyPub:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase keys.FieldID:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase keys.FieldNextRotation:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the Keys fields.\nfunc (_m *Keys) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase keys.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase keys.FieldVerificationKeys:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field verification_keys\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.VerificationKeys); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field verification_keys: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase keys.FieldSigningKey:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field signing_key\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.SigningKey); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field signing_key: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase keys.FieldSigningKeyPub:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field signing_key_pub\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.SigningKeyPub); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field signing_key_pub: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase keys.FieldNextRotation:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field next_rotation\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.NextRotation = value.Time\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the Keys.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *Keys) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this Keys.\n// Note that you need to call Keys.Unwrap() before calling this method if this Keys\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *Keys) Update() *KeysUpdateOne {\n\treturn NewKeysClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the Keys entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *Keys) Unwrap() *Keys {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: Keys is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *Keys) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"Keys(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"verification_keys=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.VerificationKeys))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"signing_key=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.SigningKey))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"signing_key_pub=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.SigningKeyPub))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"next_rotation=\")\n\tbuilder.WriteString(_m.NextRotation.Format(time.ANSIC))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// KeysSlice is a parsable slice of Keys.\ntype KeysSlice []*Keys\n"
  },
  {
    "path": "storage/ent/db/keys_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\tjose \"github.com/go-jose/go-jose/v4\"\n)\n\n// KeysCreate is the builder for creating a Keys entity.\ntype KeysCreate struct {\n\tconfig\n\tmutation *KeysMutation\n\thooks    []Hook\n}\n\n// SetVerificationKeys sets the \"verification_keys\" field.\nfunc (_c *KeysCreate) SetVerificationKeys(v []storage.VerificationKey) *KeysCreate {\n\t_c.mutation.SetVerificationKeys(v)\n\treturn _c\n}\n\n// SetSigningKey sets the \"signing_key\" field.\nfunc (_c *KeysCreate) SetSigningKey(v jose.JSONWebKey) *KeysCreate {\n\t_c.mutation.SetSigningKey(v)\n\treturn _c\n}\n\n// SetSigningKeyPub sets the \"signing_key_pub\" field.\nfunc (_c *KeysCreate) SetSigningKeyPub(v jose.JSONWebKey) *KeysCreate {\n\t_c.mutation.SetSigningKeyPub(v)\n\treturn _c\n}\n\n// SetNextRotation sets the \"next_rotation\" field.\nfunc (_c *KeysCreate) SetNextRotation(v time.Time) *KeysCreate {\n\t_c.mutation.SetNextRotation(v)\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *KeysCreate) SetID(v string) *KeysCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the KeysMutation object of the builder.\nfunc (_c *KeysCreate) Mutation() *KeysMutation {\n\treturn _c.mutation\n}\n\n// Save creates the Keys in the database.\nfunc (_c *KeysCreate) Save(ctx context.Context) (*Keys, error) {\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *KeysCreate) SaveX(ctx context.Context) *Keys {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *KeysCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *KeysCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *KeysCreate) check() error {\n\tif _, ok := _c.mutation.VerificationKeys(); !ok {\n\t\treturn &ValidationError{Name: \"verification_keys\", err: errors.New(`db: missing required field \"Keys.verification_keys\"`)}\n\t}\n\tif _, ok := _c.mutation.SigningKey(); !ok {\n\t\treturn &ValidationError{Name: \"signing_key\", err: errors.New(`db: missing required field \"Keys.signing_key\"`)}\n\t}\n\tif _, ok := _c.mutation.SigningKeyPub(); !ok {\n\t\treturn &ValidationError{Name: \"signing_key_pub\", err: errors.New(`db: missing required field \"Keys.signing_key_pub\"`)}\n\t}\n\tif _, ok := _c.mutation.NextRotation(); !ok {\n\t\treturn &ValidationError{Name: \"next_rotation\", err: errors.New(`db: missing required field \"Keys.next_rotation\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := keys.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"Keys.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *KeysCreate) sqlSave(ctx context.Context) (*Keys, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected Keys.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *KeysCreate) createSpec() (*Keys, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &Keys{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(keys.Table, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.VerificationKeys(); ok {\n\t\t_spec.SetField(keys.FieldVerificationKeys, field.TypeJSON, value)\n\t\t_node.VerificationKeys = value\n\t}\n\tif value, ok := _c.mutation.SigningKey(); ok {\n\t\t_spec.SetField(keys.FieldSigningKey, field.TypeJSON, value)\n\t\t_node.SigningKey = value\n\t}\n\tif value, ok := _c.mutation.SigningKeyPub(); ok {\n\t\t_spec.SetField(keys.FieldSigningKeyPub, field.TypeJSON, value)\n\t\t_node.SigningKeyPub = value\n\t}\n\tif value, ok := _c.mutation.NextRotation(); ok {\n\t\t_spec.SetField(keys.FieldNextRotation, field.TypeTime, value)\n\t\t_node.NextRotation = value\n\t}\n\treturn _node, _spec\n}\n\n// KeysCreateBulk is the builder for creating many Keys entities in bulk.\ntype KeysCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*KeysCreate\n}\n\n// Save creates the Keys entities in the database.\nfunc (_c *KeysCreateBulk) Save(ctx context.Context) ([]*Keys, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*Keys, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*KeysMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *KeysCreateBulk) SaveX(ctx context.Context) []*Keys {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *KeysCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *KeysCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/keys_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// KeysDelete is the builder for deleting a Keys entity.\ntype KeysDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *KeysMutation\n}\n\n// Where appends a list predicates to the KeysDelete builder.\nfunc (_d *KeysDelete) Where(ps ...predicate.Keys) *KeysDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *KeysDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *KeysDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *KeysDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(keys.Table, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// KeysDeleteOne is the builder for deleting a single Keys entity.\ntype KeysDeleteOne struct {\n\t_d *KeysDelete\n}\n\n// Where appends a list predicates to the KeysDelete builder.\nfunc (_d *KeysDeleteOne) Where(ps ...predicate.Keys) *KeysDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *KeysDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{keys.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *KeysDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/keys_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// KeysQuery is the builder for querying Keys entities.\ntype KeysQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []keys.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.Keys\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the KeysQuery builder.\nfunc (_q *KeysQuery) Where(ps ...predicate.Keys) *KeysQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *KeysQuery) Limit(limit int) *KeysQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *KeysQuery) Offset(offset int) *KeysQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *KeysQuery) Unique(unique bool) *KeysQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *KeysQuery) Order(o ...keys.OrderOption) *KeysQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first Keys entity from the query.\n// Returns a *NotFoundError when no Keys was found.\nfunc (_q *KeysQuery) First(ctx context.Context) (*Keys, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{keys.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *KeysQuery) FirstX(ctx context.Context) *Keys {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first Keys ID from the query.\n// Returns a *NotFoundError when no Keys ID was found.\nfunc (_q *KeysQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{keys.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *KeysQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single Keys entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one Keys entity is found.\n// Returns a *NotFoundError when no Keys entities are found.\nfunc (_q *KeysQuery) Only(ctx context.Context) (*Keys, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{keys.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{keys.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *KeysQuery) OnlyX(ctx context.Context) *Keys {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only Keys ID in the query.\n// Returns a *NotSingularError when more than one Keys ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *KeysQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{keys.Label}\n\tdefault:\n\t\terr = &NotSingularError{keys.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *KeysQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of KeysSlice.\nfunc (_q *KeysQuery) All(ctx context.Context) ([]*Keys, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*Keys, *KeysQuery]()\n\treturn withInterceptors[[]*Keys](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *KeysQuery) AllX(ctx context.Context) []*Keys {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of Keys IDs.\nfunc (_q *KeysQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(keys.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *KeysQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *KeysQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*KeysQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *KeysQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *KeysQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *KeysQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the KeysQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *KeysQuery) Clone() *KeysQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &KeysQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]keys.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.Keys{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tVerificationKeys []storage.VerificationKey `json:\"verification_keys,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.Keys.Query().\n//\t\tGroupBy(keys.FieldVerificationKeys).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *KeysQuery) GroupBy(field string, fields ...string) *KeysGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &KeysGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = keys.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tVerificationKeys []storage.VerificationKey `json:\"verification_keys,omitempty\"`\n//\t}\n//\n//\tclient.Keys.Query().\n//\t\tSelect(keys.FieldVerificationKeys).\n//\t\tScan(ctx, &v)\nfunc (_q *KeysQuery) Select(fields ...string) *KeysSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &KeysSelect{KeysQuery: _q}\n\tsbuild.label = keys.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a KeysSelect configured with the given aggregations.\nfunc (_q *KeysQuery) Aggregate(fns ...AggregateFunc) *KeysSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *KeysQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !keys.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *KeysQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Keys, error) {\n\tvar (\n\t\tnodes = []*Keys{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*Keys).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &Keys{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *KeysQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *KeysQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(keys.Table, keys.Columns, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, keys.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != keys.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *KeysQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(keys.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = keys.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// KeysGroupBy is the group-by builder for Keys entities.\ntype KeysGroupBy struct {\n\tselector\n\tbuild *KeysQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *KeysGroupBy) Aggregate(fns ...AggregateFunc) *KeysGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *KeysGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*KeysQuery, *KeysGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *KeysGroupBy) sqlScan(ctx context.Context, root *KeysQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// KeysSelect is the builder for selecting fields of Keys entities.\ntype KeysSelect struct {\n\t*KeysQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *KeysSelect) Aggregate(fns ...AggregateFunc) *KeysSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *KeysSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*KeysQuery, *KeysSelect](ctx, _s.KeysQuery, _s, _s.inters, v)\n}\n\nfunc (_s *KeysSelect) sqlScan(ctx context.Context, root *KeysQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/keys_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\tjose \"github.com/go-jose/go-jose/v4\"\n)\n\n// KeysUpdate is the builder for updating Keys entities.\ntype KeysUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *KeysMutation\n}\n\n// Where appends a list predicates to the KeysUpdate builder.\nfunc (_u *KeysUpdate) Where(ps ...predicate.Keys) *KeysUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetVerificationKeys sets the \"verification_keys\" field.\nfunc (_u *KeysUpdate) SetVerificationKeys(v []storage.VerificationKey) *KeysUpdate {\n\t_u.mutation.SetVerificationKeys(v)\n\treturn _u\n}\n\n// AppendVerificationKeys appends value to the \"verification_keys\" field.\nfunc (_u *KeysUpdate) AppendVerificationKeys(v []storage.VerificationKey) *KeysUpdate {\n\t_u.mutation.AppendVerificationKeys(v)\n\treturn _u\n}\n\n// SetSigningKey sets the \"signing_key\" field.\nfunc (_u *KeysUpdate) SetSigningKey(v jose.JSONWebKey) *KeysUpdate {\n\t_u.mutation.SetSigningKey(v)\n\treturn _u\n}\n\n// SetNillableSigningKey sets the \"signing_key\" field if the given value is not nil.\nfunc (_u *KeysUpdate) SetNillableSigningKey(v *jose.JSONWebKey) *KeysUpdate {\n\tif v != nil {\n\t\t_u.SetSigningKey(*v)\n\t}\n\treturn _u\n}\n\n// SetSigningKeyPub sets the \"signing_key_pub\" field.\nfunc (_u *KeysUpdate) SetSigningKeyPub(v jose.JSONWebKey) *KeysUpdate {\n\t_u.mutation.SetSigningKeyPub(v)\n\treturn _u\n}\n\n// SetNillableSigningKeyPub sets the \"signing_key_pub\" field if the given value is not nil.\nfunc (_u *KeysUpdate) SetNillableSigningKeyPub(v *jose.JSONWebKey) *KeysUpdate {\n\tif v != nil {\n\t\t_u.SetSigningKeyPub(*v)\n\t}\n\treturn _u\n}\n\n// SetNextRotation sets the \"next_rotation\" field.\nfunc (_u *KeysUpdate) SetNextRotation(v time.Time) *KeysUpdate {\n\t_u.mutation.SetNextRotation(v)\n\treturn _u\n}\n\n// SetNillableNextRotation sets the \"next_rotation\" field if the given value is not nil.\nfunc (_u *KeysUpdate) SetNillableNextRotation(v *time.Time) *KeysUpdate {\n\tif v != nil {\n\t\t_u.SetNextRotation(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the KeysMutation object of the builder.\nfunc (_u *KeysUpdate) Mutation() *KeysMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *KeysUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *KeysUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *KeysUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *KeysUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (_u *KeysUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\t_spec := sqlgraph.NewUpdateSpec(keys.Table, keys.Columns, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.VerificationKeys(); ok {\n\t\t_spec.SetField(keys.FieldVerificationKeys, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedVerificationKeys(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, keys.FieldVerificationKeys, value)\n\t\t})\n\t}\n\tif value, ok := _u.mutation.SigningKey(); ok {\n\t\t_spec.SetField(keys.FieldSigningKey, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.SigningKeyPub(); ok {\n\t\t_spec.SetField(keys.FieldSigningKeyPub, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.NextRotation(); ok {\n\t\t_spec.SetField(keys.FieldNextRotation, field.TypeTime, value)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{keys.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// KeysUpdateOne is the builder for updating a single Keys entity.\ntype KeysUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *KeysMutation\n}\n\n// SetVerificationKeys sets the \"verification_keys\" field.\nfunc (_u *KeysUpdateOne) SetVerificationKeys(v []storage.VerificationKey) *KeysUpdateOne {\n\t_u.mutation.SetVerificationKeys(v)\n\treturn _u\n}\n\n// AppendVerificationKeys appends value to the \"verification_keys\" field.\nfunc (_u *KeysUpdateOne) AppendVerificationKeys(v []storage.VerificationKey) *KeysUpdateOne {\n\t_u.mutation.AppendVerificationKeys(v)\n\treturn _u\n}\n\n// SetSigningKey sets the \"signing_key\" field.\nfunc (_u *KeysUpdateOne) SetSigningKey(v jose.JSONWebKey) *KeysUpdateOne {\n\t_u.mutation.SetSigningKey(v)\n\treturn _u\n}\n\n// SetNillableSigningKey sets the \"signing_key\" field if the given value is not nil.\nfunc (_u *KeysUpdateOne) SetNillableSigningKey(v *jose.JSONWebKey) *KeysUpdateOne {\n\tif v != nil {\n\t\t_u.SetSigningKey(*v)\n\t}\n\treturn _u\n}\n\n// SetSigningKeyPub sets the \"signing_key_pub\" field.\nfunc (_u *KeysUpdateOne) SetSigningKeyPub(v jose.JSONWebKey) *KeysUpdateOne {\n\t_u.mutation.SetSigningKeyPub(v)\n\treturn _u\n}\n\n// SetNillableSigningKeyPub sets the \"signing_key_pub\" field if the given value is not nil.\nfunc (_u *KeysUpdateOne) SetNillableSigningKeyPub(v *jose.JSONWebKey) *KeysUpdateOne {\n\tif v != nil {\n\t\t_u.SetSigningKeyPub(*v)\n\t}\n\treturn _u\n}\n\n// SetNextRotation sets the \"next_rotation\" field.\nfunc (_u *KeysUpdateOne) SetNextRotation(v time.Time) *KeysUpdateOne {\n\t_u.mutation.SetNextRotation(v)\n\treturn _u\n}\n\n// SetNillableNextRotation sets the \"next_rotation\" field if the given value is not nil.\nfunc (_u *KeysUpdateOne) SetNillableNextRotation(v *time.Time) *KeysUpdateOne {\n\tif v != nil {\n\t\t_u.SetNextRotation(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the KeysMutation object of the builder.\nfunc (_u *KeysUpdateOne) Mutation() *KeysMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the KeysUpdate builder.\nfunc (_u *KeysUpdateOne) Where(ps ...predicate.Keys) *KeysUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *KeysUpdateOne) Select(field string, fields ...string) *KeysUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated Keys entity.\nfunc (_u *KeysUpdateOne) Save(ctx context.Context) (*Keys, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *KeysUpdateOne) SaveX(ctx context.Context) *Keys {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *KeysUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *KeysUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc (_u *KeysUpdateOne) sqlSave(ctx context.Context) (_node *Keys, err error) {\n\t_spec := sqlgraph.NewUpdateSpec(keys.Table, keys.Columns, sqlgraph.NewFieldSpec(keys.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"Keys.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, keys.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !keys.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != keys.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.VerificationKeys(); ok {\n\t\t_spec.SetField(keys.FieldVerificationKeys, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedVerificationKeys(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, keys.FieldVerificationKeys, value)\n\t\t})\n\t}\n\tif value, ok := _u.mutation.SigningKey(); ok {\n\t\t_spec.SetField(keys.FieldSigningKey, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.SigningKeyPub(); ok {\n\t\t_spec.SetField(keys.FieldSigningKeyPub, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.NextRotation(); ok {\n\t\t_spec.SetField(keys.FieldNextRotation, field.TypeTime, value)\n\t}\n\t_node = &Keys{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{keys.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/migrate/migrate.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage migrate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\n\t\"entgo.io/ent/dialect\"\n\t\"entgo.io/ent/dialect/sql/schema\"\n)\n\nvar (\n\t// WithGlobalUniqueID sets the universal ids options to the migration.\n\t// If this option is enabled, ent migration will allocate a 1<<32 range\n\t// for the ids of each entity (table).\n\t// Note that this option cannot be applied on tables that already exist.\n\tWithGlobalUniqueID = schema.WithGlobalUniqueID\n\t// WithDropColumn sets the drop column option to the migration.\n\t// If this option is enabled, ent migration will drop old columns\n\t// that were used for both fields and edges. This defaults to false.\n\tWithDropColumn = schema.WithDropColumn\n\t// WithDropIndex sets the drop index option to the migration.\n\t// If this option is enabled, ent migration will drop old indexes\n\t// that were defined in the schema. This defaults to false.\n\t// Note that unique constraints are defined using `UNIQUE INDEX`,\n\t// and therefore, it's recommended to enable this option to get more\n\t// flexibility in the schema changes.\n\tWithDropIndex = schema.WithDropIndex\n\t// WithForeignKeys enables creating foreign-key in schema DDL. This defaults to true.\n\tWithForeignKeys = schema.WithForeignKeys\n)\n\n// Schema is the API for creating, migrating and dropping a schema.\ntype Schema struct {\n\tdrv dialect.Driver\n}\n\n// NewSchema creates a new schema client.\nfunc NewSchema(drv dialect.Driver) *Schema { return &Schema{drv: drv} }\n\n// Create creates all schema resources.\nfunc (s *Schema) Create(ctx context.Context, opts ...schema.MigrateOption) error {\n\treturn Create(ctx, s, Tables, opts...)\n}\n\n// Create creates all table resources using the given schema driver.\nfunc Create(ctx context.Context, s *Schema, tables []*schema.Table, opts ...schema.MigrateOption) error {\n\tmigrate, err := schema.NewMigrate(s.drv, opts...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"ent/migrate: %w\", err)\n\t}\n\treturn migrate.Create(ctx, tables...)\n}\n\n// WriteTo writes the schema changes to w instead of running them against the database.\n//\n//\tif err := client.Schema.WriteTo(context.Background(), os.Stdout); err != nil {\n//\t\tlog.Fatal(err)\n//\t}\nfunc (s *Schema) WriteTo(ctx context.Context, w io.Writer, opts ...schema.MigrateOption) error {\n\treturn Create(ctx, &Schema{drv: &schema.WriteDriver{Writer: w, Driver: s.drv}}, Tables, opts...)\n}\n"
  },
  {
    "path": "storage/ent/db/migrate/schema.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage migrate\n\nimport (\n\t\"entgo.io/ent/dialect/sql/schema\"\n\t\"entgo.io/ent/schema/field\"\n)\n\nvar (\n\t// AuthCodesColumns holds the columns for the \"auth_codes\" table.\n\tAuthCodesColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"client_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"scopes\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"nonce\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"redirect_uri\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_user_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_username\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email_verified\", Type: field.TypeBool},\n\t\t{Name: \"claims_groups\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"claims_preferred_username\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_data\", Type: field.TypeBytes, Nullable: true},\n\t\t{Name: \"expiry\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"code_challenge\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"code_challenge_method\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"auth_time\", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t}\n\t// AuthCodesTable holds the schema information for the \"auth_codes\" table.\n\tAuthCodesTable = &schema.Table{\n\t\tName:       \"auth_codes\",\n\t\tColumns:    AuthCodesColumns,\n\t\tPrimaryKey: []*schema.Column{AuthCodesColumns[0]},\n\t}\n\t// AuthRequestsColumns holds the columns for the \"auth_requests\" table.\n\tAuthRequestsColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"client_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"scopes\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"response_types\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"redirect_uri\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"nonce\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"state\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"force_approval_prompt\", Type: field.TypeBool},\n\t\t{Name: \"logged_in\", Type: field.TypeBool},\n\t\t{Name: \"claims_user_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_username\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email_verified\", Type: field.TypeBool},\n\t\t{Name: \"claims_groups\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"claims_preferred_username\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_data\", Type: field.TypeBytes, Nullable: true},\n\t\t{Name: \"expiry\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"code_challenge\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"code_challenge_method\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"hmac_key\", Type: field.TypeBytes},\n\t\t{Name: \"mfa_validated\", Type: field.TypeBool, Default: false},\n\t\t{Name: \"prompt\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"max_age\", Type: field.TypeInt, Default: -1},\n\t\t{Name: \"auth_time\", Type: field.TypeTime, Nullable: true, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t}\n\t// AuthRequestsTable holds the schema information for the \"auth_requests\" table.\n\tAuthRequestsTable = &schema.Table{\n\t\tName:       \"auth_requests\",\n\t\tColumns:    AuthRequestsColumns,\n\t\tPrimaryKey: []*schema.Column{AuthRequestsColumns[0]},\n\t}\n\t// AuthSessionsColumns holds the columns for the \"auth_sessions\" table.\n\tAuthSessionsColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"user_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"nonce\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"client_states\", Type: field.TypeBytes},\n\t\t{Name: \"created_at\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"last_activity\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"ip_address\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"user_agent\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"absolute_expiry\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"idle_expiry\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t}\n\t// AuthSessionsTable holds the schema information for the \"auth_sessions\" table.\n\tAuthSessionsTable = &schema.Table{\n\t\tName:       \"auth_sessions\",\n\t\tColumns:    AuthSessionsColumns,\n\t\tPrimaryKey: []*schema.Column{AuthSessionsColumns[0]},\n\t}\n\t// ConnectorsColumns holds the columns for the \"connectors\" table.\n\tConnectorsColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 100, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"type\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"name\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"resource_version\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"config\", Type: field.TypeBytes},\n\t\t{Name: \"grant_types\", Type: field.TypeJSON, Nullable: true},\n\t}\n\t// ConnectorsTable holds the schema information for the \"connectors\" table.\n\tConnectorsTable = &schema.Table{\n\t\tName:       \"connectors\",\n\t\tColumns:    ConnectorsColumns,\n\t\tPrimaryKey: []*schema.Column{ConnectorsColumns[0]},\n\t}\n\t// DeviceRequestsColumns holds the columns for the \"device_requests\" table.\n\tDeviceRequestsColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeInt, Increment: true},\n\t\t{Name: \"user_code\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"device_code\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"client_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"client_secret\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"scopes\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"expiry\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t}\n\t// DeviceRequestsTable holds the schema information for the \"device_requests\" table.\n\tDeviceRequestsTable = &schema.Table{\n\t\tName:       \"device_requests\",\n\t\tColumns:    DeviceRequestsColumns,\n\t\tPrimaryKey: []*schema.Column{DeviceRequestsColumns[0]},\n\t}\n\t// DeviceTokensColumns holds the columns for the \"device_tokens\" table.\n\tDeviceTokensColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeInt, Increment: true},\n\t\t{Name: \"device_code\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"status\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"token\", Type: field.TypeBytes, Nullable: true},\n\t\t{Name: \"expiry\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"last_request\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"poll_interval\", Type: field.TypeInt},\n\t\t{Name: \"code_challenge\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"code_challenge_method\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t}\n\t// DeviceTokensTable holds the schema information for the \"device_tokens\" table.\n\tDeviceTokensTable = &schema.Table{\n\t\tName:       \"device_tokens\",\n\t\tColumns:    DeviceTokensColumns,\n\t\tPrimaryKey: []*schema.Column{DeviceTokensColumns[0]},\n\t}\n\t// KeysColumns holds the columns for the \"keys\" table.\n\tKeysColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"verification_keys\", Type: field.TypeJSON},\n\t\t{Name: \"signing_key\", Type: field.TypeJSON},\n\t\t{Name: \"signing_key_pub\", Type: field.TypeJSON},\n\t\t{Name: \"next_rotation\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t}\n\t// KeysTable holds the schema information for the \"keys\" table.\n\tKeysTable = &schema.Table{\n\t\tName:       \"keys\",\n\t\tColumns:    KeysColumns,\n\t\tPrimaryKey: []*schema.Column{KeysColumns[0]},\n\t}\n\t// Oauth2clientsColumns holds the columns for the \"oauth2clients\" table.\n\tOauth2clientsColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 100, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"secret\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"redirect_uris\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"trusted_peers\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"public\", Type: field.TypeBool},\n\t\t{Name: \"name\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"logo_url\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"allowed_connectors\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"mfa_chain\", Type: field.TypeJSON, Nullable: true},\n\t}\n\t// Oauth2clientsTable holds the schema information for the \"oauth2clients\" table.\n\tOauth2clientsTable = &schema.Table{\n\t\tName:       \"oauth2clients\",\n\t\tColumns:    Oauth2clientsColumns,\n\t\tPrimaryKey: []*schema.Column{Oauth2clientsColumns[0]},\n\t}\n\t// OfflineSessionsColumns holds the columns for the \"offline_sessions\" table.\n\tOfflineSessionsColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"user_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"conn_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"refresh\", Type: field.TypeBytes},\n\t\t{Name: \"connector_data\", Type: field.TypeBytes, Nullable: true},\n\t}\n\t// OfflineSessionsTable holds the schema information for the \"offline_sessions\" table.\n\tOfflineSessionsTable = &schema.Table{\n\t\tName:       \"offline_sessions\",\n\t\tColumns:    OfflineSessionsColumns,\n\t\tPrimaryKey: []*schema.Column{OfflineSessionsColumns[0]},\n\t}\n\t// PasswordsColumns holds the columns for the \"passwords\" table.\n\tPasswordsColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeInt, Increment: true},\n\t\t{Name: \"email\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"hash\", Type: field.TypeBytes},\n\t\t{Name: \"username\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"name\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"preferred_username\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"email_verified\", Type: field.TypeBool, Nullable: true},\n\t\t{Name: \"user_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"groups\", Type: field.TypeJSON, Nullable: true},\n\t}\n\t// PasswordsTable holds the schema information for the \"passwords\" table.\n\tPasswordsTable = &schema.Table{\n\t\tName:       \"passwords\",\n\t\tColumns:    PasswordsColumns,\n\t\tPrimaryKey: []*schema.Column{PasswordsColumns[0]},\n\t}\n\t// RefreshTokensColumns holds the columns for the \"refresh_tokens\" table.\n\tRefreshTokensColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"client_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"scopes\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"nonce\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_user_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_username\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email_verified\", Type: field.TypeBool},\n\t\t{Name: \"claims_groups\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"claims_preferred_username\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_data\", Type: field.TypeBytes, Nullable: true},\n\t\t{Name: \"token\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"obsolete_token\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"created_at\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"last_used\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t}\n\t// RefreshTokensTable holds the schema information for the \"refresh_tokens\" table.\n\tRefreshTokensTable = &schema.Table{\n\t\tName:       \"refresh_tokens\",\n\t\tColumns:    RefreshTokensColumns,\n\t\tPrimaryKey: []*schema.Column{RefreshTokensColumns[0]},\n\t}\n\t// UserIdentitiesColumns holds the columns for the \"user_identities\" table.\n\tUserIdentitiesColumns = []*schema.Column{\n\t\t{Name: \"id\", Type: field.TypeString, Unique: true, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"user_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"connector_id\", Type: field.TypeString, Size: 2147483647, SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_user_id\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_username\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_preferred_username\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email\", Type: field.TypeString, Size: 2147483647, Default: \"\", SchemaType: map[string]string{\"mysql\": \"varchar(384)\", \"postgres\": \"text\", \"sqlite3\": \"text\"}},\n\t\t{Name: \"claims_email_verified\", Type: field.TypeBool, Default: false},\n\t\t{Name: \"claims_groups\", Type: field.TypeJSON, Nullable: true},\n\t\t{Name: \"consents\", Type: field.TypeBytes},\n\t\t{Name: \"mfa_secrets\", Type: field.TypeBytes, Nullable: true},\n\t\t{Name: \"created_at\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"last_login\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t\t{Name: \"blocked_until\", Type: field.TypeTime, SchemaType: map[string]string{\"mysql\": \"datetime(3)\", \"postgres\": \"timestamptz\", \"sqlite3\": \"timestamp\"}},\n\t}\n\t// UserIdentitiesTable holds the schema information for the \"user_identities\" table.\n\tUserIdentitiesTable = &schema.Table{\n\t\tName:       \"user_identities\",\n\t\tColumns:    UserIdentitiesColumns,\n\t\tPrimaryKey: []*schema.Column{UserIdentitiesColumns[0]},\n\t}\n\t// Tables holds all the tables in the schema.\n\tTables = []*schema.Table{\n\t\tAuthCodesTable,\n\t\tAuthRequestsTable,\n\t\tAuthSessionsTable,\n\t\tConnectorsTable,\n\t\tDeviceRequestsTable,\n\t\tDeviceTokensTable,\n\t\tKeysTable,\n\t\tOauth2clientsTable,\n\t\tOfflineSessionsTable,\n\t\tPasswordsTable,\n\t\tRefreshTokensTable,\n\t\tUserIdentitiesTable,\n\t}\n)\n\nfunc init() {\n}\n"
  },
  {
    "path": "storage/ent/db/mutation.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n\tjose \"github.com/go-jose/go-jose/v4\"\n)\n\nconst (\n\t// Operation types.\n\tOpCreate    = ent.OpCreate\n\tOpDelete    = ent.OpDelete\n\tOpDeleteOne = ent.OpDeleteOne\n\tOpUpdate    = ent.OpUpdate\n\tOpUpdateOne = ent.OpUpdateOne\n\n\t// Node types.\n\tTypeAuthCode       = \"AuthCode\"\n\tTypeAuthRequest    = \"AuthRequest\"\n\tTypeAuthSession    = \"AuthSession\"\n\tTypeConnector      = \"Connector\"\n\tTypeDeviceRequest  = \"DeviceRequest\"\n\tTypeDeviceToken    = \"DeviceToken\"\n\tTypeKeys           = \"Keys\"\n\tTypeOAuth2Client   = \"OAuth2Client\"\n\tTypeOfflineSession = \"OfflineSession\"\n\tTypePassword       = \"Password\"\n\tTypeRefreshToken   = \"RefreshToken\"\n\tTypeUserIdentity   = \"UserIdentity\"\n)\n\n// AuthCodeMutation represents an operation that mutates the AuthCode nodes in the graph.\ntype AuthCodeMutation struct {\n\tconfig\n\top                        Op\n\ttyp                       string\n\tid                        *string\n\tclient_id                 *string\n\tscopes                    *[]string\n\tappendscopes              []string\n\tnonce                     *string\n\tredirect_uri              *string\n\tclaims_user_id            *string\n\tclaims_username           *string\n\tclaims_email              *string\n\tclaims_email_verified     *bool\n\tclaims_groups             *[]string\n\tappendclaims_groups       []string\n\tclaims_preferred_username *string\n\tconnector_id              *string\n\tconnector_data            *[]byte\n\texpiry                    *time.Time\n\tcode_challenge            *string\n\tcode_challenge_method     *string\n\tauth_time                 *time.Time\n\tclearedFields             map[string]struct{}\n\tdone                      bool\n\toldValue                  func(context.Context) (*AuthCode, error)\n\tpredicates                []predicate.AuthCode\n}\n\nvar _ ent.Mutation = (*AuthCodeMutation)(nil)\n\n// authcodeOption allows management of the mutation configuration using functional options.\ntype authcodeOption func(*AuthCodeMutation)\n\n// newAuthCodeMutation creates new mutation for the AuthCode entity.\nfunc newAuthCodeMutation(c config, op Op, opts ...authcodeOption) *AuthCodeMutation {\n\tm := &AuthCodeMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeAuthCode,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withAuthCodeID sets the ID field of the mutation.\nfunc withAuthCodeID(id string) authcodeOption {\n\treturn func(m *AuthCodeMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *AuthCode\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*AuthCode, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().AuthCode.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withAuthCode sets the old AuthCode of the mutation.\nfunc withAuthCode(node *AuthCode) authcodeOption {\n\treturn func(m *AuthCodeMutation) {\n\t\tm.oldValue = func(context.Context) (*AuthCode, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m AuthCodeMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m AuthCodeMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of AuthCode entities.\nfunc (m *AuthCodeMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *AuthCodeMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *AuthCodeMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().AuthCode.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (m *AuthCodeMutation) SetClientID(s string) {\n\tm.client_id = &s\n}\n\n// ClientID returns the value of the \"client_id\" field in the mutation.\nfunc (m *AuthCodeMutation) ClientID() (r string, exists bool) {\n\tv := m.client_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClientID returns the old \"client_id\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldClientID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClientID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClientID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClientID: %w\", err)\n\t}\n\treturn oldValue.ClientID, nil\n}\n\n// ResetClientID resets all changes to the \"client_id\" field.\nfunc (m *AuthCodeMutation) ResetClientID() {\n\tm.client_id = nil\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (m *AuthCodeMutation) SetScopes(s []string) {\n\tm.scopes = &s\n\tm.appendscopes = nil\n}\n\n// Scopes returns the value of the \"scopes\" field in the mutation.\nfunc (m *AuthCodeMutation) Scopes() (r []string, exists bool) {\n\tv := m.scopes\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldScopes returns the old \"scopes\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldScopes(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldScopes is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldScopes requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldScopes: %w\", err)\n\t}\n\treturn oldValue.Scopes, nil\n}\n\n// AppendScopes adds s to the \"scopes\" field.\nfunc (m *AuthCodeMutation) AppendScopes(s []string) {\n\tm.appendscopes = append(m.appendscopes, s...)\n}\n\n// AppendedScopes returns the list of values that were appended to the \"scopes\" field in this mutation.\nfunc (m *AuthCodeMutation) AppendedScopes() ([]string, bool) {\n\tif len(m.appendscopes) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendscopes, true\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (m *AuthCodeMutation) ClearScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tm.clearedFields[authcode.FieldScopes] = struct{}{}\n}\n\n// ScopesCleared returns if the \"scopes\" field was cleared in this mutation.\nfunc (m *AuthCodeMutation) ScopesCleared() bool {\n\t_, ok := m.clearedFields[authcode.FieldScopes]\n\treturn ok\n}\n\n// ResetScopes resets all changes to the \"scopes\" field.\nfunc (m *AuthCodeMutation) ResetScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tdelete(m.clearedFields, authcode.FieldScopes)\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (m *AuthCodeMutation) SetNonce(s string) {\n\tm.nonce = &s\n}\n\n// Nonce returns the value of the \"nonce\" field in the mutation.\nfunc (m *AuthCodeMutation) Nonce() (r string, exists bool) {\n\tv := m.nonce\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldNonce returns the old \"nonce\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldNonce(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldNonce is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldNonce requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldNonce: %w\", err)\n\t}\n\treturn oldValue.Nonce, nil\n}\n\n// ResetNonce resets all changes to the \"nonce\" field.\nfunc (m *AuthCodeMutation) ResetNonce() {\n\tm.nonce = nil\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (m *AuthCodeMutation) SetRedirectURI(s string) {\n\tm.redirect_uri = &s\n}\n\n// RedirectURI returns the value of the \"redirect_uri\" field in the mutation.\nfunc (m *AuthCodeMutation) RedirectURI() (r string, exists bool) {\n\tv := m.redirect_uri\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldRedirectURI returns the old \"redirect_uri\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldRedirectURI(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldRedirectURI is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldRedirectURI requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldRedirectURI: %w\", err)\n\t}\n\treturn oldValue.RedirectURI, nil\n}\n\n// ResetRedirectURI resets all changes to the \"redirect_uri\" field.\nfunc (m *AuthCodeMutation) ResetRedirectURI() {\n\tm.redirect_uri = nil\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (m *AuthCodeMutation) SetClaimsUserID(s string) {\n\tm.claims_user_id = &s\n}\n\n// ClaimsUserID returns the value of the \"claims_user_id\" field in the mutation.\nfunc (m *AuthCodeMutation) ClaimsUserID() (r string, exists bool) {\n\tv := m.claims_user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUserID returns the old \"claims_user_id\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldClaimsUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUserID: %w\", err)\n\t}\n\treturn oldValue.ClaimsUserID, nil\n}\n\n// ResetClaimsUserID resets all changes to the \"claims_user_id\" field.\nfunc (m *AuthCodeMutation) ResetClaimsUserID() {\n\tm.claims_user_id = nil\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (m *AuthCodeMutation) SetClaimsUsername(s string) {\n\tm.claims_username = &s\n}\n\n// ClaimsUsername returns the value of the \"claims_username\" field in the mutation.\nfunc (m *AuthCodeMutation) ClaimsUsername() (r string, exists bool) {\n\tv := m.claims_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUsername returns the old \"claims_username\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldClaimsUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsUsername, nil\n}\n\n// ResetClaimsUsername resets all changes to the \"claims_username\" field.\nfunc (m *AuthCodeMutation) ResetClaimsUsername() {\n\tm.claims_username = nil\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (m *AuthCodeMutation) SetClaimsEmail(s string) {\n\tm.claims_email = &s\n}\n\n// ClaimsEmail returns the value of the \"claims_email\" field in the mutation.\nfunc (m *AuthCodeMutation) ClaimsEmail() (r string, exists bool) {\n\tv := m.claims_email\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmail returns the old \"claims_email\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldClaimsEmail(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmail is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmail requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmail: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmail, nil\n}\n\n// ResetClaimsEmail resets all changes to the \"claims_email\" field.\nfunc (m *AuthCodeMutation) ResetClaimsEmail() {\n\tm.claims_email = nil\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (m *AuthCodeMutation) SetClaimsEmailVerified(b bool) {\n\tm.claims_email_verified = &b\n}\n\n// ClaimsEmailVerified returns the value of the \"claims_email_verified\" field in the mutation.\nfunc (m *AuthCodeMutation) ClaimsEmailVerified() (r bool, exists bool) {\n\tv := m.claims_email_verified\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmailVerified returns the old \"claims_email_verified\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmailVerified: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmailVerified, nil\n}\n\n// ResetClaimsEmailVerified resets all changes to the \"claims_email_verified\" field.\nfunc (m *AuthCodeMutation) ResetClaimsEmailVerified() {\n\tm.claims_email_verified = nil\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (m *AuthCodeMutation) SetClaimsGroups(s []string) {\n\tm.claims_groups = &s\n\tm.appendclaims_groups = nil\n}\n\n// ClaimsGroups returns the value of the \"claims_groups\" field in the mutation.\nfunc (m *AuthCodeMutation) ClaimsGroups() (r []string, exists bool) {\n\tv := m.claims_groups\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsGroups returns the old \"claims_groups\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsGroups is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsGroups requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsGroups: %w\", err)\n\t}\n\treturn oldValue.ClaimsGroups, nil\n}\n\n// AppendClaimsGroups adds s to the \"claims_groups\" field.\nfunc (m *AuthCodeMutation) AppendClaimsGroups(s []string) {\n\tm.appendclaims_groups = append(m.appendclaims_groups, s...)\n}\n\n// AppendedClaimsGroups returns the list of values that were appended to the \"claims_groups\" field in this mutation.\nfunc (m *AuthCodeMutation) AppendedClaimsGroups() ([]string, bool) {\n\tif len(m.appendclaims_groups) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendclaims_groups, true\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (m *AuthCodeMutation) ClearClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tm.clearedFields[authcode.FieldClaimsGroups] = struct{}{}\n}\n\n// ClaimsGroupsCleared returns if the \"claims_groups\" field was cleared in this mutation.\nfunc (m *AuthCodeMutation) ClaimsGroupsCleared() bool {\n\t_, ok := m.clearedFields[authcode.FieldClaimsGroups]\n\treturn ok\n}\n\n// ResetClaimsGroups resets all changes to the \"claims_groups\" field.\nfunc (m *AuthCodeMutation) ResetClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tdelete(m.clearedFields, authcode.FieldClaimsGroups)\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (m *AuthCodeMutation) SetClaimsPreferredUsername(s string) {\n\tm.claims_preferred_username = &s\n}\n\n// ClaimsPreferredUsername returns the value of the \"claims_preferred_username\" field in the mutation.\nfunc (m *AuthCodeMutation) ClaimsPreferredUsername() (r string, exists bool) {\n\tv := m.claims_preferred_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsPreferredUsername returns the old \"claims_preferred_username\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsPreferredUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsPreferredUsername, nil\n}\n\n// ResetClaimsPreferredUsername resets all changes to the \"claims_preferred_username\" field.\nfunc (m *AuthCodeMutation) ResetClaimsPreferredUsername() {\n\tm.claims_preferred_username = nil\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (m *AuthCodeMutation) SetConnectorID(s string) {\n\tm.connector_id = &s\n}\n\n// ConnectorID returns the value of the \"connector_id\" field in the mutation.\nfunc (m *AuthCodeMutation) ConnectorID() (r string, exists bool) {\n\tv := m.connector_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorID returns the old \"connector_id\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldConnectorID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorID: %w\", err)\n\t}\n\treturn oldValue.ConnectorID, nil\n}\n\n// ResetConnectorID resets all changes to the \"connector_id\" field.\nfunc (m *AuthCodeMutation) ResetConnectorID() {\n\tm.connector_id = nil\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (m *AuthCodeMutation) SetConnectorData(b []byte) {\n\tm.connector_data = &b\n}\n\n// ConnectorData returns the value of the \"connector_data\" field in the mutation.\nfunc (m *AuthCodeMutation) ConnectorData() (r []byte, exists bool) {\n\tv := m.connector_data\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorData returns the old \"connector_data\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorData is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorData requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorData: %w\", err)\n\t}\n\treturn oldValue.ConnectorData, nil\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (m *AuthCodeMutation) ClearConnectorData() {\n\tm.connector_data = nil\n\tm.clearedFields[authcode.FieldConnectorData] = struct{}{}\n}\n\n// ConnectorDataCleared returns if the \"connector_data\" field was cleared in this mutation.\nfunc (m *AuthCodeMutation) ConnectorDataCleared() bool {\n\t_, ok := m.clearedFields[authcode.FieldConnectorData]\n\treturn ok\n}\n\n// ResetConnectorData resets all changes to the \"connector_data\" field.\nfunc (m *AuthCodeMutation) ResetConnectorData() {\n\tm.connector_data = nil\n\tdelete(m.clearedFields, authcode.FieldConnectorData)\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (m *AuthCodeMutation) SetExpiry(t time.Time) {\n\tm.expiry = &t\n}\n\n// Expiry returns the value of the \"expiry\" field in the mutation.\nfunc (m *AuthCodeMutation) Expiry() (r time.Time, exists bool) {\n\tv := m.expiry\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldExpiry returns the old \"expiry\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldExpiry(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldExpiry is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldExpiry requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldExpiry: %w\", err)\n\t}\n\treturn oldValue.Expiry, nil\n}\n\n// ResetExpiry resets all changes to the \"expiry\" field.\nfunc (m *AuthCodeMutation) ResetExpiry() {\n\tm.expiry = nil\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (m *AuthCodeMutation) SetCodeChallenge(s string) {\n\tm.code_challenge = &s\n}\n\n// CodeChallenge returns the value of the \"code_challenge\" field in the mutation.\nfunc (m *AuthCodeMutation) CodeChallenge() (r string, exists bool) {\n\tv := m.code_challenge\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCodeChallenge returns the old \"code_challenge\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldCodeChallenge(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCodeChallenge is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCodeChallenge requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCodeChallenge: %w\", err)\n\t}\n\treturn oldValue.CodeChallenge, nil\n}\n\n// ResetCodeChallenge resets all changes to the \"code_challenge\" field.\nfunc (m *AuthCodeMutation) ResetCodeChallenge() {\n\tm.code_challenge = nil\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (m *AuthCodeMutation) SetCodeChallengeMethod(s string) {\n\tm.code_challenge_method = &s\n}\n\n// CodeChallengeMethod returns the value of the \"code_challenge_method\" field in the mutation.\nfunc (m *AuthCodeMutation) CodeChallengeMethod() (r string, exists bool) {\n\tv := m.code_challenge_method\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCodeChallengeMethod returns the old \"code_challenge_method\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldCodeChallengeMethod(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCodeChallengeMethod is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCodeChallengeMethod requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCodeChallengeMethod: %w\", err)\n\t}\n\treturn oldValue.CodeChallengeMethod, nil\n}\n\n// ResetCodeChallengeMethod resets all changes to the \"code_challenge_method\" field.\nfunc (m *AuthCodeMutation) ResetCodeChallengeMethod() {\n\tm.code_challenge_method = nil\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (m *AuthCodeMutation) SetAuthTime(t time.Time) {\n\tm.auth_time = &t\n}\n\n// AuthTime returns the value of the \"auth_time\" field in the mutation.\nfunc (m *AuthCodeMutation) AuthTime() (r time.Time, exists bool) {\n\tv := m.auth_time\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldAuthTime returns the old \"auth_time\" field's value of the AuthCode entity.\n// If the AuthCode object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthCodeMutation) OldAuthTime(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldAuthTime is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldAuthTime requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldAuthTime: %w\", err)\n\t}\n\treturn oldValue.AuthTime, nil\n}\n\n// ClearAuthTime clears the value of the \"auth_time\" field.\nfunc (m *AuthCodeMutation) ClearAuthTime() {\n\tm.auth_time = nil\n\tm.clearedFields[authcode.FieldAuthTime] = struct{}{}\n}\n\n// AuthTimeCleared returns if the \"auth_time\" field was cleared in this mutation.\nfunc (m *AuthCodeMutation) AuthTimeCleared() bool {\n\t_, ok := m.clearedFields[authcode.FieldAuthTime]\n\treturn ok\n}\n\n// ResetAuthTime resets all changes to the \"auth_time\" field.\nfunc (m *AuthCodeMutation) ResetAuthTime() {\n\tm.auth_time = nil\n\tdelete(m.clearedFields, authcode.FieldAuthTime)\n}\n\n// Where appends a list predicates to the AuthCodeMutation builder.\nfunc (m *AuthCodeMutation) Where(ps ...predicate.AuthCode) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the AuthCodeMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *AuthCodeMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.AuthCode, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *AuthCodeMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *AuthCodeMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (AuthCode).\nfunc (m *AuthCodeMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *AuthCodeMutation) Fields() []string {\n\tfields := make([]string, 0, 16)\n\tif m.client_id != nil {\n\t\tfields = append(fields, authcode.FieldClientID)\n\t}\n\tif m.scopes != nil {\n\t\tfields = append(fields, authcode.FieldScopes)\n\t}\n\tif m.nonce != nil {\n\t\tfields = append(fields, authcode.FieldNonce)\n\t}\n\tif m.redirect_uri != nil {\n\t\tfields = append(fields, authcode.FieldRedirectURI)\n\t}\n\tif m.claims_user_id != nil {\n\t\tfields = append(fields, authcode.FieldClaimsUserID)\n\t}\n\tif m.claims_username != nil {\n\t\tfields = append(fields, authcode.FieldClaimsUsername)\n\t}\n\tif m.claims_email != nil {\n\t\tfields = append(fields, authcode.FieldClaimsEmail)\n\t}\n\tif m.claims_email_verified != nil {\n\t\tfields = append(fields, authcode.FieldClaimsEmailVerified)\n\t}\n\tif m.claims_groups != nil {\n\t\tfields = append(fields, authcode.FieldClaimsGroups)\n\t}\n\tif m.claims_preferred_username != nil {\n\t\tfields = append(fields, authcode.FieldClaimsPreferredUsername)\n\t}\n\tif m.connector_id != nil {\n\t\tfields = append(fields, authcode.FieldConnectorID)\n\t}\n\tif m.connector_data != nil {\n\t\tfields = append(fields, authcode.FieldConnectorData)\n\t}\n\tif m.expiry != nil {\n\t\tfields = append(fields, authcode.FieldExpiry)\n\t}\n\tif m.code_challenge != nil {\n\t\tfields = append(fields, authcode.FieldCodeChallenge)\n\t}\n\tif m.code_challenge_method != nil {\n\t\tfields = append(fields, authcode.FieldCodeChallengeMethod)\n\t}\n\tif m.auth_time != nil {\n\t\tfields = append(fields, authcode.FieldAuthTime)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *AuthCodeMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase authcode.FieldClientID:\n\t\treturn m.ClientID()\n\tcase authcode.FieldScopes:\n\t\treturn m.Scopes()\n\tcase authcode.FieldNonce:\n\t\treturn m.Nonce()\n\tcase authcode.FieldRedirectURI:\n\t\treturn m.RedirectURI()\n\tcase authcode.FieldClaimsUserID:\n\t\treturn m.ClaimsUserID()\n\tcase authcode.FieldClaimsUsername:\n\t\treturn m.ClaimsUsername()\n\tcase authcode.FieldClaimsEmail:\n\t\treturn m.ClaimsEmail()\n\tcase authcode.FieldClaimsEmailVerified:\n\t\treturn m.ClaimsEmailVerified()\n\tcase authcode.FieldClaimsGroups:\n\t\treturn m.ClaimsGroups()\n\tcase authcode.FieldClaimsPreferredUsername:\n\t\treturn m.ClaimsPreferredUsername()\n\tcase authcode.FieldConnectorID:\n\t\treturn m.ConnectorID()\n\tcase authcode.FieldConnectorData:\n\t\treturn m.ConnectorData()\n\tcase authcode.FieldExpiry:\n\t\treturn m.Expiry()\n\tcase authcode.FieldCodeChallenge:\n\t\treturn m.CodeChallenge()\n\tcase authcode.FieldCodeChallengeMethod:\n\t\treturn m.CodeChallengeMethod()\n\tcase authcode.FieldAuthTime:\n\t\treturn m.AuthTime()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *AuthCodeMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase authcode.FieldClientID:\n\t\treturn m.OldClientID(ctx)\n\tcase authcode.FieldScopes:\n\t\treturn m.OldScopes(ctx)\n\tcase authcode.FieldNonce:\n\t\treturn m.OldNonce(ctx)\n\tcase authcode.FieldRedirectURI:\n\t\treturn m.OldRedirectURI(ctx)\n\tcase authcode.FieldClaimsUserID:\n\t\treturn m.OldClaimsUserID(ctx)\n\tcase authcode.FieldClaimsUsername:\n\t\treturn m.OldClaimsUsername(ctx)\n\tcase authcode.FieldClaimsEmail:\n\t\treturn m.OldClaimsEmail(ctx)\n\tcase authcode.FieldClaimsEmailVerified:\n\t\treturn m.OldClaimsEmailVerified(ctx)\n\tcase authcode.FieldClaimsGroups:\n\t\treturn m.OldClaimsGroups(ctx)\n\tcase authcode.FieldClaimsPreferredUsername:\n\t\treturn m.OldClaimsPreferredUsername(ctx)\n\tcase authcode.FieldConnectorID:\n\t\treturn m.OldConnectorID(ctx)\n\tcase authcode.FieldConnectorData:\n\t\treturn m.OldConnectorData(ctx)\n\tcase authcode.FieldExpiry:\n\t\treturn m.OldExpiry(ctx)\n\tcase authcode.FieldCodeChallenge:\n\t\treturn m.OldCodeChallenge(ctx)\n\tcase authcode.FieldCodeChallengeMethod:\n\t\treturn m.OldCodeChallengeMethod(ctx)\n\tcase authcode.FieldAuthTime:\n\t\treturn m.OldAuthTime(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown AuthCode field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *AuthCodeMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase authcode.FieldClientID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClientID(v)\n\t\treturn nil\n\tcase authcode.FieldScopes:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetScopes(v)\n\t\treturn nil\n\tcase authcode.FieldNonce:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetNonce(v)\n\t\treturn nil\n\tcase authcode.FieldRedirectURI:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetRedirectURI(v)\n\t\treturn nil\n\tcase authcode.FieldClaimsUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUserID(v)\n\t\treturn nil\n\tcase authcode.FieldClaimsUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUsername(v)\n\t\treturn nil\n\tcase authcode.FieldClaimsEmail:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmail(v)\n\t\treturn nil\n\tcase authcode.FieldClaimsEmailVerified:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmailVerified(v)\n\t\treturn nil\n\tcase authcode.FieldClaimsGroups:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsGroups(v)\n\t\treturn nil\n\tcase authcode.FieldClaimsPreferredUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsPreferredUsername(v)\n\t\treturn nil\n\tcase authcode.FieldConnectorID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorID(v)\n\t\treturn nil\n\tcase authcode.FieldConnectorData:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorData(v)\n\t\treturn nil\n\tcase authcode.FieldExpiry:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetExpiry(v)\n\t\treturn nil\n\tcase authcode.FieldCodeChallenge:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCodeChallenge(v)\n\t\treturn nil\n\tcase authcode.FieldCodeChallengeMethod:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCodeChallengeMethod(v)\n\t\treturn nil\n\tcase authcode.FieldAuthTime:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetAuthTime(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthCode field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *AuthCodeMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *AuthCodeMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *AuthCodeMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown AuthCode numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *AuthCodeMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(authcode.FieldScopes) {\n\t\tfields = append(fields, authcode.FieldScopes)\n\t}\n\tif m.FieldCleared(authcode.FieldClaimsGroups) {\n\t\tfields = append(fields, authcode.FieldClaimsGroups)\n\t}\n\tif m.FieldCleared(authcode.FieldConnectorData) {\n\t\tfields = append(fields, authcode.FieldConnectorData)\n\t}\n\tif m.FieldCleared(authcode.FieldAuthTime) {\n\t\tfields = append(fields, authcode.FieldAuthTime)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *AuthCodeMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *AuthCodeMutation) ClearField(name string) error {\n\tswitch name {\n\tcase authcode.FieldScopes:\n\t\tm.ClearScopes()\n\t\treturn nil\n\tcase authcode.FieldClaimsGroups:\n\t\tm.ClearClaimsGroups()\n\t\treturn nil\n\tcase authcode.FieldConnectorData:\n\t\tm.ClearConnectorData()\n\t\treturn nil\n\tcase authcode.FieldAuthTime:\n\t\tm.ClearAuthTime()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthCode nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *AuthCodeMutation) ResetField(name string) error {\n\tswitch name {\n\tcase authcode.FieldClientID:\n\t\tm.ResetClientID()\n\t\treturn nil\n\tcase authcode.FieldScopes:\n\t\tm.ResetScopes()\n\t\treturn nil\n\tcase authcode.FieldNonce:\n\t\tm.ResetNonce()\n\t\treturn nil\n\tcase authcode.FieldRedirectURI:\n\t\tm.ResetRedirectURI()\n\t\treturn nil\n\tcase authcode.FieldClaimsUserID:\n\t\tm.ResetClaimsUserID()\n\t\treturn nil\n\tcase authcode.FieldClaimsUsername:\n\t\tm.ResetClaimsUsername()\n\t\treturn nil\n\tcase authcode.FieldClaimsEmail:\n\t\tm.ResetClaimsEmail()\n\t\treturn nil\n\tcase authcode.FieldClaimsEmailVerified:\n\t\tm.ResetClaimsEmailVerified()\n\t\treturn nil\n\tcase authcode.FieldClaimsGroups:\n\t\tm.ResetClaimsGroups()\n\t\treturn nil\n\tcase authcode.FieldClaimsPreferredUsername:\n\t\tm.ResetClaimsPreferredUsername()\n\t\treturn nil\n\tcase authcode.FieldConnectorID:\n\t\tm.ResetConnectorID()\n\t\treturn nil\n\tcase authcode.FieldConnectorData:\n\t\tm.ResetConnectorData()\n\t\treturn nil\n\tcase authcode.FieldExpiry:\n\t\tm.ResetExpiry()\n\t\treturn nil\n\tcase authcode.FieldCodeChallenge:\n\t\tm.ResetCodeChallenge()\n\t\treturn nil\n\tcase authcode.FieldCodeChallengeMethod:\n\t\tm.ResetCodeChallengeMethod()\n\t\treturn nil\n\tcase authcode.FieldAuthTime:\n\t\tm.ResetAuthTime()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthCode field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *AuthCodeMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *AuthCodeMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *AuthCodeMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *AuthCodeMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *AuthCodeMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *AuthCodeMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *AuthCodeMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown AuthCode unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *AuthCodeMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown AuthCode edge %s\", name)\n}\n\n// AuthRequestMutation represents an operation that mutates the AuthRequest nodes in the graph.\ntype AuthRequestMutation struct {\n\tconfig\n\top                        Op\n\ttyp                       string\n\tid                        *string\n\tclient_id                 *string\n\tscopes                    *[]string\n\tappendscopes              []string\n\tresponse_types            *[]string\n\tappendresponse_types      []string\n\tredirect_uri              *string\n\tnonce                     *string\n\tstate                     *string\n\tforce_approval_prompt     *bool\n\tlogged_in                 *bool\n\tclaims_user_id            *string\n\tclaims_username           *string\n\tclaims_email              *string\n\tclaims_email_verified     *bool\n\tclaims_groups             *[]string\n\tappendclaims_groups       []string\n\tclaims_preferred_username *string\n\tconnector_id              *string\n\tconnector_data            *[]byte\n\texpiry                    *time.Time\n\tcode_challenge            *string\n\tcode_challenge_method     *string\n\thmac_key                  *[]byte\n\tmfa_validated             *bool\n\tprompt                    *string\n\tmax_age                   *int\n\taddmax_age                *int\n\tauth_time                 *time.Time\n\tclearedFields             map[string]struct{}\n\tdone                      bool\n\toldValue                  func(context.Context) (*AuthRequest, error)\n\tpredicates                []predicate.AuthRequest\n}\n\nvar _ ent.Mutation = (*AuthRequestMutation)(nil)\n\n// authrequestOption allows management of the mutation configuration using functional options.\ntype authrequestOption func(*AuthRequestMutation)\n\n// newAuthRequestMutation creates new mutation for the AuthRequest entity.\nfunc newAuthRequestMutation(c config, op Op, opts ...authrequestOption) *AuthRequestMutation {\n\tm := &AuthRequestMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeAuthRequest,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withAuthRequestID sets the ID field of the mutation.\nfunc withAuthRequestID(id string) authrequestOption {\n\treturn func(m *AuthRequestMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *AuthRequest\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*AuthRequest, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().AuthRequest.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withAuthRequest sets the old AuthRequest of the mutation.\nfunc withAuthRequest(node *AuthRequest) authrequestOption {\n\treturn func(m *AuthRequestMutation) {\n\t\tm.oldValue = func(context.Context) (*AuthRequest, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m AuthRequestMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m AuthRequestMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of AuthRequest entities.\nfunc (m *AuthRequestMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *AuthRequestMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *AuthRequestMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().AuthRequest.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (m *AuthRequestMutation) SetClientID(s string) {\n\tm.client_id = &s\n}\n\n// ClientID returns the value of the \"client_id\" field in the mutation.\nfunc (m *AuthRequestMutation) ClientID() (r string, exists bool) {\n\tv := m.client_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClientID returns the old \"client_id\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldClientID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClientID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClientID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClientID: %w\", err)\n\t}\n\treturn oldValue.ClientID, nil\n}\n\n// ResetClientID resets all changes to the \"client_id\" field.\nfunc (m *AuthRequestMutation) ResetClientID() {\n\tm.client_id = nil\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (m *AuthRequestMutation) SetScopes(s []string) {\n\tm.scopes = &s\n\tm.appendscopes = nil\n}\n\n// Scopes returns the value of the \"scopes\" field in the mutation.\nfunc (m *AuthRequestMutation) Scopes() (r []string, exists bool) {\n\tv := m.scopes\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldScopes returns the old \"scopes\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldScopes(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldScopes is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldScopes requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldScopes: %w\", err)\n\t}\n\treturn oldValue.Scopes, nil\n}\n\n// AppendScopes adds s to the \"scopes\" field.\nfunc (m *AuthRequestMutation) AppendScopes(s []string) {\n\tm.appendscopes = append(m.appendscopes, s...)\n}\n\n// AppendedScopes returns the list of values that were appended to the \"scopes\" field in this mutation.\nfunc (m *AuthRequestMutation) AppendedScopes() ([]string, bool) {\n\tif len(m.appendscopes) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendscopes, true\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (m *AuthRequestMutation) ClearScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tm.clearedFields[authrequest.FieldScopes] = struct{}{}\n}\n\n// ScopesCleared returns if the \"scopes\" field was cleared in this mutation.\nfunc (m *AuthRequestMutation) ScopesCleared() bool {\n\t_, ok := m.clearedFields[authrequest.FieldScopes]\n\treturn ok\n}\n\n// ResetScopes resets all changes to the \"scopes\" field.\nfunc (m *AuthRequestMutation) ResetScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tdelete(m.clearedFields, authrequest.FieldScopes)\n}\n\n// SetResponseTypes sets the \"response_types\" field.\nfunc (m *AuthRequestMutation) SetResponseTypes(s []string) {\n\tm.response_types = &s\n\tm.appendresponse_types = nil\n}\n\n// ResponseTypes returns the value of the \"response_types\" field in the mutation.\nfunc (m *AuthRequestMutation) ResponseTypes() (r []string, exists bool) {\n\tv := m.response_types\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldResponseTypes returns the old \"response_types\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldResponseTypes(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldResponseTypes is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldResponseTypes requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldResponseTypes: %w\", err)\n\t}\n\treturn oldValue.ResponseTypes, nil\n}\n\n// AppendResponseTypes adds s to the \"response_types\" field.\nfunc (m *AuthRequestMutation) AppendResponseTypes(s []string) {\n\tm.appendresponse_types = append(m.appendresponse_types, s...)\n}\n\n// AppendedResponseTypes returns the list of values that were appended to the \"response_types\" field in this mutation.\nfunc (m *AuthRequestMutation) AppendedResponseTypes() ([]string, bool) {\n\tif len(m.appendresponse_types) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendresponse_types, true\n}\n\n// ClearResponseTypes clears the value of the \"response_types\" field.\nfunc (m *AuthRequestMutation) ClearResponseTypes() {\n\tm.response_types = nil\n\tm.appendresponse_types = nil\n\tm.clearedFields[authrequest.FieldResponseTypes] = struct{}{}\n}\n\n// ResponseTypesCleared returns if the \"response_types\" field was cleared in this mutation.\nfunc (m *AuthRequestMutation) ResponseTypesCleared() bool {\n\t_, ok := m.clearedFields[authrequest.FieldResponseTypes]\n\treturn ok\n}\n\n// ResetResponseTypes resets all changes to the \"response_types\" field.\nfunc (m *AuthRequestMutation) ResetResponseTypes() {\n\tm.response_types = nil\n\tm.appendresponse_types = nil\n\tdelete(m.clearedFields, authrequest.FieldResponseTypes)\n}\n\n// SetRedirectURI sets the \"redirect_uri\" field.\nfunc (m *AuthRequestMutation) SetRedirectURI(s string) {\n\tm.redirect_uri = &s\n}\n\n// RedirectURI returns the value of the \"redirect_uri\" field in the mutation.\nfunc (m *AuthRequestMutation) RedirectURI() (r string, exists bool) {\n\tv := m.redirect_uri\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldRedirectURI returns the old \"redirect_uri\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldRedirectURI(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldRedirectURI is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldRedirectURI requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldRedirectURI: %w\", err)\n\t}\n\treturn oldValue.RedirectURI, nil\n}\n\n// ResetRedirectURI resets all changes to the \"redirect_uri\" field.\nfunc (m *AuthRequestMutation) ResetRedirectURI() {\n\tm.redirect_uri = nil\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (m *AuthRequestMutation) SetNonce(s string) {\n\tm.nonce = &s\n}\n\n// Nonce returns the value of the \"nonce\" field in the mutation.\nfunc (m *AuthRequestMutation) Nonce() (r string, exists bool) {\n\tv := m.nonce\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldNonce returns the old \"nonce\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldNonce(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldNonce is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldNonce requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldNonce: %w\", err)\n\t}\n\treturn oldValue.Nonce, nil\n}\n\n// ResetNonce resets all changes to the \"nonce\" field.\nfunc (m *AuthRequestMutation) ResetNonce() {\n\tm.nonce = nil\n}\n\n// SetState sets the \"state\" field.\nfunc (m *AuthRequestMutation) SetState(s string) {\n\tm.state = &s\n}\n\n// State returns the value of the \"state\" field in the mutation.\nfunc (m *AuthRequestMutation) State() (r string, exists bool) {\n\tv := m.state\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldState returns the old \"state\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldState(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldState is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldState requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldState: %w\", err)\n\t}\n\treturn oldValue.State, nil\n}\n\n// ResetState resets all changes to the \"state\" field.\nfunc (m *AuthRequestMutation) ResetState() {\n\tm.state = nil\n}\n\n// SetForceApprovalPrompt sets the \"force_approval_prompt\" field.\nfunc (m *AuthRequestMutation) SetForceApprovalPrompt(b bool) {\n\tm.force_approval_prompt = &b\n}\n\n// ForceApprovalPrompt returns the value of the \"force_approval_prompt\" field in the mutation.\nfunc (m *AuthRequestMutation) ForceApprovalPrompt() (r bool, exists bool) {\n\tv := m.force_approval_prompt\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldForceApprovalPrompt returns the old \"force_approval_prompt\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldForceApprovalPrompt(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldForceApprovalPrompt is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldForceApprovalPrompt requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldForceApprovalPrompt: %w\", err)\n\t}\n\treturn oldValue.ForceApprovalPrompt, nil\n}\n\n// ResetForceApprovalPrompt resets all changes to the \"force_approval_prompt\" field.\nfunc (m *AuthRequestMutation) ResetForceApprovalPrompt() {\n\tm.force_approval_prompt = nil\n}\n\n// SetLoggedIn sets the \"logged_in\" field.\nfunc (m *AuthRequestMutation) SetLoggedIn(b bool) {\n\tm.logged_in = &b\n}\n\n// LoggedIn returns the value of the \"logged_in\" field in the mutation.\nfunc (m *AuthRequestMutation) LoggedIn() (r bool, exists bool) {\n\tv := m.logged_in\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldLoggedIn returns the old \"logged_in\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldLoggedIn(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldLoggedIn is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldLoggedIn requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldLoggedIn: %w\", err)\n\t}\n\treturn oldValue.LoggedIn, nil\n}\n\n// ResetLoggedIn resets all changes to the \"logged_in\" field.\nfunc (m *AuthRequestMutation) ResetLoggedIn() {\n\tm.logged_in = nil\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (m *AuthRequestMutation) SetClaimsUserID(s string) {\n\tm.claims_user_id = &s\n}\n\n// ClaimsUserID returns the value of the \"claims_user_id\" field in the mutation.\nfunc (m *AuthRequestMutation) ClaimsUserID() (r string, exists bool) {\n\tv := m.claims_user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUserID returns the old \"claims_user_id\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldClaimsUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUserID: %w\", err)\n\t}\n\treturn oldValue.ClaimsUserID, nil\n}\n\n// ResetClaimsUserID resets all changes to the \"claims_user_id\" field.\nfunc (m *AuthRequestMutation) ResetClaimsUserID() {\n\tm.claims_user_id = nil\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (m *AuthRequestMutation) SetClaimsUsername(s string) {\n\tm.claims_username = &s\n}\n\n// ClaimsUsername returns the value of the \"claims_username\" field in the mutation.\nfunc (m *AuthRequestMutation) ClaimsUsername() (r string, exists bool) {\n\tv := m.claims_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUsername returns the old \"claims_username\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldClaimsUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsUsername, nil\n}\n\n// ResetClaimsUsername resets all changes to the \"claims_username\" field.\nfunc (m *AuthRequestMutation) ResetClaimsUsername() {\n\tm.claims_username = nil\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (m *AuthRequestMutation) SetClaimsEmail(s string) {\n\tm.claims_email = &s\n}\n\n// ClaimsEmail returns the value of the \"claims_email\" field in the mutation.\nfunc (m *AuthRequestMutation) ClaimsEmail() (r string, exists bool) {\n\tv := m.claims_email\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmail returns the old \"claims_email\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldClaimsEmail(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmail is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmail requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmail: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmail, nil\n}\n\n// ResetClaimsEmail resets all changes to the \"claims_email\" field.\nfunc (m *AuthRequestMutation) ResetClaimsEmail() {\n\tm.claims_email = nil\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (m *AuthRequestMutation) SetClaimsEmailVerified(b bool) {\n\tm.claims_email_verified = &b\n}\n\n// ClaimsEmailVerified returns the value of the \"claims_email_verified\" field in the mutation.\nfunc (m *AuthRequestMutation) ClaimsEmailVerified() (r bool, exists bool) {\n\tv := m.claims_email_verified\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmailVerified returns the old \"claims_email_verified\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmailVerified: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmailVerified, nil\n}\n\n// ResetClaimsEmailVerified resets all changes to the \"claims_email_verified\" field.\nfunc (m *AuthRequestMutation) ResetClaimsEmailVerified() {\n\tm.claims_email_verified = nil\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (m *AuthRequestMutation) SetClaimsGroups(s []string) {\n\tm.claims_groups = &s\n\tm.appendclaims_groups = nil\n}\n\n// ClaimsGroups returns the value of the \"claims_groups\" field in the mutation.\nfunc (m *AuthRequestMutation) ClaimsGroups() (r []string, exists bool) {\n\tv := m.claims_groups\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsGroups returns the old \"claims_groups\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsGroups is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsGroups requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsGroups: %w\", err)\n\t}\n\treturn oldValue.ClaimsGroups, nil\n}\n\n// AppendClaimsGroups adds s to the \"claims_groups\" field.\nfunc (m *AuthRequestMutation) AppendClaimsGroups(s []string) {\n\tm.appendclaims_groups = append(m.appendclaims_groups, s...)\n}\n\n// AppendedClaimsGroups returns the list of values that were appended to the \"claims_groups\" field in this mutation.\nfunc (m *AuthRequestMutation) AppendedClaimsGroups() ([]string, bool) {\n\tif len(m.appendclaims_groups) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendclaims_groups, true\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (m *AuthRequestMutation) ClearClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tm.clearedFields[authrequest.FieldClaimsGroups] = struct{}{}\n}\n\n// ClaimsGroupsCleared returns if the \"claims_groups\" field was cleared in this mutation.\nfunc (m *AuthRequestMutation) ClaimsGroupsCleared() bool {\n\t_, ok := m.clearedFields[authrequest.FieldClaimsGroups]\n\treturn ok\n}\n\n// ResetClaimsGroups resets all changes to the \"claims_groups\" field.\nfunc (m *AuthRequestMutation) ResetClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tdelete(m.clearedFields, authrequest.FieldClaimsGroups)\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (m *AuthRequestMutation) SetClaimsPreferredUsername(s string) {\n\tm.claims_preferred_username = &s\n}\n\n// ClaimsPreferredUsername returns the value of the \"claims_preferred_username\" field in the mutation.\nfunc (m *AuthRequestMutation) ClaimsPreferredUsername() (r string, exists bool) {\n\tv := m.claims_preferred_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsPreferredUsername returns the old \"claims_preferred_username\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsPreferredUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsPreferredUsername, nil\n}\n\n// ResetClaimsPreferredUsername resets all changes to the \"claims_preferred_username\" field.\nfunc (m *AuthRequestMutation) ResetClaimsPreferredUsername() {\n\tm.claims_preferred_username = nil\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (m *AuthRequestMutation) SetConnectorID(s string) {\n\tm.connector_id = &s\n}\n\n// ConnectorID returns the value of the \"connector_id\" field in the mutation.\nfunc (m *AuthRequestMutation) ConnectorID() (r string, exists bool) {\n\tv := m.connector_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorID returns the old \"connector_id\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldConnectorID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorID: %w\", err)\n\t}\n\treturn oldValue.ConnectorID, nil\n}\n\n// ResetConnectorID resets all changes to the \"connector_id\" field.\nfunc (m *AuthRequestMutation) ResetConnectorID() {\n\tm.connector_id = nil\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (m *AuthRequestMutation) SetConnectorData(b []byte) {\n\tm.connector_data = &b\n}\n\n// ConnectorData returns the value of the \"connector_data\" field in the mutation.\nfunc (m *AuthRequestMutation) ConnectorData() (r []byte, exists bool) {\n\tv := m.connector_data\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorData returns the old \"connector_data\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorData is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorData requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorData: %w\", err)\n\t}\n\treturn oldValue.ConnectorData, nil\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (m *AuthRequestMutation) ClearConnectorData() {\n\tm.connector_data = nil\n\tm.clearedFields[authrequest.FieldConnectorData] = struct{}{}\n}\n\n// ConnectorDataCleared returns if the \"connector_data\" field was cleared in this mutation.\nfunc (m *AuthRequestMutation) ConnectorDataCleared() bool {\n\t_, ok := m.clearedFields[authrequest.FieldConnectorData]\n\treturn ok\n}\n\n// ResetConnectorData resets all changes to the \"connector_data\" field.\nfunc (m *AuthRequestMutation) ResetConnectorData() {\n\tm.connector_data = nil\n\tdelete(m.clearedFields, authrequest.FieldConnectorData)\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (m *AuthRequestMutation) SetExpiry(t time.Time) {\n\tm.expiry = &t\n}\n\n// Expiry returns the value of the \"expiry\" field in the mutation.\nfunc (m *AuthRequestMutation) Expiry() (r time.Time, exists bool) {\n\tv := m.expiry\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldExpiry returns the old \"expiry\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldExpiry(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldExpiry is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldExpiry requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldExpiry: %w\", err)\n\t}\n\treturn oldValue.Expiry, nil\n}\n\n// ResetExpiry resets all changes to the \"expiry\" field.\nfunc (m *AuthRequestMutation) ResetExpiry() {\n\tm.expiry = nil\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (m *AuthRequestMutation) SetCodeChallenge(s string) {\n\tm.code_challenge = &s\n}\n\n// CodeChallenge returns the value of the \"code_challenge\" field in the mutation.\nfunc (m *AuthRequestMutation) CodeChallenge() (r string, exists bool) {\n\tv := m.code_challenge\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCodeChallenge returns the old \"code_challenge\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldCodeChallenge(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCodeChallenge is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCodeChallenge requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCodeChallenge: %w\", err)\n\t}\n\treturn oldValue.CodeChallenge, nil\n}\n\n// ResetCodeChallenge resets all changes to the \"code_challenge\" field.\nfunc (m *AuthRequestMutation) ResetCodeChallenge() {\n\tm.code_challenge = nil\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (m *AuthRequestMutation) SetCodeChallengeMethod(s string) {\n\tm.code_challenge_method = &s\n}\n\n// CodeChallengeMethod returns the value of the \"code_challenge_method\" field in the mutation.\nfunc (m *AuthRequestMutation) CodeChallengeMethod() (r string, exists bool) {\n\tv := m.code_challenge_method\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCodeChallengeMethod returns the old \"code_challenge_method\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldCodeChallengeMethod(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCodeChallengeMethod is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCodeChallengeMethod requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCodeChallengeMethod: %w\", err)\n\t}\n\treturn oldValue.CodeChallengeMethod, nil\n}\n\n// ResetCodeChallengeMethod resets all changes to the \"code_challenge_method\" field.\nfunc (m *AuthRequestMutation) ResetCodeChallengeMethod() {\n\tm.code_challenge_method = nil\n}\n\n// SetHmacKey sets the \"hmac_key\" field.\nfunc (m *AuthRequestMutation) SetHmacKey(b []byte) {\n\tm.hmac_key = &b\n}\n\n// HmacKey returns the value of the \"hmac_key\" field in the mutation.\nfunc (m *AuthRequestMutation) HmacKey() (r []byte, exists bool) {\n\tv := m.hmac_key\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldHmacKey returns the old \"hmac_key\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldHmacKey(ctx context.Context) (v []byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldHmacKey is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldHmacKey requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldHmacKey: %w\", err)\n\t}\n\treturn oldValue.HmacKey, nil\n}\n\n// ResetHmacKey resets all changes to the \"hmac_key\" field.\nfunc (m *AuthRequestMutation) ResetHmacKey() {\n\tm.hmac_key = nil\n}\n\n// SetMfaValidated sets the \"mfa_validated\" field.\nfunc (m *AuthRequestMutation) SetMfaValidated(b bool) {\n\tm.mfa_validated = &b\n}\n\n// MfaValidated returns the value of the \"mfa_validated\" field in the mutation.\nfunc (m *AuthRequestMutation) MfaValidated() (r bool, exists bool) {\n\tv := m.mfa_validated\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldMfaValidated returns the old \"mfa_validated\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldMfaValidated(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldMfaValidated is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldMfaValidated requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldMfaValidated: %w\", err)\n\t}\n\treturn oldValue.MfaValidated, nil\n}\n\n// ResetMfaValidated resets all changes to the \"mfa_validated\" field.\nfunc (m *AuthRequestMutation) ResetMfaValidated() {\n\tm.mfa_validated = nil\n}\n\n// SetPrompt sets the \"prompt\" field.\nfunc (m *AuthRequestMutation) SetPrompt(s string) {\n\tm.prompt = &s\n}\n\n// Prompt returns the value of the \"prompt\" field in the mutation.\nfunc (m *AuthRequestMutation) Prompt() (r string, exists bool) {\n\tv := m.prompt\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldPrompt returns the old \"prompt\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldPrompt(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldPrompt is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldPrompt requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldPrompt: %w\", err)\n\t}\n\treturn oldValue.Prompt, nil\n}\n\n// ResetPrompt resets all changes to the \"prompt\" field.\nfunc (m *AuthRequestMutation) ResetPrompt() {\n\tm.prompt = nil\n}\n\n// SetMaxAge sets the \"max_age\" field.\nfunc (m *AuthRequestMutation) SetMaxAge(i int) {\n\tm.max_age = &i\n\tm.addmax_age = nil\n}\n\n// MaxAge returns the value of the \"max_age\" field in the mutation.\nfunc (m *AuthRequestMutation) MaxAge() (r int, exists bool) {\n\tv := m.max_age\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldMaxAge returns the old \"max_age\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldMaxAge(ctx context.Context) (v int, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldMaxAge is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldMaxAge requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldMaxAge: %w\", err)\n\t}\n\treturn oldValue.MaxAge, nil\n}\n\n// AddMaxAge adds i to the \"max_age\" field.\nfunc (m *AuthRequestMutation) AddMaxAge(i int) {\n\tif m.addmax_age != nil {\n\t\t*m.addmax_age += i\n\t} else {\n\t\tm.addmax_age = &i\n\t}\n}\n\n// AddedMaxAge returns the value that was added to the \"max_age\" field in this mutation.\nfunc (m *AuthRequestMutation) AddedMaxAge() (r int, exists bool) {\n\tv := m.addmax_age\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// ResetMaxAge resets all changes to the \"max_age\" field.\nfunc (m *AuthRequestMutation) ResetMaxAge() {\n\tm.max_age = nil\n\tm.addmax_age = nil\n}\n\n// SetAuthTime sets the \"auth_time\" field.\nfunc (m *AuthRequestMutation) SetAuthTime(t time.Time) {\n\tm.auth_time = &t\n}\n\n// AuthTime returns the value of the \"auth_time\" field in the mutation.\nfunc (m *AuthRequestMutation) AuthTime() (r time.Time, exists bool) {\n\tv := m.auth_time\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldAuthTime returns the old \"auth_time\" field's value of the AuthRequest entity.\n// If the AuthRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthRequestMutation) OldAuthTime(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldAuthTime is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldAuthTime requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldAuthTime: %w\", err)\n\t}\n\treturn oldValue.AuthTime, nil\n}\n\n// ClearAuthTime clears the value of the \"auth_time\" field.\nfunc (m *AuthRequestMutation) ClearAuthTime() {\n\tm.auth_time = nil\n\tm.clearedFields[authrequest.FieldAuthTime] = struct{}{}\n}\n\n// AuthTimeCleared returns if the \"auth_time\" field was cleared in this mutation.\nfunc (m *AuthRequestMutation) AuthTimeCleared() bool {\n\t_, ok := m.clearedFields[authrequest.FieldAuthTime]\n\treturn ok\n}\n\n// ResetAuthTime resets all changes to the \"auth_time\" field.\nfunc (m *AuthRequestMutation) ResetAuthTime() {\n\tm.auth_time = nil\n\tdelete(m.clearedFields, authrequest.FieldAuthTime)\n}\n\n// Where appends a list predicates to the AuthRequestMutation builder.\nfunc (m *AuthRequestMutation) Where(ps ...predicate.AuthRequest) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the AuthRequestMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *AuthRequestMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.AuthRequest, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *AuthRequestMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *AuthRequestMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (AuthRequest).\nfunc (m *AuthRequestMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *AuthRequestMutation) Fields() []string {\n\tfields := make([]string, 0, 24)\n\tif m.client_id != nil {\n\t\tfields = append(fields, authrequest.FieldClientID)\n\t}\n\tif m.scopes != nil {\n\t\tfields = append(fields, authrequest.FieldScopes)\n\t}\n\tif m.response_types != nil {\n\t\tfields = append(fields, authrequest.FieldResponseTypes)\n\t}\n\tif m.redirect_uri != nil {\n\t\tfields = append(fields, authrequest.FieldRedirectURI)\n\t}\n\tif m.nonce != nil {\n\t\tfields = append(fields, authrequest.FieldNonce)\n\t}\n\tif m.state != nil {\n\t\tfields = append(fields, authrequest.FieldState)\n\t}\n\tif m.force_approval_prompt != nil {\n\t\tfields = append(fields, authrequest.FieldForceApprovalPrompt)\n\t}\n\tif m.logged_in != nil {\n\t\tfields = append(fields, authrequest.FieldLoggedIn)\n\t}\n\tif m.claims_user_id != nil {\n\t\tfields = append(fields, authrequest.FieldClaimsUserID)\n\t}\n\tif m.claims_username != nil {\n\t\tfields = append(fields, authrequest.FieldClaimsUsername)\n\t}\n\tif m.claims_email != nil {\n\t\tfields = append(fields, authrequest.FieldClaimsEmail)\n\t}\n\tif m.claims_email_verified != nil {\n\t\tfields = append(fields, authrequest.FieldClaimsEmailVerified)\n\t}\n\tif m.claims_groups != nil {\n\t\tfields = append(fields, authrequest.FieldClaimsGroups)\n\t}\n\tif m.claims_preferred_username != nil {\n\t\tfields = append(fields, authrequest.FieldClaimsPreferredUsername)\n\t}\n\tif m.connector_id != nil {\n\t\tfields = append(fields, authrequest.FieldConnectorID)\n\t}\n\tif m.connector_data != nil {\n\t\tfields = append(fields, authrequest.FieldConnectorData)\n\t}\n\tif m.expiry != nil {\n\t\tfields = append(fields, authrequest.FieldExpiry)\n\t}\n\tif m.code_challenge != nil {\n\t\tfields = append(fields, authrequest.FieldCodeChallenge)\n\t}\n\tif m.code_challenge_method != nil {\n\t\tfields = append(fields, authrequest.FieldCodeChallengeMethod)\n\t}\n\tif m.hmac_key != nil {\n\t\tfields = append(fields, authrequest.FieldHmacKey)\n\t}\n\tif m.mfa_validated != nil {\n\t\tfields = append(fields, authrequest.FieldMfaValidated)\n\t}\n\tif m.prompt != nil {\n\t\tfields = append(fields, authrequest.FieldPrompt)\n\t}\n\tif m.max_age != nil {\n\t\tfields = append(fields, authrequest.FieldMaxAge)\n\t}\n\tif m.auth_time != nil {\n\t\tfields = append(fields, authrequest.FieldAuthTime)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *AuthRequestMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase authrequest.FieldClientID:\n\t\treturn m.ClientID()\n\tcase authrequest.FieldScopes:\n\t\treturn m.Scopes()\n\tcase authrequest.FieldResponseTypes:\n\t\treturn m.ResponseTypes()\n\tcase authrequest.FieldRedirectURI:\n\t\treturn m.RedirectURI()\n\tcase authrequest.FieldNonce:\n\t\treturn m.Nonce()\n\tcase authrequest.FieldState:\n\t\treturn m.State()\n\tcase authrequest.FieldForceApprovalPrompt:\n\t\treturn m.ForceApprovalPrompt()\n\tcase authrequest.FieldLoggedIn:\n\t\treturn m.LoggedIn()\n\tcase authrequest.FieldClaimsUserID:\n\t\treturn m.ClaimsUserID()\n\tcase authrequest.FieldClaimsUsername:\n\t\treturn m.ClaimsUsername()\n\tcase authrequest.FieldClaimsEmail:\n\t\treturn m.ClaimsEmail()\n\tcase authrequest.FieldClaimsEmailVerified:\n\t\treturn m.ClaimsEmailVerified()\n\tcase authrequest.FieldClaimsGroups:\n\t\treturn m.ClaimsGroups()\n\tcase authrequest.FieldClaimsPreferredUsername:\n\t\treturn m.ClaimsPreferredUsername()\n\tcase authrequest.FieldConnectorID:\n\t\treturn m.ConnectorID()\n\tcase authrequest.FieldConnectorData:\n\t\treturn m.ConnectorData()\n\tcase authrequest.FieldExpiry:\n\t\treturn m.Expiry()\n\tcase authrequest.FieldCodeChallenge:\n\t\treturn m.CodeChallenge()\n\tcase authrequest.FieldCodeChallengeMethod:\n\t\treturn m.CodeChallengeMethod()\n\tcase authrequest.FieldHmacKey:\n\t\treturn m.HmacKey()\n\tcase authrequest.FieldMfaValidated:\n\t\treturn m.MfaValidated()\n\tcase authrequest.FieldPrompt:\n\t\treturn m.Prompt()\n\tcase authrequest.FieldMaxAge:\n\t\treturn m.MaxAge()\n\tcase authrequest.FieldAuthTime:\n\t\treturn m.AuthTime()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *AuthRequestMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase authrequest.FieldClientID:\n\t\treturn m.OldClientID(ctx)\n\tcase authrequest.FieldScopes:\n\t\treturn m.OldScopes(ctx)\n\tcase authrequest.FieldResponseTypes:\n\t\treturn m.OldResponseTypes(ctx)\n\tcase authrequest.FieldRedirectURI:\n\t\treturn m.OldRedirectURI(ctx)\n\tcase authrequest.FieldNonce:\n\t\treturn m.OldNonce(ctx)\n\tcase authrequest.FieldState:\n\t\treturn m.OldState(ctx)\n\tcase authrequest.FieldForceApprovalPrompt:\n\t\treturn m.OldForceApprovalPrompt(ctx)\n\tcase authrequest.FieldLoggedIn:\n\t\treturn m.OldLoggedIn(ctx)\n\tcase authrequest.FieldClaimsUserID:\n\t\treturn m.OldClaimsUserID(ctx)\n\tcase authrequest.FieldClaimsUsername:\n\t\treturn m.OldClaimsUsername(ctx)\n\tcase authrequest.FieldClaimsEmail:\n\t\treturn m.OldClaimsEmail(ctx)\n\tcase authrequest.FieldClaimsEmailVerified:\n\t\treturn m.OldClaimsEmailVerified(ctx)\n\tcase authrequest.FieldClaimsGroups:\n\t\treturn m.OldClaimsGroups(ctx)\n\tcase authrequest.FieldClaimsPreferredUsername:\n\t\treturn m.OldClaimsPreferredUsername(ctx)\n\tcase authrequest.FieldConnectorID:\n\t\treturn m.OldConnectorID(ctx)\n\tcase authrequest.FieldConnectorData:\n\t\treturn m.OldConnectorData(ctx)\n\tcase authrequest.FieldExpiry:\n\t\treturn m.OldExpiry(ctx)\n\tcase authrequest.FieldCodeChallenge:\n\t\treturn m.OldCodeChallenge(ctx)\n\tcase authrequest.FieldCodeChallengeMethod:\n\t\treturn m.OldCodeChallengeMethod(ctx)\n\tcase authrequest.FieldHmacKey:\n\t\treturn m.OldHmacKey(ctx)\n\tcase authrequest.FieldMfaValidated:\n\t\treturn m.OldMfaValidated(ctx)\n\tcase authrequest.FieldPrompt:\n\t\treturn m.OldPrompt(ctx)\n\tcase authrequest.FieldMaxAge:\n\t\treturn m.OldMaxAge(ctx)\n\tcase authrequest.FieldAuthTime:\n\t\treturn m.OldAuthTime(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown AuthRequest field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *AuthRequestMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase authrequest.FieldClientID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClientID(v)\n\t\treturn nil\n\tcase authrequest.FieldScopes:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetScopes(v)\n\t\treturn nil\n\tcase authrequest.FieldResponseTypes:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetResponseTypes(v)\n\t\treturn nil\n\tcase authrequest.FieldRedirectURI:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetRedirectURI(v)\n\t\treturn nil\n\tcase authrequest.FieldNonce:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetNonce(v)\n\t\treturn nil\n\tcase authrequest.FieldState:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetState(v)\n\t\treturn nil\n\tcase authrequest.FieldForceApprovalPrompt:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetForceApprovalPrompt(v)\n\t\treturn nil\n\tcase authrequest.FieldLoggedIn:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetLoggedIn(v)\n\t\treturn nil\n\tcase authrequest.FieldClaimsUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUserID(v)\n\t\treturn nil\n\tcase authrequest.FieldClaimsUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUsername(v)\n\t\treturn nil\n\tcase authrequest.FieldClaimsEmail:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmail(v)\n\t\treturn nil\n\tcase authrequest.FieldClaimsEmailVerified:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmailVerified(v)\n\t\treturn nil\n\tcase authrequest.FieldClaimsGroups:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsGroups(v)\n\t\treturn nil\n\tcase authrequest.FieldClaimsPreferredUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsPreferredUsername(v)\n\t\treturn nil\n\tcase authrequest.FieldConnectorID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorID(v)\n\t\treturn nil\n\tcase authrequest.FieldConnectorData:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorData(v)\n\t\treturn nil\n\tcase authrequest.FieldExpiry:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetExpiry(v)\n\t\treturn nil\n\tcase authrequest.FieldCodeChallenge:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCodeChallenge(v)\n\t\treturn nil\n\tcase authrequest.FieldCodeChallengeMethod:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCodeChallengeMethod(v)\n\t\treturn nil\n\tcase authrequest.FieldHmacKey:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetHmacKey(v)\n\t\treturn nil\n\tcase authrequest.FieldMfaValidated:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetMfaValidated(v)\n\t\treturn nil\n\tcase authrequest.FieldPrompt:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetPrompt(v)\n\t\treturn nil\n\tcase authrequest.FieldMaxAge:\n\t\tv, ok := value.(int)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetMaxAge(v)\n\t\treturn nil\n\tcase authrequest.FieldAuthTime:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetAuthTime(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthRequest field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *AuthRequestMutation) AddedFields() []string {\n\tvar fields []string\n\tif m.addmax_age != nil {\n\t\tfields = append(fields, authrequest.FieldMaxAge)\n\t}\n\treturn fields\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *AuthRequestMutation) AddedField(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase authrequest.FieldMaxAge:\n\t\treturn m.AddedMaxAge()\n\t}\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *AuthRequestMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\tcase authrequest.FieldMaxAge:\n\t\tv, ok := value.(int)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.AddMaxAge(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthRequest numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *AuthRequestMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(authrequest.FieldScopes) {\n\t\tfields = append(fields, authrequest.FieldScopes)\n\t}\n\tif m.FieldCleared(authrequest.FieldResponseTypes) {\n\t\tfields = append(fields, authrequest.FieldResponseTypes)\n\t}\n\tif m.FieldCleared(authrequest.FieldClaimsGroups) {\n\t\tfields = append(fields, authrequest.FieldClaimsGroups)\n\t}\n\tif m.FieldCleared(authrequest.FieldConnectorData) {\n\t\tfields = append(fields, authrequest.FieldConnectorData)\n\t}\n\tif m.FieldCleared(authrequest.FieldAuthTime) {\n\t\tfields = append(fields, authrequest.FieldAuthTime)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *AuthRequestMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *AuthRequestMutation) ClearField(name string) error {\n\tswitch name {\n\tcase authrequest.FieldScopes:\n\t\tm.ClearScopes()\n\t\treturn nil\n\tcase authrequest.FieldResponseTypes:\n\t\tm.ClearResponseTypes()\n\t\treturn nil\n\tcase authrequest.FieldClaimsGroups:\n\t\tm.ClearClaimsGroups()\n\t\treturn nil\n\tcase authrequest.FieldConnectorData:\n\t\tm.ClearConnectorData()\n\t\treturn nil\n\tcase authrequest.FieldAuthTime:\n\t\tm.ClearAuthTime()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthRequest nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *AuthRequestMutation) ResetField(name string) error {\n\tswitch name {\n\tcase authrequest.FieldClientID:\n\t\tm.ResetClientID()\n\t\treturn nil\n\tcase authrequest.FieldScopes:\n\t\tm.ResetScopes()\n\t\treturn nil\n\tcase authrequest.FieldResponseTypes:\n\t\tm.ResetResponseTypes()\n\t\treturn nil\n\tcase authrequest.FieldRedirectURI:\n\t\tm.ResetRedirectURI()\n\t\treturn nil\n\tcase authrequest.FieldNonce:\n\t\tm.ResetNonce()\n\t\treturn nil\n\tcase authrequest.FieldState:\n\t\tm.ResetState()\n\t\treturn nil\n\tcase authrequest.FieldForceApprovalPrompt:\n\t\tm.ResetForceApprovalPrompt()\n\t\treturn nil\n\tcase authrequest.FieldLoggedIn:\n\t\tm.ResetLoggedIn()\n\t\treturn nil\n\tcase authrequest.FieldClaimsUserID:\n\t\tm.ResetClaimsUserID()\n\t\treturn nil\n\tcase authrequest.FieldClaimsUsername:\n\t\tm.ResetClaimsUsername()\n\t\treturn nil\n\tcase authrequest.FieldClaimsEmail:\n\t\tm.ResetClaimsEmail()\n\t\treturn nil\n\tcase authrequest.FieldClaimsEmailVerified:\n\t\tm.ResetClaimsEmailVerified()\n\t\treturn nil\n\tcase authrequest.FieldClaimsGroups:\n\t\tm.ResetClaimsGroups()\n\t\treturn nil\n\tcase authrequest.FieldClaimsPreferredUsername:\n\t\tm.ResetClaimsPreferredUsername()\n\t\treturn nil\n\tcase authrequest.FieldConnectorID:\n\t\tm.ResetConnectorID()\n\t\treturn nil\n\tcase authrequest.FieldConnectorData:\n\t\tm.ResetConnectorData()\n\t\treturn nil\n\tcase authrequest.FieldExpiry:\n\t\tm.ResetExpiry()\n\t\treturn nil\n\tcase authrequest.FieldCodeChallenge:\n\t\tm.ResetCodeChallenge()\n\t\treturn nil\n\tcase authrequest.FieldCodeChallengeMethod:\n\t\tm.ResetCodeChallengeMethod()\n\t\treturn nil\n\tcase authrequest.FieldHmacKey:\n\t\tm.ResetHmacKey()\n\t\treturn nil\n\tcase authrequest.FieldMfaValidated:\n\t\tm.ResetMfaValidated()\n\t\treturn nil\n\tcase authrequest.FieldPrompt:\n\t\tm.ResetPrompt()\n\t\treturn nil\n\tcase authrequest.FieldMaxAge:\n\t\tm.ResetMaxAge()\n\t\treturn nil\n\tcase authrequest.FieldAuthTime:\n\t\tm.ResetAuthTime()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthRequest field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *AuthRequestMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *AuthRequestMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *AuthRequestMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *AuthRequestMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *AuthRequestMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *AuthRequestMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *AuthRequestMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown AuthRequest unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *AuthRequestMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown AuthRequest edge %s\", name)\n}\n\n// AuthSessionMutation represents an operation that mutates the AuthSession nodes in the graph.\ntype AuthSessionMutation struct {\n\tconfig\n\top              Op\n\ttyp             string\n\tid              *string\n\tuser_id         *string\n\tconnector_id    *string\n\tnonce           *string\n\tclient_states   *[]byte\n\tcreated_at      *time.Time\n\tlast_activity   *time.Time\n\tip_address      *string\n\tuser_agent      *string\n\tabsolute_expiry *time.Time\n\tidle_expiry     *time.Time\n\tclearedFields   map[string]struct{}\n\tdone            bool\n\toldValue        func(context.Context) (*AuthSession, error)\n\tpredicates      []predicate.AuthSession\n}\n\nvar _ ent.Mutation = (*AuthSessionMutation)(nil)\n\n// authsessionOption allows management of the mutation configuration using functional options.\ntype authsessionOption func(*AuthSessionMutation)\n\n// newAuthSessionMutation creates new mutation for the AuthSession entity.\nfunc newAuthSessionMutation(c config, op Op, opts ...authsessionOption) *AuthSessionMutation {\n\tm := &AuthSessionMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeAuthSession,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withAuthSessionID sets the ID field of the mutation.\nfunc withAuthSessionID(id string) authsessionOption {\n\treturn func(m *AuthSessionMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *AuthSession\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*AuthSession, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().AuthSession.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withAuthSession sets the old AuthSession of the mutation.\nfunc withAuthSession(node *AuthSession) authsessionOption {\n\treturn func(m *AuthSessionMutation) {\n\t\tm.oldValue = func(context.Context) (*AuthSession, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m AuthSessionMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m AuthSessionMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of AuthSession entities.\nfunc (m *AuthSessionMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *AuthSessionMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *AuthSessionMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().AuthSession.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (m *AuthSessionMutation) SetUserID(s string) {\n\tm.user_id = &s\n}\n\n// UserID returns the value of the \"user_id\" field in the mutation.\nfunc (m *AuthSessionMutation) UserID() (r string, exists bool) {\n\tv := m.user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldUserID returns the old \"user_id\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldUserID: %w\", err)\n\t}\n\treturn oldValue.UserID, nil\n}\n\n// ResetUserID resets all changes to the \"user_id\" field.\nfunc (m *AuthSessionMutation) ResetUserID() {\n\tm.user_id = nil\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (m *AuthSessionMutation) SetConnectorID(s string) {\n\tm.connector_id = &s\n}\n\n// ConnectorID returns the value of the \"connector_id\" field in the mutation.\nfunc (m *AuthSessionMutation) ConnectorID() (r string, exists bool) {\n\tv := m.connector_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorID returns the old \"connector_id\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldConnectorID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorID: %w\", err)\n\t}\n\treturn oldValue.ConnectorID, nil\n}\n\n// ResetConnectorID resets all changes to the \"connector_id\" field.\nfunc (m *AuthSessionMutation) ResetConnectorID() {\n\tm.connector_id = nil\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (m *AuthSessionMutation) SetNonce(s string) {\n\tm.nonce = &s\n}\n\n// Nonce returns the value of the \"nonce\" field in the mutation.\nfunc (m *AuthSessionMutation) Nonce() (r string, exists bool) {\n\tv := m.nonce\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldNonce returns the old \"nonce\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldNonce(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldNonce is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldNonce requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldNonce: %w\", err)\n\t}\n\treturn oldValue.Nonce, nil\n}\n\n// ResetNonce resets all changes to the \"nonce\" field.\nfunc (m *AuthSessionMutation) ResetNonce() {\n\tm.nonce = nil\n}\n\n// SetClientStates sets the \"client_states\" field.\nfunc (m *AuthSessionMutation) SetClientStates(b []byte) {\n\tm.client_states = &b\n}\n\n// ClientStates returns the value of the \"client_states\" field in the mutation.\nfunc (m *AuthSessionMutation) ClientStates() (r []byte, exists bool) {\n\tv := m.client_states\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClientStates returns the old \"client_states\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldClientStates(ctx context.Context) (v []byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClientStates is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClientStates requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClientStates: %w\", err)\n\t}\n\treturn oldValue.ClientStates, nil\n}\n\n// ResetClientStates resets all changes to the \"client_states\" field.\nfunc (m *AuthSessionMutation) ResetClientStates() {\n\tm.client_states = nil\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (m *AuthSessionMutation) SetCreatedAt(t time.Time) {\n\tm.created_at = &t\n}\n\n// CreatedAt returns the value of the \"created_at\" field in the mutation.\nfunc (m *AuthSessionMutation) CreatedAt() (r time.Time, exists bool) {\n\tv := m.created_at\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCreatedAt returns the old \"created_at\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCreatedAt is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCreatedAt requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCreatedAt: %w\", err)\n\t}\n\treturn oldValue.CreatedAt, nil\n}\n\n// ResetCreatedAt resets all changes to the \"created_at\" field.\nfunc (m *AuthSessionMutation) ResetCreatedAt() {\n\tm.created_at = nil\n}\n\n// SetLastActivity sets the \"last_activity\" field.\nfunc (m *AuthSessionMutation) SetLastActivity(t time.Time) {\n\tm.last_activity = &t\n}\n\n// LastActivity returns the value of the \"last_activity\" field in the mutation.\nfunc (m *AuthSessionMutation) LastActivity() (r time.Time, exists bool) {\n\tv := m.last_activity\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldLastActivity returns the old \"last_activity\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldLastActivity(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldLastActivity is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldLastActivity requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldLastActivity: %w\", err)\n\t}\n\treturn oldValue.LastActivity, nil\n}\n\n// ResetLastActivity resets all changes to the \"last_activity\" field.\nfunc (m *AuthSessionMutation) ResetLastActivity() {\n\tm.last_activity = nil\n}\n\n// SetIPAddress sets the \"ip_address\" field.\nfunc (m *AuthSessionMutation) SetIPAddress(s string) {\n\tm.ip_address = &s\n}\n\n// IPAddress returns the value of the \"ip_address\" field in the mutation.\nfunc (m *AuthSessionMutation) IPAddress() (r string, exists bool) {\n\tv := m.ip_address\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldIPAddress returns the old \"ip_address\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldIPAddress(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldIPAddress is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldIPAddress requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldIPAddress: %w\", err)\n\t}\n\treturn oldValue.IPAddress, nil\n}\n\n// ResetIPAddress resets all changes to the \"ip_address\" field.\nfunc (m *AuthSessionMutation) ResetIPAddress() {\n\tm.ip_address = nil\n}\n\n// SetUserAgent sets the \"user_agent\" field.\nfunc (m *AuthSessionMutation) SetUserAgent(s string) {\n\tm.user_agent = &s\n}\n\n// UserAgent returns the value of the \"user_agent\" field in the mutation.\nfunc (m *AuthSessionMutation) UserAgent() (r string, exists bool) {\n\tv := m.user_agent\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldUserAgent returns the old \"user_agent\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldUserAgent(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldUserAgent is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldUserAgent requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldUserAgent: %w\", err)\n\t}\n\treturn oldValue.UserAgent, nil\n}\n\n// ResetUserAgent resets all changes to the \"user_agent\" field.\nfunc (m *AuthSessionMutation) ResetUserAgent() {\n\tm.user_agent = nil\n}\n\n// SetAbsoluteExpiry sets the \"absolute_expiry\" field.\nfunc (m *AuthSessionMutation) SetAbsoluteExpiry(t time.Time) {\n\tm.absolute_expiry = &t\n}\n\n// AbsoluteExpiry returns the value of the \"absolute_expiry\" field in the mutation.\nfunc (m *AuthSessionMutation) AbsoluteExpiry() (r time.Time, exists bool) {\n\tv := m.absolute_expiry\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldAbsoluteExpiry returns the old \"absolute_expiry\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldAbsoluteExpiry(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldAbsoluteExpiry is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldAbsoluteExpiry requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldAbsoluteExpiry: %w\", err)\n\t}\n\treturn oldValue.AbsoluteExpiry, nil\n}\n\n// ResetAbsoluteExpiry resets all changes to the \"absolute_expiry\" field.\nfunc (m *AuthSessionMutation) ResetAbsoluteExpiry() {\n\tm.absolute_expiry = nil\n}\n\n// SetIdleExpiry sets the \"idle_expiry\" field.\nfunc (m *AuthSessionMutation) SetIdleExpiry(t time.Time) {\n\tm.idle_expiry = &t\n}\n\n// IdleExpiry returns the value of the \"idle_expiry\" field in the mutation.\nfunc (m *AuthSessionMutation) IdleExpiry() (r time.Time, exists bool) {\n\tv := m.idle_expiry\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldIdleExpiry returns the old \"idle_expiry\" field's value of the AuthSession entity.\n// If the AuthSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *AuthSessionMutation) OldIdleExpiry(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldIdleExpiry is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldIdleExpiry requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldIdleExpiry: %w\", err)\n\t}\n\treturn oldValue.IdleExpiry, nil\n}\n\n// ResetIdleExpiry resets all changes to the \"idle_expiry\" field.\nfunc (m *AuthSessionMutation) ResetIdleExpiry() {\n\tm.idle_expiry = nil\n}\n\n// Where appends a list predicates to the AuthSessionMutation builder.\nfunc (m *AuthSessionMutation) Where(ps ...predicate.AuthSession) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the AuthSessionMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *AuthSessionMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.AuthSession, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *AuthSessionMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *AuthSessionMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (AuthSession).\nfunc (m *AuthSessionMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *AuthSessionMutation) Fields() []string {\n\tfields := make([]string, 0, 10)\n\tif m.user_id != nil {\n\t\tfields = append(fields, authsession.FieldUserID)\n\t}\n\tif m.connector_id != nil {\n\t\tfields = append(fields, authsession.FieldConnectorID)\n\t}\n\tif m.nonce != nil {\n\t\tfields = append(fields, authsession.FieldNonce)\n\t}\n\tif m.client_states != nil {\n\t\tfields = append(fields, authsession.FieldClientStates)\n\t}\n\tif m.created_at != nil {\n\t\tfields = append(fields, authsession.FieldCreatedAt)\n\t}\n\tif m.last_activity != nil {\n\t\tfields = append(fields, authsession.FieldLastActivity)\n\t}\n\tif m.ip_address != nil {\n\t\tfields = append(fields, authsession.FieldIPAddress)\n\t}\n\tif m.user_agent != nil {\n\t\tfields = append(fields, authsession.FieldUserAgent)\n\t}\n\tif m.absolute_expiry != nil {\n\t\tfields = append(fields, authsession.FieldAbsoluteExpiry)\n\t}\n\tif m.idle_expiry != nil {\n\t\tfields = append(fields, authsession.FieldIdleExpiry)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *AuthSessionMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase authsession.FieldUserID:\n\t\treturn m.UserID()\n\tcase authsession.FieldConnectorID:\n\t\treturn m.ConnectorID()\n\tcase authsession.FieldNonce:\n\t\treturn m.Nonce()\n\tcase authsession.FieldClientStates:\n\t\treturn m.ClientStates()\n\tcase authsession.FieldCreatedAt:\n\t\treturn m.CreatedAt()\n\tcase authsession.FieldLastActivity:\n\t\treturn m.LastActivity()\n\tcase authsession.FieldIPAddress:\n\t\treturn m.IPAddress()\n\tcase authsession.FieldUserAgent:\n\t\treturn m.UserAgent()\n\tcase authsession.FieldAbsoluteExpiry:\n\t\treturn m.AbsoluteExpiry()\n\tcase authsession.FieldIdleExpiry:\n\t\treturn m.IdleExpiry()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *AuthSessionMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase authsession.FieldUserID:\n\t\treturn m.OldUserID(ctx)\n\tcase authsession.FieldConnectorID:\n\t\treturn m.OldConnectorID(ctx)\n\tcase authsession.FieldNonce:\n\t\treturn m.OldNonce(ctx)\n\tcase authsession.FieldClientStates:\n\t\treturn m.OldClientStates(ctx)\n\tcase authsession.FieldCreatedAt:\n\t\treturn m.OldCreatedAt(ctx)\n\tcase authsession.FieldLastActivity:\n\t\treturn m.OldLastActivity(ctx)\n\tcase authsession.FieldIPAddress:\n\t\treturn m.OldIPAddress(ctx)\n\tcase authsession.FieldUserAgent:\n\t\treturn m.OldUserAgent(ctx)\n\tcase authsession.FieldAbsoluteExpiry:\n\t\treturn m.OldAbsoluteExpiry(ctx)\n\tcase authsession.FieldIdleExpiry:\n\t\treturn m.OldIdleExpiry(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown AuthSession field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *AuthSessionMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase authsession.FieldUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetUserID(v)\n\t\treturn nil\n\tcase authsession.FieldConnectorID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorID(v)\n\t\treturn nil\n\tcase authsession.FieldNonce:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetNonce(v)\n\t\treturn nil\n\tcase authsession.FieldClientStates:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClientStates(v)\n\t\treturn nil\n\tcase authsession.FieldCreatedAt:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCreatedAt(v)\n\t\treturn nil\n\tcase authsession.FieldLastActivity:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetLastActivity(v)\n\t\treturn nil\n\tcase authsession.FieldIPAddress:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetIPAddress(v)\n\t\treturn nil\n\tcase authsession.FieldUserAgent:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetUserAgent(v)\n\t\treturn nil\n\tcase authsession.FieldAbsoluteExpiry:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetAbsoluteExpiry(v)\n\t\treturn nil\n\tcase authsession.FieldIdleExpiry:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetIdleExpiry(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthSession field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *AuthSessionMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *AuthSessionMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *AuthSessionMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown AuthSession numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *AuthSessionMutation) ClearedFields() []string {\n\treturn nil\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *AuthSessionMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *AuthSessionMutation) ClearField(name string) error {\n\treturn fmt.Errorf(\"unknown AuthSession nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *AuthSessionMutation) ResetField(name string) error {\n\tswitch name {\n\tcase authsession.FieldUserID:\n\t\tm.ResetUserID()\n\t\treturn nil\n\tcase authsession.FieldConnectorID:\n\t\tm.ResetConnectorID()\n\t\treturn nil\n\tcase authsession.FieldNonce:\n\t\tm.ResetNonce()\n\t\treturn nil\n\tcase authsession.FieldClientStates:\n\t\tm.ResetClientStates()\n\t\treturn nil\n\tcase authsession.FieldCreatedAt:\n\t\tm.ResetCreatedAt()\n\t\treturn nil\n\tcase authsession.FieldLastActivity:\n\t\tm.ResetLastActivity()\n\t\treturn nil\n\tcase authsession.FieldIPAddress:\n\t\tm.ResetIPAddress()\n\t\treturn nil\n\tcase authsession.FieldUserAgent:\n\t\tm.ResetUserAgent()\n\t\treturn nil\n\tcase authsession.FieldAbsoluteExpiry:\n\t\tm.ResetAbsoluteExpiry()\n\t\treturn nil\n\tcase authsession.FieldIdleExpiry:\n\t\tm.ResetIdleExpiry()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown AuthSession field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *AuthSessionMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *AuthSessionMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *AuthSessionMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *AuthSessionMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *AuthSessionMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *AuthSessionMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *AuthSessionMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown AuthSession unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *AuthSessionMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown AuthSession edge %s\", name)\n}\n\n// ConnectorMutation represents an operation that mutates the Connector nodes in the graph.\ntype ConnectorMutation struct {\n\tconfig\n\top                Op\n\ttyp               string\n\tid                *string\n\t_type             *string\n\tname              *string\n\tresource_version  *string\n\t_config           *[]byte\n\tgrant_types       *[]string\n\tappendgrant_types []string\n\tclearedFields     map[string]struct{}\n\tdone              bool\n\toldValue          func(context.Context) (*Connector, error)\n\tpredicates        []predicate.Connector\n}\n\nvar _ ent.Mutation = (*ConnectorMutation)(nil)\n\n// connectorOption allows management of the mutation configuration using functional options.\ntype connectorOption func(*ConnectorMutation)\n\n// newConnectorMutation creates new mutation for the Connector entity.\nfunc newConnectorMutation(c config, op Op, opts ...connectorOption) *ConnectorMutation {\n\tm := &ConnectorMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeConnector,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withConnectorID sets the ID field of the mutation.\nfunc withConnectorID(id string) connectorOption {\n\treturn func(m *ConnectorMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *Connector\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*Connector, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().Connector.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withConnector sets the old Connector of the mutation.\nfunc withConnector(node *Connector) connectorOption {\n\treturn func(m *ConnectorMutation) {\n\t\tm.oldValue = func(context.Context) (*Connector, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m ConnectorMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m ConnectorMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of Connector entities.\nfunc (m *ConnectorMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *ConnectorMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *ConnectorMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().Connector.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetType sets the \"type\" field.\nfunc (m *ConnectorMutation) SetType(s string) {\n\tm._type = &s\n}\n\n// GetType returns the value of the \"type\" field in the mutation.\nfunc (m *ConnectorMutation) GetType() (r string, exists bool) {\n\tv := m._type\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldType returns the old \"type\" field's value of the Connector entity.\n// If the Connector object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *ConnectorMutation) OldType(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldType is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldType requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldType: %w\", err)\n\t}\n\treturn oldValue.Type, nil\n}\n\n// ResetType resets all changes to the \"type\" field.\nfunc (m *ConnectorMutation) ResetType() {\n\tm._type = nil\n}\n\n// SetName sets the \"name\" field.\nfunc (m *ConnectorMutation) SetName(s string) {\n\tm.name = &s\n}\n\n// Name returns the value of the \"name\" field in the mutation.\nfunc (m *ConnectorMutation) Name() (r string, exists bool) {\n\tv := m.name\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldName returns the old \"name\" field's value of the Connector entity.\n// If the Connector object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *ConnectorMutation) OldName(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldName is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldName requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldName: %w\", err)\n\t}\n\treturn oldValue.Name, nil\n}\n\n// ResetName resets all changes to the \"name\" field.\nfunc (m *ConnectorMutation) ResetName() {\n\tm.name = nil\n}\n\n// SetResourceVersion sets the \"resource_version\" field.\nfunc (m *ConnectorMutation) SetResourceVersion(s string) {\n\tm.resource_version = &s\n}\n\n// ResourceVersion returns the value of the \"resource_version\" field in the mutation.\nfunc (m *ConnectorMutation) ResourceVersion() (r string, exists bool) {\n\tv := m.resource_version\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldResourceVersion returns the old \"resource_version\" field's value of the Connector entity.\n// If the Connector object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *ConnectorMutation) OldResourceVersion(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldResourceVersion is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldResourceVersion requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldResourceVersion: %w\", err)\n\t}\n\treturn oldValue.ResourceVersion, nil\n}\n\n// ResetResourceVersion resets all changes to the \"resource_version\" field.\nfunc (m *ConnectorMutation) ResetResourceVersion() {\n\tm.resource_version = nil\n}\n\n// SetConfig sets the \"config\" field.\nfunc (m *ConnectorMutation) SetConfig(b []byte) {\n\tm._config = &b\n}\n\n// Config returns the value of the \"config\" field in the mutation.\nfunc (m *ConnectorMutation) Config() (r []byte, exists bool) {\n\tv := m._config\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConfig returns the old \"config\" field's value of the Connector entity.\n// If the Connector object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *ConnectorMutation) OldConfig(ctx context.Context) (v []byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConfig is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConfig requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConfig: %w\", err)\n\t}\n\treturn oldValue.Config, nil\n}\n\n// ResetConfig resets all changes to the \"config\" field.\nfunc (m *ConnectorMutation) ResetConfig() {\n\tm._config = nil\n}\n\n// SetGrantTypes sets the \"grant_types\" field.\nfunc (m *ConnectorMutation) SetGrantTypes(s []string) {\n\tm.grant_types = &s\n\tm.appendgrant_types = nil\n}\n\n// GrantTypes returns the value of the \"grant_types\" field in the mutation.\nfunc (m *ConnectorMutation) GrantTypes() (r []string, exists bool) {\n\tv := m.grant_types\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldGrantTypes returns the old \"grant_types\" field's value of the Connector entity.\n// If the Connector object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *ConnectorMutation) OldGrantTypes(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldGrantTypes is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldGrantTypes requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldGrantTypes: %w\", err)\n\t}\n\treturn oldValue.GrantTypes, nil\n}\n\n// AppendGrantTypes adds s to the \"grant_types\" field.\nfunc (m *ConnectorMutation) AppendGrantTypes(s []string) {\n\tm.appendgrant_types = append(m.appendgrant_types, s...)\n}\n\n// AppendedGrantTypes returns the list of values that were appended to the \"grant_types\" field in this mutation.\nfunc (m *ConnectorMutation) AppendedGrantTypes() ([]string, bool) {\n\tif len(m.appendgrant_types) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendgrant_types, true\n}\n\n// ClearGrantTypes clears the value of the \"grant_types\" field.\nfunc (m *ConnectorMutation) ClearGrantTypes() {\n\tm.grant_types = nil\n\tm.appendgrant_types = nil\n\tm.clearedFields[connector.FieldGrantTypes] = struct{}{}\n}\n\n// GrantTypesCleared returns if the \"grant_types\" field was cleared in this mutation.\nfunc (m *ConnectorMutation) GrantTypesCleared() bool {\n\t_, ok := m.clearedFields[connector.FieldGrantTypes]\n\treturn ok\n}\n\n// ResetGrantTypes resets all changes to the \"grant_types\" field.\nfunc (m *ConnectorMutation) ResetGrantTypes() {\n\tm.grant_types = nil\n\tm.appendgrant_types = nil\n\tdelete(m.clearedFields, connector.FieldGrantTypes)\n}\n\n// Where appends a list predicates to the ConnectorMutation builder.\nfunc (m *ConnectorMutation) Where(ps ...predicate.Connector) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the ConnectorMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *ConnectorMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.Connector, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *ConnectorMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *ConnectorMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (Connector).\nfunc (m *ConnectorMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *ConnectorMutation) Fields() []string {\n\tfields := make([]string, 0, 5)\n\tif m._type != nil {\n\t\tfields = append(fields, connector.FieldType)\n\t}\n\tif m.name != nil {\n\t\tfields = append(fields, connector.FieldName)\n\t}\n\tif m.resource_version != nil {\n\t\tfields = append(fields, connector.FieldResourceVersion)\n\t}\n\tif m._config != nil {\n\t\tfields = append(fields, connector.FieldConfig)\n\t}\n\tif m.grant_types != nil {\n\t\tfields = append(fields, connector.FieldGrantTypes)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *ConnectorMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase connector.FieldType:\n\t\treturn m.GetType()\n\tcase connector.FieldName:\n\t\treturn m.Name()\n\tcase connector.FieldResourceVersion:\n\t\treturn m.ResourceVersion()\n\tcase connector.FieldConfig:\n\t\treturn m.Config()\n\tcase connector.FieldGrantTypes:\n\t\treturn m.GrantTypes()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *ConnectorMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase connector.FieldType:\n\t\treturn m.OldType(ctx)\n\tcase connector.FieldName:\n\t\treturn m.OldName(ctx)\n\tcase connector.FieldResourceVersion:\n\t\treturn m.OldResourceVersion(ctx)\n\tcase connector.FieldConfig:\n\t\treturn m.OldConfig(ctx)\n\tcase connector.FieldGrantTypes:\n\t\treturn m.OldGrantTypes(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown Connector field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *ConnectorMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase connector.FieldType:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetType(v)\n\t\treturn nil\n\tcase connector.FieldName:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetName(v)\n\t\treturn nil\n\tcase connector.FieldResourceVersion:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetResourceVersion(v)\n\t\treturn nil\n\tcase connector.FieldConfig:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConfig(v)\n\t\treturn nil\n\tcase connector.FieldGrantTypes:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetGrantTypes(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Connector field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *ConnectorMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *ConnectorMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *ConnectorMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown Connector numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *ConnectorMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(connector.FieldGrantTypes) {\n\t\tfields = append(fields, connector.FieldGrantTypes)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *ConnectorMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *ConnectorMutation) ClearField(name string) error {\n\tswitch name {\n\tcase connector.FieldGrantTypes:\n\t\tm.ClearGrantTypes()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Connector nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *ConnectorMutation) ResetField(name string) error {\n\tswitch name {\n\tcase connector.FieldType:\n\t\tm.ResetType()\n\t\treturn nil\n\tcase connector.FieldName:\n\t\tm.ResetName()\n\t\treturn nil\n\tcase connector.FieldResourceVersion:\n\t\tm.ResetResourceVersion()\n\t\treturn nil\n\tcase connector.FieldConfig:\n\t\tm.ResetConfig()\n\t\treturn nil\n\tcase connector.FieldGrantTypes:\n\t\tm.ResetGrantTypes()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Connector field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *ConnectorMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *ConnectorMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *ConnectorMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *ConnectorMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *ConnectorMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *ConnectorMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *ConnectorMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown Connector unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *ConnectorMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown Connector edge %s\", name)\n}\n\n// DeviceRequestMutation represents an operation that mutates the DeviceRequest nodes in the graph.\ntype DeviceRequestMutation struct {\n\tconfig\n\top            Op\n\ttyp           string\n\tid            *int\n\tuser_code     *string\n\tdevice_code   *string\n\tclient_id     *string\n\tclient_secret *string\n\tscopes        *[]string\n\tappendscopes  []string\n\texpiry        *time.Time\n\tclearedFields map[string]struct{}\n\tdone          bool\n\toldValue      func(context.Context) (*DeviceRequest, error)\n\tpredicates    []predicate.DeviceRequest\n}\n\nvar _ ent.Mutation = (*DeviceRequestMutation)(nil)\n\n// devicerequestOption allows management of the mutation configuration using functional options.\ntype devicerequestOption func(*DeviceRequestMutation)\n\n// newDeviceRequestMutation creates new mutation for the DeviceRequest entity.\nfunc newDeviceRequestMutation(c config, op Op, opts ...devicerequestOption) *DeviceRequestMutation {\n\tm := &DeviceRequestMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeDeviceRequest,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withDeviceRequestID sets the ID field of the mutation.\nfunc withDeviceRequestID(id int) devicerequestOption {\n\treturn func(m *DeviceRequestMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *DeviceRequest\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*DeviceRequest, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().DeviceRequest.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withDeviceRequest sets the old DeviceRequest of the mutation.\nfunc withDeviceRequest(node *DeviceRequest) devicerequestOption {\n\treturn func(m *DeviceRequestMutation) {\n\t\tm.oldValue = func(context.Context) (*DeviceRequest, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m DeviceRequestMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m DeviceRequestMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *DeviceRequestMutation) ID() (id int, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *DeviceRequestMutation) IDs(ctx context.Context) ([]int, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []int{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().DeviceRequest.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetUserCode sets the \"user_code\" field.\nfunc (m *DeviceRequestMutation) SetUserCode(s string) {\n\tm.user_code = &s\n}\n\n// UserCode returns the value of the \"user_code\" field in the mutation.\nfunc (m *DeviceRequestMutation) UserCode() (r string, exists bool) {\n\tv := m.user_code\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldUserCode returns the old \"user_code\" field's value of the DeviceRequest entity.\n// If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceRequestMutation) OldUserCode(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldUserCode is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldUserCode requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldUserCode: %w\", err)\n\t}\n\treturn oldValue.UserCode, nil\n}\n\n// ResetUserCode resets all changes to the \"user_code\" field.\nfunc (m *DeviceRequestMutation) ResetUserCode() {\n\tm.user_code = nil\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (m *DeviceRequestMutation) SetDeviceCode(s string) {\n\tm.device_code = &s\n}\n\n// DeviceCode returns the value of the \"device_code\" field in the mutation.\nfunc (m *DeviceRequestMutation) DeviceCode() (r string, exists bool) {\n\tv := m.device_code\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldDeviceCode returns the old \"device_code\" field's value of the DeviceRequest entity.\n// If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceRequestMutation) OldDeviceCode(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldDeviceCode is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldDeviceCode requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldDeviceCode: %w\", err)\n\t}\n\treturn oldValue.DeviceCode, nil\n}\n\n// ResetDeviceCode resets all changes to the \"device_code\" field.\nfunc (m *DeviceRequestMutation) ResetDeviceCode() {\n\tm.device_code = nil\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (m *DeviceRequestMutation) SetClientID(s string) {\n\tm.client_id = &s\n}\n\n// ClientID returns the value of the \"client_id\" field in the mutation.\nfunc (m *DeviceRequestMutation) ClientID() (r string, exists bool) {\n\tv := m.client_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClientID returns the old \"client_id\" field's value of the DeviceRequest entity.\n// If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceRequestMutation) OldClientID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClientID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClientID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClientID: %w\", err)\n\t}\n\treturn oldValue.ClientID, nil\n}\n\n// ResetClientID resets all changes to the \"client_id\" field.\nfunc (m *DeviceRequestMutation) ResetClientID() {\n\tm.client_id = nil\n}\n\n// SetClientSecret sets the \"client_secret\" field.\nfunc (m *DeviceRequestMutation) SetClientSecret(s string) {\n\tm.client_secret = &s\n}\n\n// ClientSecret returns the value of the \"client_secret\" field in the mutation.\nfunc (m *DeviceRequestMutation) ClientSecret() (r string, exists bool) {\n\tv := m.client_secret\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClientSecret returns the old \"client_secret\" field's value of the DeviceRequest entity.\n// If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceRequestMutation) OldClientSecret(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClientSecret is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClientSecret requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClientSecret: %w\", err)\n\t}\n\treturn oldValue.ClientSecret, nil\n}\n\n// ResetClientSecret resets all changes to the \"client_secret\" field.\nfunc (m *DeviceRequestMutation) ResetClientSecret() {\n\tm.client_secret = nil\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (m *DeviceRequestMutation) SetScopes(s []string) {\n\tm.scopes = &s\n\tm.appendscopes = nil\n}\n\n// Scopes returns the value of the \"scopes\" field in the mutation.\nfunc (m *DeviceRequestMutation) Scopes() (r []string, exists bool) {\n\tv := m.scopes\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldScopes returns the old \"scopes\" field's value of the DeviceRequest entity.\n// If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceRequestMutation) OldScopes(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldScopes is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldScopes requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldScopes: %w\", err)\n\t}\n\treturn oldValue.Scopes, nil\n}\n\n// AppendScopes adds s to the \"scopes\" field.\nfunc (m *DeviceRequestMutation) AppendScopes(s []string) {\n\tm.appendscopes = append(m.appendscopes, s...)\n}\n\n// AppendedScopes returns the list of values that were appended to the \"scopes\" field in this mutation.\nfunc (m *DeviceRequestMutation) AppendedScopes() ([]string, bool) {\n\tif len(m.appendscopes) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendscopes, true\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (m *DeviceRequestMutation) ClearScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tm.clearedFields[devicerequest.FieldScopes] = struct{}{}\n}\n\n// ScopesCleared returns if the \"scopes\" field was cleared in this mutation.\nfunc (m *DeviceRequestMutation) ScopesCleared() bool {\n\t_, ok := m.clearedFields[devicerequest.FieldScopes]\n\treturn ok\n}\n\n// ResetScopes resets all changes to the \"scopes\" field.\nfunc (m *DeviceRequestMutation) ResetScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tdelete(m.clearedFields, devicerequest.FieldScopes)\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (m *DeviceRequestMutation) SetExpiry(t time.Time) {\n\tm.expiry = &t\n}\n\n// Expiry returns the value of the \"expiry\" field in the mutation.\nfunc (m *DeviceRequestMutation) Expiry() (r time.Time, exists bool) {\n\tv := m.expiry\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldExpiry returns the old \"expiry\" field's value of the DeviceRequest entity.\n// If the DeviceRequest object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceRequestMutation) OldExpiry(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldExpiry is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldExpiry requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldExpiry: %w\", err)\n\t}\n\treturn oldValue.Expiry, nil\n}\n\n// ResetExpiry resets all changes to the \"expiry\" field.\nfunc (m *DeviceRequestMutation) ResetExpiry() {\n\tm.expiry = nil\n}\n\n// Where appends a list predicates to the DeviceRequestMutation builder.\nfunc (m *DeviceRequestMutation) Where(ps ...predicate.DeviceRequest) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the DeviceRequestMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *DeviceRequestMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.DeviceRequest, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *DeviceRequestMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *DeviceRequestMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (DeviceRequest).\nfunc (m *DeviceRequestMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *DeviceRequestMutation) Fields() []string {\n\tfields := make([]string, 0, 6)\n\tif m.user_code != nil {\n\t\tfields = append(fields, devicerequest.FieldUserCode)\n\t}\n\tif m.device_code != nil {\n\t\tfields = append(fields, devicerequest.FieldDeviceCode)\n\t}\n\tif m.client_id != nil {\n\t\tfields = append(fields, devicerequest.FieldClientID)\n\t}\n\tif m.client_secret != nil {\n\t\tfields = append(fields, devicerequest.FieldClientSecret)\n\t}\n\tif m.scopes != nil {\n\t\tfields = append(fields, devicerequest.FieldScopes)\n\t}\n\tif m.expiry != nil {\n\t\tfields = append(fields, devicerequest.FieldExpiry)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *DeviceRequestMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase devicerequest.FieldUserCode:\n\t\treturn m.UserCode()\n\tcase devicerequest.FieldDeviceCode:\n\t\treturn m.DeviceCode()\n\tcase devicerequest.FieldClientID:\n\t\treturn m.ClientID()\n\tcase devicerequest.FieldClientSecret:\n\t\treturn m.ClientSecret()\n\tcase devicerequest.FieldScopes:\n\t\treturn m.Scopes()\n\tcase devicerequest.FieldExpiry:\n\t\treturn m.Expiry()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *DeviceRequestMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase devicerequest.FieldUserCode:\n\t\treturn m.OldUserCode(ctx)\n\tcase devicerequest.FieldDeviceCode:\n\t\treturn m.OldDeviceCode(ctx)\n\tcase devicerequest.FieldClientID:\n\t\treturn m.OldClientID(ctx)\n\tcase devicerequest.FieldClientSecret:\n\t\treturn m.OldClientSecret(ctx)\n\tcase devicerequest.FieldScopes:\n\t\treturn m.OldScopes(ctx)\n\tcase devicerequest.FieldExpiry:\n\t\treturn m.OldExpiry(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown DeviceRequest field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *DeviceRequestMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase devicerequest.FieldUserCode:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetUserCode(v)\n\t\treturn nil\n\tcase devicerequest.FieldDeviceCode:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetDeviceCode(v)\n\t\treturn nil\n\tcase devicerequest.FieldClientID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClientID(v)\n\t\treturn nil\n\tcase devicerequest.FieldClientSecret:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClientSecret(v)\n\t\treturn nil\n\tcase devicerequest.FieldScopes:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetScopes(v)\n\t\treturn nil\n\tcase devicerequest.FieldExpiry:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetExpiry(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown DeviceRequest field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *DeviceRequestMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *DeviceRequestMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *DeviceRequestMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown DeviceRequest numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *DeviceRequestMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(devicerequest.FieldScopes) {\n\t\tfields = append(fields, devicerequest.FieldScopes)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *DeviceRequestMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *DeviceRequestMutation) ClearField(name string) error {\n\tswitch name {\n\tcase devicerequest.FieldScopes:\n\t\tm.ClearScopes()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown DeviceRequest nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *DeviceRequestMutation) ResetField(name string) error {\n\tswitch name {\n\tcase devicerequest.FieldUserCode:\n\t\tm.ResetUserCode()\n\t\treturn nil\n\tcase devicerequest.FieldDeviceCode:\n\t\tm.ResetDeviceCode()\n\t\treturn nil\n\tcase devicerequest.FieldClientID:\n\t\tm.ResetClientID()\n\t\treturn nil\n\tcase devicerequest.FieldClientSecret:\n\t\tm.ResetClientSecret()\n\t\treturn nil\n\tcase devicerequest.FieldScopes:\n\t\tm.ResetScopes()\n\t\treturn nil\n\tcase devicerequest.FieldExpiry:\n\t\tm.ResetExpiry()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown DeviceRequest field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *DeviceRequestMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *DeviceRequestMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *DeviceRequestMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *DeviceRequestMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *DeviceRequestMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *DeviceRequestMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *DeviceRequestMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown DeviceRequest unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *DeviceRequestMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown DeviceRequest edge %s\", name)\n}\n\n// DeviceTokenMutation represents an operation that mutates the DeviceToken nodes in the graph.\ntype DeviceTokenMutation struct {\n\tconfig\n\top                    Op\n\ttyp                   string\n\tid                    *int\n\tdevice_code           *string\n\tstatus                *string\n\ttoken                 *[]byte\n\texpiry                *time.Time\n\tlast_request          *time.Time\n\tpoll_interval         *int\n\taddpoll_interval      *int\n\tcode_challenge        *string\n\tcode_challenge_method *string\n\tclearedFields         map[string]struct{}\n\tdone                  bool\n\toldValue              func(context.Context) (*DeviceToken, error)\n\tpredicates            []predicate.DeviceToken\n}\n\nvar _ ent.Mutation = (*DeviceTokenMutation)(nil)\n\n// devicetokenOption allows management of the mutation configuration using functional options.\ntype devicetokenOption func(*DeviceTokenMutation)\n\n// newDeviceTokenMutation creates new mutation for the DeviceToken entity.\nfunc newDeviceTokenMutation(c config, op Op, opts ...devicetokenOption) *DeviceTokenMutation {\n\tm := &DeviceTokenMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeDeviceToken,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withDeviceTokenID sets the ID field of the mutation.\nfunc withDeviceTokenID(id int) devicetokenOption {\n\treturn func(m *DeviceTokenMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *DeviceToken\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*DeviceToken, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().DeviceToken.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withDeviceToken sets the old DeviceToken of the mutation.\nfunc withDeviceToken(node *DeviceToken) devicetokenOption {\n\treturn func(m *DeviceTokenMutation) {\n\t\tm.oldValue = func(context.Context) (*DeviceToken, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m DeviceTokenMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m DeviceTokenMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *DeviceTokenMutation) ID() (id int, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *DeviceTokenMutation) IDs(ctx context.Context) ([]int, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []int{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().DeviceToken.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetDeviceCode sets the \"device_code\" field.\nfunc (m *DeviceTokenMutation) SetDeviceCode(s string) {\n\tm.device_code = &s\n}\n\n// DeviceCode returns the value of the \"device_code\" field in the mutation.\nfunc (m *DeviceTokenMutation) DeviceCode() (r string, exists bool) {\n\tv := m.device_code\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldDeviceCode returns the old \"device_code\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldDeviceCode(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldDeviceCode is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldDeviceCode requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldDeviceCode: %w\", err)\n\t}\n\treturn oldValue.DeviceCode, nil\n}\n\n// ResetDeviceCode resets all changes to the \"device_code\" field.\nfunc (m *DeviceTokenMutation) ResetDeviceCode() {\n\tm.device_code = nil\n}\n\n// SetStatus sets the \"status\" field.\nfunc (m *DeviceTokenMutation) SetStatus(s string) {\n\tm.status = &s\n}\n\n// Status returns the value of the \"status\" field in the mutation.\nfunc (m *DeviceTokenMutation) Status() (r string, exists bool) {\n\tv := m.status\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldStatus returns the old \"status\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldStatus(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldStatus is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldStatus requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldStatus: %w\", err)\n\t}\n\treturn oldValue.Status, nil\n}\n\n// ResetStatus resets all changes to the \"status\" field.\nfunc (m *DeviceTokenMutation) ResetStatus() {\n\tm.status = nil\n}\n\n// SetToken sets the \"token\" field.\nfunc (m *DeviceTokenMutation) SetToken(b []byte) {\n\tm.token = &b\n}\n\n// Token returns the value of the \"token\" field in the mutation.\nfunc (m *DeviceTokenMutation) Token() (r []byte, exists bool) {\n\tv := m.token\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldToken returns the old \"token\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldToken(ctx context.Context) (v *[]byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldToken is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldToken requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldToken: %w\", err)\n\t}\n\treturn oldValue.Token, nil\n}\n\n// ClearToken clears the value of the \"token\" field.\nfunc (m *DeviceTokenMutation) ClearToken() {\n\tm.token = nil\n\tm.clearedFields[devicetoken.FieldToken] = struct{}{}\n}\n\n// TokenCleared returns if the \"token\" field was cleared in this mutation.\nfunc (m *DeviceTokenMutation) TokenCleared() bool {\n\t_, ok := m.clearedFields[devicetoken.FieldToken]\n\treturn ok\n}\n\n// ResetToken resets all changes to the \"token\" field.\nfunc (m *DeviceTokenMutation) ResetToken() {\n\tm.token = nil\n\tdelete(m.clearedFields, devicetoken.FieldToken)\n}\n\n// SetExpiry sets the \"expiry\" field.\nfunc (m *DeviceTokenMutation) SetExpiry(t time.Time) {\n\tm.expiry = &t\n}\n\n// Expiry returns the value of the \"expiry\" field in the mutation.\nfunc (m *DeviceTokenMutation) Expiry() (r time.Time, exists bool) {\n\tv := m.expiry\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldExpiry returns the old \"expiry\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldExpiry(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldExpiry is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldExpiry requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldExpiry: %w\", err)\n\t}\n\treturn oldValue.Expiry, nil\n}\n\n// ResetExpiry resets all changes to the \"expiry\" field.\nfunc (m *DeviceTokenMutation) ResetExpiry() {\n\tm.expiry = nil\n}\n\n// SetLastRequest sets the \"last_request\" field.\nfunc (m *DeviceTokenMutation) SetLastRequest(t time.Time) {\n\tm.last_request = &t\n}\n\n// LastRequest returns the value of the \"last_request\" field in the mutation.\nfunc (m *DeviceTokenMutation) LastRequest() (r time.Time, exists bool) {\n\tv := m.last_request\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldLastRequest returns the old \"last_request\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldLastRequest(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldLastRequest is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldLastRequest requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldLastRequest: %w\", err)\n\t}\n\treturn oldValue.LastRequest, nil\n}\n\n// ResetLastRequest resets all changes to the \"last_request\" field.\nfunc (m *DeviceTokenMutation) ResetLastRequest() {\n\tm.last_request = nil\n}\n\n// SetPollInterval sets the \"poll_interval\" field.\nfunc (m *DeviceTokenMutation) SetPollInterval(i int) {\n\tm.poll_interval = &i\n\tm.addpoll_interval = nil\n}\n\n// PollInterval returns the value of the \"poll_interval\" field in the mutation.\nfunc (m *DeviceTokenMutation) PollInterval() (r int, exists bool) {\n\tv := m.poll_interval\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldPollInterval returns the old \"poll_interval\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldPollInterval(ctx context.Context) (v int, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldPollInterval is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldPollInterval requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldPollInterval: %w\", err)\n\t}\n\treturn oldValue.PollInterval, nil\n}\n\n// AddPollInterval adds i to the \"poll_interval\" field.\nfunc (m *DeviceTokenMutation) AddPollInterval(i int) {\n\tif m.addpoll_interval != nil {\n\t\t*m.addpoll_interval += i\n\t} else {\n\t\tm.addpoll_interval = &i\n\t}\n}\n\n// AddedPollInterval returns the value that was added to the \"poll_interval\" field in this mutation.\nfunc (m *DeviceTokenMutation) AddedPollInterval() (r int, exists bool) {\n\tv := m.addpoll_interval\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// ResetPollInterval resets all changes to the \"poll_interval\" field.\nfunc (m *DeviceTokenMutation) ResetPollInterval() {\n\tm.poll_interval = nil\n\tm.addpoll_interval = nil\n}\n\n// SetCodeChallenge sets the \"code_challenge\" field.\nfunc (m *DeviceTokenMutation) SetCodeChallenge(s string) {\n\tm.code_challenge = &s\n}\n\n// CodeChallenge returns the value of the \"code_challenge\" field in the mutation.\nfunc (m *DeviceTokenMutation) CodeChallenge() (r string, exists bool) {\n\tv := m.code_challenge\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCodeChallenge returns the old \"code_challenge\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldCodeChallenge(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCodeChallenge is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCodeChallenge requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCodeChallenge: %w\", err)\n\t}\n\treturn oldValue.CodeChallenge, nil\n}\n\n// ResetCodeChallenge resets all changes to the \"code_challenge\" field.\nfunc (m *DeviceTokenMutation) ResetCodeChallenge() {\n\tm.code_challenge = nil\n}\n\n// SetCodeChallengeMethod sets the \"code_challenge_method\" field.\nfunc (m *DeviceTokenMutation) SetCodeChallengeMethod(s string) {\n\tm.code_challenge_method = &s\n}\n\n// CodeChallengeMethod returns the value of the \"code_challenge_method\" field in the mutation.\nfunc (m *DeviceTokenMutation) CodeChallengeMethod() (r string, exists bool) {\n\tv := m.code_challenge_method\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCodeChallengeMethod returns the old \"code_challenge_method\" field's value of the DeviceToken entity.\n// If the DeviceToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *DeviceTokenMutation) OldCodeChallengeMethod(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCodeChallengeMethod is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCodeChallengeMethod requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCodeChallengeMethod: %w\", err)\n\t}\n\treturn oldValue.CodeChallengeMethod, nil\n}\n\n// ResetCodeChallengeMethod resets all changes to the \"code_challenge_method\" field.\nfunc (m *DeviceTokenMutation) ResetCodeChallengeMethod() {\n\tm.code_challenge_method = nil\n}\n\n// Where appends a list predicates to the DeviceTokenMutation builder.\nfunc (m *DeviceTokenMutation) Where(ps ...predicate.DeviceToken) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the DeviceTokenMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *DeviceTokenMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.DeviceToken, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *DeviceTokenMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *DeviceTokenMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (DeviceToken).\nfunc (m *DeviceTokenMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *DeviceTokenMutation) Fields() []string {\n\tfields := make([]string, 0, 8)\n\tif m.device_code != nil {\n\t\tfields = append(fields, devicetoken.FieldDeviceCode)\n\t}\n\tif m.status != nil {\n\t\tfields = append(fields, devicetoken.FieldStatus)\n\t}\n\tif m.token != nil {\n\t\tfields = append(fields, devicetoken.FieldToken)\n\t}\n\tif m.expiry != nil {\n\t\tfields = append(fields, devicetoken.FieldExpiry)\n\t}\n\tif m.last_request != nil {\n\t\tfields = append(fields, devicetoken.FieldLastRequest)\n\t}\n\tif m.poll_interval != nil {\n\t\tfields = append(fields, devicetoken.FieldPollInterval)\n\t}\n\tif m.code_challenge != nil {\n\t\tfields = append(fields, devicetoken.FieldCodeChallenge)\n\t}\n\tif m.code_challenge_method != nil {\n\t\tfields = append(fields, devicetoken.FieldCodeChallengeMethod)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *DeviceTokenMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase devicetoken.FieldDeviceCode:\n\t\treturn m.DeviceCode()\n\tcase devicetoken.FieldStatus:\n\t\treturn m.Status()\n\tcase devicetoken.FieldToken:\n\t\treturn m.Token()\n\tcase devicetoken.FieldExpiry:\n\t\treturn m.Expiry()\n\tcase devicetoken.FieldLastRequest:\n\t\treturn m.LastRequest()\n\tcase devicetoken.FieldPollInterval:\n\t\treturn m.PollInterval()\n\tcase devicetoken.FieldCodeChallenge:\n\t\treturn m.CodeChallenge()\n\tcase devicetoken.FieldCodeChallengeMethod:\n\t\treturn m.CodeChallengeMethod()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *DeviceTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase devicetoken.FieldDeviceCode:\n\t\treturn m.OldDeviceCode(ctx)\n\tcase devicetoken.FieldStatus:\n\t\treturn m.OldStatus(ctx)\n\tcase devicetoken.FieldToken:\n\t\treturn m.OldToken(ctx)\n\tcase devicetoken.FieldExpiry:\n\t\treturn m.OldExpiry(ctx)\n\tcase devicetoken.FieldLastRequest:\n\t\treturn m.OldLastRequest(ctx)\n\tcase devicetoken.FieldPollInterval:\n\t\treturn m.OldPollInterval(ctx)\n\tcase devicetoken.FieldCodeChallenge:\n\t\treturn m.OldCodeChallenge(ctx)\n\tcase devicetoken.FieldCodeChallengeMethod:\n\t\treturn m.OldCodeChallengeMethod(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown DeviceToken field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *DeviceTokenMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase devicetoken.FieldDeviceCode:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetDeviceCode(v)\n\t\treturn nil\n\tcase devicetoken.FieldStatus:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetStatus(v)\n\t\treturn nil\n\tcase devicetoken.FieldToken:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetToken(v)\n\t\treturn nil\n\tcase devicetoken.FieldExpiry:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetExpiry(v)\n\t\treturn nil\n\tcase devicetoken.FieldLastRequest:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetLastRequest(v)\n\t\treturn nil\n\tcase devicetoken.FieldPollInterval:\n\t\tv, ok := value.(int)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetPollInterval(v)\n\t\treturn nil\n\tcase devicetoken.FieldCodeChallenge:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCodeChallenge(v)\n\t\treturn nil\n\tcase devicetoken.FieldCodeChallengeMethod:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCodeChallengeMethod(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown DeviceToken field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *DeviceTokenMutation) AddedFields() []string {\n\tvar fields []string\n\tif m.addpoll_interval != nil {\n\t\tfields = append(fields, devicetoken.FieldPollInterval)\n\t}\n\treturn fields\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *DeviceTokenMutation) AddedField(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase devicetoken.FieldPollInterval:\n\t\treturn m.AddedPollInterval()\n\t}\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *DeviceTokenMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\tcase devicetoken.FieldPollInterval:\n\t\tv, ok := value.(int)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.AddPollInterval(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown DeviceToken numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *DeviceTokenMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(devicetoken.FieldToken) {\n\t\tfields = append(fields, devicetoken.FieldToken)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *DeviceTokenMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *DeviceTokenMutation) ClearField(name string) error {\n\tswitch name {\n\tcase devicetoken.FieldToken:\n\t\tm.ClearToken()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown DeviceToken nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *DeviceTokenMutation) ResetField(name string) error {\n\tswitch name {\n\tcase devicetoken.FieldDeviceCode:\n\t\tm.ResetDeviceCode()\n\t\treturn nil\n\tcase devicetoken.FieldStatus:\n\t\tm.ResetStatus()\n\t\treturn nil\n\tcase devicetoken.FieldToken:\n\t\tm.ResetToken()\n\t\treturn nil\n\tcase devicetoken.FieldExpiry:\n\t\tm.ResetExpiry()\n\t\treturn nil\n\tcase devicetoken.FieldLastRequest:\n\t\tm.ResetLastRequest()\n\t\treturn nil\n\tcase devicetoken.FieldPollInterval:\n\t\tm.ResetPollInterval()\n\t\treturn nil\n\tcase devicetoken.FieldCodeChallenge:\n\t\tm.ResetCodeChallenge()\n\t\treturn nil\n\tcase devicetoken.FieldCodeChallengeMethod:\n\t\tm.ResetCodeChallengeMethod()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown DeviceToken field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *DeviceTokenMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *DeviceTokenMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *DeviceTokenMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *DeviceTokenMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *DeviceTokenMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *DeviceTokenMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *DeviceTokenMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown DeviceToken unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *DeviceTokenMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown DeviceToken edge %s\", name)\n}\n\n// KeysMutation represents an operation that mutates the Keys nodes in the graph.\ntype KeysMutation struct {\n\tconfig\n\top                      Op\n\ttyp                     string\n\tid                      *string\n\tverification_keys       *[]storage.VerificationKey\n\tappendverification_keys []storage.VerificationKey\n\tsigning_key             *jose.JSONWebKey\n\tsigning_key_pub         *jose.JSONWebKey\n\tnext_rotation           *time.Time\n\tclearedFields           map[string]struct{}\n\tdone                    bool\n\toldValue                func(context.Context) (*Keys, error)\n\tpredicates              []predicate.Keys\n}\n\nvar _ ent.Mutation = (*KeysMutation)(nil)\n\n// keysOption allows management of the mutation configuration using functional options.\ntype keysOption func(*KeysMutation)\n\n// newKeysMutation creates new mutation for the Keys entity.\nfunc newKeysMutation(c config, op Op, opts ...keysOption) *KeysMutation {\n\tm := &KeysMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeKeys,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withKeysID sets the ID field of the mutation.\nfunc withKeysID(id string) keysOption {\n\treturn func(m *KeysMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *Keys\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*Keys, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().Keys.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withKeys sets the old Keys of the mutation.\nfunc withKeys(node *Keys) keysOption {\n\treturn func(m *KeysMutation) {\n\t\tm.oldValue = func(context.Context) (*Keys, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m KeysMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m KeysMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of Keys entities.\nfunc (m *KeysMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *KeysMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *KeysMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().Keys.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetVerificationKeys sets the \"verification_keys\" field.\nfunc (m *KeysMutation) SetVerificationKeys(sk []storage.VerificationKey) {\n\tm.verification_keys = &sk\n\tm.appendverification_keys = nil\n}\n\n// VerificationKeys returns the value of the \"verification_keys\" field in the mutation.\nfunc (m *KeysMutation) VerificationKeys() (r []storage.VerificationKey, exists bool) {\n\tv := m.verification_keys\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldVerificationKeys returns the old \"verification_keys\" field's value of the Keys entity.\n// If the Keys object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *KeysMutation) OldVerificationKeys(ctx context.Context) (v []storage.VerificationKey, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldVerificationKeys is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldVerificationKeys requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldVerificationKeys: %w\", err)\n\t}\n\treturn oldValue.VerificationKeys, nil\n}\n\n// AppendVerificationKeys adds sk to the \"verification_keys\" field.\nfunc (m *KeysMutation) AppendVerificationKeys(sk []storage.VerificationKey) {\n\tm.appendverification_keys = append(m.appendverification_keys, sk...)\n}\n\n// AppendedVerificationKeys returns the list of values that were appended to the \"verification_keys\" field in this mutation.\nfunc (m *KeysMutation) AppendedVerificationKeys() ([]storage.VerificationKey, bool) {\n\tif len(m.appendverification_keys) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendverification_keys, true\n}\n\n// ResetVerificationKeys resets all changes to the \"verification_keys\" field.\nfunc (m *KeysMutation) ResetVerificationKeys() {\n\tm.verification_keys = nil\n\tm.appendverification_keys = nil\n}\n\n// SetSigningKey sets the \"signing_key\" field.\nfunc (m *KeysMutation) SetSigningKey(jwk jose.JSONWebKey) {\n\tm.signing_key = &jwk\n}\n\n// SigningKey returns the value of the \"signing_key\" field in the mutation.\nfunc (m *KeysMutation) SigningKey() (r jose.JSONWebKey, exists bool) {\n\tv := m.signing_key\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldSigningKey returns the old \"signing_key\" field's value of the Keys entity.\n// If the Keys object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *KeysMutation) OldSigningKey(ctx context.Context) (v jose.JSONWebKey, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldSigningKey is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldSigningKey requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldSigningKey: %w\", err)\n\t}\n\treturn oldValue.SigningKey, nil\n}\n\n// ResetSigningKey resets all changes to the \"signing_key\" field.\nfunc (m *KeysMutation) ResetSigningKey() {\n\tm.signing_key = nil\n}\n\n// SetSigningKeyPub sets the \"signing_key_pub\" field.\nfunc (m *KeysMutation) SetSigningKeyPub(jwk jose.JSONWebKey) {\n\tm.signing_key_pub = &jwk\n}\n\n// SigningKeyPub returns the value of the \"signing_key_pub\" field in the mutation.\nfunc (m *KeysMutation) SigningKeyPub() (r jose.JSONWebKey, exists bool) {\n\tv := m.signing_key_pub\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldSigningKeyPub returns the old \"signing_key_pub\" field's value of the Keys entity.\n// If the Keys object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *KeysMutation) OldSigningKeyPub(ctx context.Context) (v jose.JSONWebKey, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldSigningKeyPub is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldSigningKeyPub requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldSigningKeyPub: %w\", err)\n\t}\n\treturn oldValue.SigningKeyPub, nil\n}\n\n// ResetSigningKeyPub resets all changes to the \"signing_key_pub\" field.\nfunc (m *KeysMutation) ResetSigningKeyPub() {\n\tm.signing_key_pub = nil\n}\n\n// SetNextRotation sets the \"next_rotation\" field.\nfunc (m *KeysMutation) SetNextRotation(t time.Time) {\n\tm.next_rotation = &t\n}\n\n// NextRotation returns the value of the \"next_rotation\" field in the mutation.\nfunc (m *KeysMutation) NextRotation() (r time.Time, exists bool) {\n\tv := m.next_rotation\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldNextRotation returns the old \"next_rotation\" field's value of the Keys entity.\n// If the Keys object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *KeysMutation) OldNextRotation(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldNextRotation is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldNextRotation requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldNextRotation: %w\", err)\n\t}\n\treturn oldValue.NextRotation, nil\n}\n\n// ResetNextRotation resets all changes to the \"next_rotation\" field.\nfunc (m *KeysMutation) ResetNextRotation() {\n\tm.next_rotation = nil\n}\n\n// Where appends a list predicates to the KeysMutation builder.\nfunc (m *KeysMutation) Where(ps ...predicate.Keys) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the KeysMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *KeysMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.Keys, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *KeysMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *KeysMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (Keys).\nfunc (m *KeysMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *KeysMutation) Fields() []string {\n\tfields := make([]string, 0, 4)\n\tif m.verification_keys != nil {\n\t\tfields = append(fields, keys.FieldVerificationKeys)\n\t}\n\tif m.signing_key != nil {\n\t\tfields = append(fields, keys.FieldSigningKey)\n\t}\n\tif m.signing_key_pub != nil {\n\t\tfields = append(fields, keys.FieldSigningKeyPub)\n\t}\n\tif m.next_rotation != nil {\n\t\tfields = append(fields, keys.FieldNextRotation)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *KeysMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase keys.FieldVerificationKeys:\n\t\treturn m.VerificationKeys()\n\tcase keys.FieldSigningKey:\n\t\treturn m.SigningKey()\n\tcase keys.FieldSigningKeyPub:\n\t\treturn m.SigningKeyPub()\n\tcase keys.FieldNextRotation:\n\t\treturn m.NextRotation()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *KeysMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase keys.FieldVerificationKeys:\n\t\treturn m.OldVerificationKeys(ctx)\n\tcase keys.FieldSigningKey:\n\t\treturn m.OldSigningKey(ctx)\n\tcase keys.FieldSigningKeyPub:\n\t\treturn m.OldSigningKeyPub(ctx)\n\tcase keys.FieldNextRotation:\n\t\treturn m.OldNextRotation(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown Keys field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *KeysMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase keys.FieldVerificationKeys:\n\t\tv, ok := value.([]storage.VerificationKey)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetVerificationKeys(v)\n\t\treturn nil\n\tcase keys.FieldSigningKey:\n\t\tv, ok := value.(jose.JSONWebKey)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetSigningKey(v)\n\t\treturn nil\n\tcase keys.FieldSigningKeyPub:\n\t\tv, ok := value.(jose.JSONWebKey)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetSigningKeyPub(v)\n\t\treturn nil\n\tcase keys.FieldNextRotation:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetNextRotation(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Keys field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *KeysMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *KeysMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *KeysMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown Keys numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *KeysMutation) ClearedFields() []string {\n\treturn nil\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *KeysMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *KeysMutation) ClearField(name string) error {\n\treturn fmt.Errorf(\"unknown Keys nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *KeysMutation) ResetField(name string) error {\n\tswitch name {\n\tcase keys.FieldVerificationKeys:\n\t\tm.ResetVerificationKeys()\n\t\treturn nil\n\tcase keys.FieldSigningKey:\n\t\tm.ResetSigningKey()\n\t\treturn nil\n\tcase keys.FieldSigningKeyPub:\n\t\tm.ResetSigningKeyPub()\n\t\treturn nil\n\tcase keys.FieldNextRotation:\n\t\tm.ResetNextRotation()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Keys field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *KeysMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *KeysMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *KeysMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *KeysMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *KeysMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *KeysMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *KeysMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown Keys unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *KeysMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown Keys edge %s\", name)\n}\n\n// OAuth2ClientMutation represents an operation that mutates the OAuth2Client nodes in the graph.\ntype OAuth2ClientMutation struct {\n\tconfig\n\top                       Op\n\ttyp                      string\n\tid                       *string\n\tsecret                   *string\n\tredirect_uris            *[]string\n\tappendredirect_uris      []string\n\ttrusted_peers            *[]string\n\tappendtrusted_peers      []string\n\tpublic                   *bool\n\tname                     *string\n\tlogo_url                 *string\n\tallowed_connectors       *[]string\n\tappendallowed_connectors []string\n\tmfa_chain                *[]string\n\tappendmfa_chain          []string\n\tclearedFields            map[string]struct{}\n\tdone                     bool\n\toldValue                 func(context.Context) (*OAuth2Client, error)\n\tpredicates               []predicate.OAuth2Client\n}\n\nvar _ ent.Mutation = (*OAuth2ClientMutation)(nil)\n\n// oauth2clientOption allows management of the mutation configuration using functional options.\ntype oauth2clientOption func(*OAuth2ClientMutation)\n\n// newOAuth2ClientMutation creates new mutation for the OAuth2Client entity.\nfunc newOAuth2ClientMutation(c config, op Op, opts ...oauth2clientOption) *OAuth2ClientMutation {\n\tm := &OAuth2ClientMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeOAuth2Client,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withOAuth2ClientID sets the ID field of the mutation.\nfunc withOAuth2ClientID(id string) oauth2clientOption {\n\treturn func(m *OAuth2ClientMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *OAuth2Client\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*OAuth2Client, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().OAuth2Client.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withOAuth2Client sets the old OAuth2Client of the mutation.\nfunc withOAuth2Client(node *OAuth2Client) oauth2clientOption {\n\treturn func(m *OAuth2ClientMutation) {\n\t\tm.oldValue = func(context.Context) (*OAuth2Client, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m OAuth2ClientMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m OAuth2ClientMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of OAuth2Client entities.\nfunc (m *OAuth2ClientMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *OAuth2ClientMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *OAuth2ClientMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().OAuth2Client.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetSecret sets the \"secret\" field.\nfunc (m *OAuth2ClientMutation) SetSecret(s string) {\n\tm.secret = &s\n}\n\n// Secret returns the value of the \"secret\" field in the mutation.\nfunc (m *OAuth2ClientMutation) Secret() (r string, exists bool) {\n\tv := m.secret\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldSecret returns the old \"secret\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldSecret(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldSecret is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldSecret requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldSecret: %w\", err)\n\t}\n\treturn oldValue.Secret, nil\n}\n\n// ResetSecret resets all changes to the \"secret\" field.\nfunc (m *OAuth2ClientMutation) ResetSecret() {\n\tm.secret = nil\n}\n\n// SetRedirectUris sets the \"redirect_uris\" field.\nfunc (m *OAuth2ClientMutation) SetRedirectUris(s []string) {\n\tm.redirect_uris = &s\n\tm.appendredirect_uris = nil\n}\n\n// RedirectUris returns the value of the \"redirect_uris\" field in the mutation.\nfunc (m *OAuth2ClientMutation) RedirectUris() (r []string, exists bool) {\n\tv := m.redirect_uris\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldRedirectUris returns the old \"redirect_uris\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldRedirectUris(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldRedirectUris is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldRedirectUris requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldRedirectUris: %w\", err)\n\t}\n\treturn oldValue.RedirectUris, nil\n}\n\n// AppendRedirectUris adds s to the \"redirect_uris\" field.\nfunc (m *OAuth2ClientMutation) AppendRedirectUris(s []string) {\n\tm.appendredirect_uris = append(m.appendredirect_uris, s...)\n}\n\n// AppendedRedirectUris returns the list of values that were appended to the \"redirect_uris\" field in this mutation.\nfunc (m *OAuth2ClientMutation) AppendedRedirectUris() ([]string, bool) {\n\tif len(m.appendredirect_uris) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendredirect_uris, true\n}\n\n// ClearRedirectUris clears the value of the \"redirect_uris\" field.\nfunc (m *OAuth2ClientMutation) ClearRedirectUris() {\n\tm.redirect_uris = nil\n\tm.appendredirect_uris = nil\n\tm.clearedFields[oauth2client.FieldRedirectUris] = struct{}{}\n}\n\n// RedirectUrisCleared returns if the \"redirect_uris\" field was cleared in this mutation.\nfunc (m *OAuth2ClientMutation) RedirectUrisCleared() bool {\n\t_, ok := m.clearedFields[oauth2client.FieldRedirectUris]\n\treturn ok\n}\n\n// ResetRedirectUris resets all changes to the \"redirect_uris\" field.\nfunc (m *OAuth2ClientMutation) ResetRedirectUris() {\n\tm.redirect_uris = nil\n\tm.appendredirect_uris = nil\n\tdelete(m.clearedFields, oauth2client.FieldRedirectUris)\n}\n\n// SetTrustedPeers sets the \"trusted_peers\" field.\nfunc (m *OAuth2ClientMutation) SetTrustedPeers(s []string) {\n\tm.trusted_peers = &s\n\tm.appendtrusted_peers = nil\n}\n\n// TrustedPeers returns the value of the \"trusted_peers\" field in the mutation.\nfunc (m *OAuth2ClientMutation) TrustedPeers() (r []string, exists bool) {\n\tv := m.trusted_peers\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldTrustedPeers returns the old \"trusted_peers\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldTrustedPeers(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldTrustedPeers is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldTrustedPeers requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldTrustedPeers: %w\", err)\n\t}\n\treturn oldValue.TrustedPeers, nil\n}\n\n// AppendTrustedPeers adds s to the \"trusted_peers\" field.\nfunc (m *OAuth2ClientMutation) AppendTrustedPeers(s []string) {\n\tm.appendtrusted_peers = append(m.appendtrusted_peers, s...)\n}\n\n// AppendedTrustedPeers returns the list of values that were appended to the \"trusted_peers\" field in this mutation.\nfunc (m *OAuth2ClientMutation) AppendedTrustedPeers() ([]string, bool) {\n\tif len(m.appendtrusted_peers) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendtrusted_peers, true\n}\n\n// ClearTrustedPeers clears the value of the \"trusted_peers\" field.\nfunc (m *OAuth2ClientMutation) ClearTrustedPeers() {\n\tm.trusted_peers = nil\n\tm.appendtrusted_peers = nil\n\tm.clearedFields[oauth2client.FieldTrustedPeers] = struct{}{}\n}\n\n// TrustedPeersCleared returns if the \"trusted_peers\" field was cleared in this mutation.\nfunc (m *OAuth2ClientMutation) TrustedPeersCleared() bool {\n\t_, ok := m.clearedFields[oauth2client.FieldTrustedPeers]\n\treturn ok\n}\n\n// ResetTrustedPeers resets all changes to the \"trusted_peers\" field.\nfunc (m *OAuth2ClientMutation) ResetTrustedPeers() {\n\tm.trusted_peers = nil\n\tm.appendtrusted_peers = nil\n\tdelete(m.clearedFields, oauth2client.FieldTrustedPeers)\n}\n\n// SetPublic sets the \"public\" field.\nfunc (m *OAuth2ClientMutation) SetPublic(b bool) {\n\tm.public = &b\n}\n\n// Public returns the value of the \"public\" field in the mutation.\nfunc (m *OAuth2ClientMutation) Public() (r bool, exists bool) {\n\tv := m.public\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldPublic returns the old \"public\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldPublic(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldPublic is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldPublic requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldPublic: %w\", err)\n\t}\n\treturn oldValue.Public, nil\n}\n\n// ResetPublic resets all changes to the \"public\" field.\nfunc (m *OAuth2ClientMutation) ResetPublic() {\n\tm.public = nil\n}\n\n// SetName sets the \"name\" field.\nfunc (m *OAuth2ClientMutation) SetName(s string) {\n\tm.name = &s\n}\n\n// Name returns the value of the \"name\" field in the mutation.\nfunc (m *OAuth2ClientMutation) Name() (r string, exists bool) {\n\tv := m.name\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldName returns the old \"name\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldName(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldName is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldName requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldName: %w\", err)\n\t}\n\treturn oldValue.Name, nil\n}\n\n// ResetName resets all changes to the \"name\" field.\nfunc (m *OAuth2ClientMutation) ResetName() {\n\tm.name = nil\n}\n\n// SetLogoURL sets the \"logo_url\" field.\nfunc (m *OAuth2ClientMutation) SetLogoURL(s string) {\n\tm.logo_url = &s\n}\n\n// LogoURL returns the value of the \"logo_url\" field in the mutation.\nfunc (m *OAuth2ClientMutation) LogoURL() (r string, exists bool) {\n\tv := m.logo_url\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldLogoURL returns the old \"logo_url\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldLogoURL(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldLogoURL is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldLogoURL requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldLogoURL: %w\", err)\n\t}\n\treturn oldValue.LogoURL, nil\n}\n\n// ResetLogoURL resets all changes to the \"logo_url\" field.\nfunc (m *OAuth2ClientMutation) ResetLogoURL() {\n\tm.logo_url = nil\n}\n\n// SetAllowedConnectors sets the \"allowed_connectors\" field.\nfunc (m *OAuth2ClientMutation) SetAllowedConnectors(s []string) {\n\tm.allowed_connectors = &s\n\tm.appendallowed_connectors = nil\n}\n\n// AllowedConnectors returns the value of the \"allowed_connectors\" field in the mutation.\nfunc (m *OAuth2ClientMutation) AllowedConnectors() (r []string, exists bool) {\n\tv := m.allowed_connectors\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldAllowedConnectors returns the old \"allowed_connectors\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldAllowedConnectors(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldAllowedConnectors is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldAllowedConnectors requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldAllowedConnectors: %w\", err)\n\t}\n\treturn oldValue.AllowedConnectors, nil\n}\n\n// AppendAllowedConnectors adds s to the \"allowed_connectors\" field.\nfunc (m *OAuth2ClientMutation) AppendAllowedConnectors(s []string) {\n\tm.appendallowed_connectors = append(m.appendallowed_connectors, s...)\n}\n\n// AppendedAllowedConnectors returns the list of values that were appended to the \"allowed_connectors\" field in this mutation.\nfunc (m *OAuth2ClientMutation) AppendedAllowedConnectors() ([]string, bool) {\n\tif len(m.appendallowed_connectors) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendallowed_connectors, true\n}\n\n// ClearAllowedConnectors clears the value of the \"allowed_connectors\" field.\nfunc (m *OAuth2ClientMutation) ClearAllowedConnectors() {\n\tm.allowed_connectors = nil\n\tm.appendallowed_connectors = nil\n\tm.clearedFields[oauth2client.FieldAllowedConnectors] = struct{}{}\n}\n\n// AllowedConnectorsCleared returns if the \"allowed_connectors\" field was cleared in this mutation.\nfunc (m *OAuth2ClientMutation) AllowedConnectorsCleared() bool {\n\t_, ok := m.clearedFields[oauth2client.FieldAllowedConnectors]\n\treturn ok\n}\n\n// ResetAllowedConnectors resets all changes to the \"allowed_connectors\" field.\nfunc (m *OAuth2ClientMutation) ResetAllowedConnectors() {\n\tm.allowed_connectors = nil\n\tm.appendallowed_connectors = nil\n\tdelete(m.clearedFields, oauth2client.FieldAllowedConnectors)\n}\n\n// SetMfaChain sets the \"mfa_chain\" field.\nfunc (m *OAuth2ClientMutation) SetMfaChain(s []string) {\n\tm.mfa_chain = &s\n\tm.appendmfa_chain = nil\n}\n\n// MfaChain returns the value of the \"mfa_chain\" field in the mutation.\nfunc (m *OAuth2ClientMutation) MfaChain() (r []string, exists bool) {\n\tv := m.mfa_chain\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldMfaChain returns the old \"mfa_chain\" field's value of the OAuth2Client entity.\n// If the OAuth2Client object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OAuth2ClientMutation) OldMfaChain(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldMfaChain is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldMfaChain requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldMfaChain: %w\", err)\n\t}\n\treturn oldValue.MfaChain, nil\n}\n\n// AppendMfaChain adds s to the \"mfa_chain\" field.\nfunc (m *OAuth2ClientMutation) AppendMfaChain(s []string) {\n\tm.appendmfa_chain = append(m.appendmfa_chain, s...)\n}\n\n// AppendedMfaChain returns the list of values that were appended to the \"mfa_chain\" field in this mutation.\nfunc (m *OAuth2ClientMutation) AppendedMfaChain() ([]string, bool) {\n\tif len(m.appendmfa_chain) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendmfa_chain, true\n}\n\n// ClearMfaChain clears the value of the \"mfa_chain\" field.\nfunc (m *OAuth2ClientMutation) ClearMfaChain() {\n\tm.mfa_chain = nil\n\tm.appendmfa_chain = nil\n\tm.clearedFields[oauth2client.FieldMfaChain] = struct{}{}\n}\n\n// MfaChainCleared returns if the \"mfa_chain\" field was cleared in this mutation.\nfunc (m *OAuth2ClientMutation) MfaChainCleared() bool {\n\t_, ok := m.clearedFields[oauth2client.FieldMfaChain]\n\treturn ok\n}\n\n// ResetMfaChain resets all changes to the \"mfa_chain\" field.\nfunc (m *OAuth2ClientMutation) ResetMfaChain() {\n\tm.mfa_chain = nil\n\tm.appendmfa_chain = nil\n\tdelete(m.clearedFields, oauth2client.FieldMfaChain)\n}\n\n// Where appends a list predicates to the OAuth2ClientMutation builder.\nfunc (m *OAuth2ClientMutation) Where(ps ...predicate.OAuth2Client) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the OAuth2ClientMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *OAuth2ClientMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.OAuth2Client, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *OAuth2ClientMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *OAuth2ClientMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (OAuth2Client).\nfunc (m *OAuth2ClientMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *OAuth2ClientMutation) Fields() []string {\n\tfields := make([]string, 0, 8)\n\tif m.secret != nil {\n\t\tfields = append(fields, oauth2client.FieldSecret)\n\t}\n\tif m.redirect_uris != nil {\n\t\tfields = append(fields, oauth2client.FieldRedirectUris)\n\t}\n\tif m.trusted_peers != nil {\n\t\tfields = append(fields, oauth2client.FieldTrustedPeers)\n\t}\n\tif m.public != nil {\n\t\tfields = append(fields, oauth2client.FieldPublic)\n\t}\n\tif m.name != nil {\n\t\tfields = append(fields, oauth2client.FieldName)\n\t}\n\tif m.logo_url != nil {\n\t\tfields = append(fields, oauth2client.FieldLogoURL)\n\t}\n\tif m.allowed_connectors != nil {\n\t\tfields = append(fields, oauth2client.FieldAllowedConnectors)\n\t}\n\tif m.mfa_chain != nil {\n\t\tfields = append(fields, oauth2client.FieldMfaChain)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *OAuth2ClientMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase oauth2client.FieldSecret:\n\t\treturn m.Secret()\n\tcase oauth2client.FieldRedirectUris:\n\t\treturn m.RedirectUris()\n\tcase oauth2client.FieldTrustedPeers:\n\t\treturn m.TrustedPeers()\n\tcase oauth2client.FieldPublic:\n\t\treturn m.Public()\n\tcase oauth2client.FieldName:\n\t\treturn m.Name()\n\tcase oauth2client.FieldLogoURL:\n\t\treturn m.LogoURL()\n\tcase oauth2client.FieldAllowedConnectors:\n\t\treturn m.AllowedConnectors()\n\tcase oauth2client.FieldMfaChain:\n\t\treturn m.MfaChain()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *OAuth2ClientMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase oauth2client.FieldSecret:\n\t\treturn m.OldSecret(ctx)\n\tcase oauth2client.FieldRedirectUris:\n\t\treturn m.OldRedirectUris(ctx)\n\tcase oauth2client.FieldTrustedPeers:\n\t\treturn m.OldTrustedPeers(ctx)\n\tcase oauth2client.FieldPublic:\n\t\treturn m.OldPublic(ctx)\n\tcase oauth2client.FieldName:\n\t\treturn m.OldName(ctx)\n\tcase oauth2client.FieldLogoURL:\n\t\treturn m.OldLogoURL(ctx)\n\tcase oauth2client.FieldAllowedConnectors:\n\t\treturn m.OldAllowedConnectors(ctx)\n\tcase oauth2client.FieldMfaChain:\n\t\treturn m.OldMfaChain(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown OAuth2Client field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *OAuth2ClientMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase oauth2client.FieldSecret:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetSecret(v)\n\t\treturn nil\n\tcase oauth2client.FieldRedirectUris:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetRedirectUris(v)\n\t\treturn nil\n\tcase oauth2client.FieldTrustedPeers:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetTrustedPeers(v)\n\t\treturn nil\n\tcase oauth2client.FieldPublic:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetPublic(v)\n\t\treturn nil\n\tcase oauth2client.FieldName:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetName(v)\n\t\treturn nil\n\tcase oauth2client.FieldLogoURL:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetLogoURL(v)\n\t\treturn nil\n\tcase oauth2client.FieldAllowedConnectors:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetAllowedConnectors(v)\n\t\treturn nil\n\tcase oauth2client.FieldMfaChain:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetMfaChain(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown OAuth2Client field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *OAuth2ClientMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *OAuth2ClientMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *OAuth2ClientMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown OAuth2Client numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *OAuth2ClientMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(oauth2client.FieldRedirectUris) {\n\t\tfields = append(fields, oauth2client.FieldRedirectUris)\n\t}\n\tif m.FieldCleared(oauth2client.FieldTrustedPeers) {\n\t\tfields = append(fields, oauth2client.FieldTrustedPeers)\n\t}\n\tif m.FieldCleared(oauth2client.FieldAllowedConnectors) {\n\t\tfields = append(fields, oauth2client.FieldAllowedConnectors)\n\t}\n\tif m.FieldCleared(oauth2client.FieldMfaChain) {\n\t\tfields = append(fields, oauth2client.FieldMfaChain)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *OAuth2ClientMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *OAuth2ClientMutation) ClearField(name string) error {\n\tswitch name {\n\tcase oauth2client.FieldRedirectUris:\n\t\tm.ClearRedirectUris()\n\t\treturn nil\n\tcase oauth2client.FieldTrustedPeers:\n\t\tm.ClearTrustedPeers()\n\t\treturn nil\n\tcase oauth2client.FieldAllowedConnectors:\n\t\tm.ClearAllowedConnectors()\n\t\treturn nil\n\tcase oauth2client.FieldMfaChain:\n\t\tm.ClearMfaChain()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown OAuth2Client nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *OAuth2ClientMutation) ResetField(name string) error {\n\tswitch name {\n\tcase oauth2client.FieldSecret:\n\t\tm.ResetSecret()\n\t\treturn nil\n\tcase oauth2client.FieldRedirectUris:\n\t\tm.ResetRedirectUris()\n\t\treturn nil\n\tcase oauth2client.FieldTrustedPeers:\n\t\tm.ResetTrustedPeers()\n\t\treturn nil\n\tcase oauth2client.FieldPublic:\n\t\tm.ResetPublic()\n\t\treturn nil\n\tcase oauth2client.FieldName:\n\t\tm.ResetName()\n\t\treturn nil\n\tcase oauth2client.FieldLogoURL:\n\t\tm.ResetLogoURL()\n\t\treturn nil\n\tcase oauth2client.FieldAllowedConnectors:\n\t\tm.ResetAllowedConnectors()\n\t\treturn nil\n\tcase oauth2client.FieldMfaChain:\n\t\tm.ResetMfaChain()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown OAuth2Client field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *OAuth2ClientMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *OAuth2ClientMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *OAuth2ClientMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *OAuth2ClientMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *OAuth2ClientMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *OAuth2ClientMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *OAuth2ClientMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown OAuth2Client unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *OAuth2ClientMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown OAuth2Client edge %s\", name)\n}\n\n// OfflineSessionMutation represents an operation that mutates the OfflineSession nodes in the graph.\ntype OfflineSessionMutation struct {\n\tconfig\n\top             Op\n\ttyp            string\n\tid             *string\n\tuser_id        *string\n\tconn_id        *string\n\trefresh        *[]byte\n\tconnector_data *[]byte\n\tclearedFields  map[string]struct{}\n\tdone           bool\n\toldValue       func(context.Context) (*OfflineSession, error)\n\tpredicates     []predicate.OfflineSession\n}\n\nvar _ ent.Mutation = (*OfflineSessionMutation)(nil)\n\n// offlinesessionOption allows management of the mutation configuration using functional options.\ntype offlinesessionOption func(*OfflineSessionMutation)\n\n// newOfflineSessionMutation creates new mutation for the OfflineSession entity.\nfunc newOfflineSessionMutation(c config, op Op, opts ...offlinesessionOption) *OfflineSessionMutation {\n\tm := &OfflineSessionMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeOfflineSession,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withOfflineSessionID sets the ID field of the mutation.\nfunc withOfflineSessionID(id string) offlinesessionOption {\n\treturn func(m *OfflineSessionMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *OfflineSession\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*OfflineSession, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().OfflineSession.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withOfflineSession sets the old OfflineSession of the mutation.\nfunc withOfflineSession(node *OfflineSession) offlinesessionOption {\n\treturn func(m *OfflineSessionMutation) {\n\t\tm.oldValue = func(context.Context) (*OfflineSession, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m OfflineSessionMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m OfflineSessionMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of OfflineSession entities.\nfunc (m *OfflineSessionMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *OfflineSessionMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *OfflineSessionMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().OfflineSession.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (m *OfflineSessionMutation) SetUserID(s string) {\n\tm.user_id = &s\n}\n\n// UserID returns the value of the \"user_id\" field in the mutation.\nfunc (m *OfflineSessionMutation) UserID() (r string, exists bool) {\n\tv := m.user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldUserID returns the old \"user_id\" field's value of the OfflineSession entity.\n// If the OfflineSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OfflineSessionMutation) OldUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldUserID: %w\", err)\n\t}\n\treturn oldValue.UserID, nil\n}\n\n// ResetUserID resets all changes to the \"user_id\" field.\nfunc (m *OfflineSessionMutation) ResetUserID() {\n\tm.user_id = nil\n}\n\n// SetConnID sets the \"conn_id\" field.\nfunc (m *OfflineSessionMutation) SetConnID(s string) {\n\tm.conn_id = &s\n}\n\n// ConnID returns the value of the \"conn_id\" field in the mutation.\nfunc (m *OfflineSessionMutation) ConnID() (r string, exists bool) {\n\tv := m.conn_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnID returns the old \"conn_id\" field's value of the OfflineSession entity.\n// If the OfflineSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OfflineSessionMutation) OldConnID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnID: %w\", err)\n\t}\n\treturn oldValue.ConnID, nil\n}\n\n// ResetConnID resets all changes to the \"conn_id\" field.\nfunc (m *OfflineSessionMutation) ResetConnID() {\n\tm.conn_id = nil\n}\n\n// SetRefresh sets the \"refresh\" field.\nfunc (m *OfflineSessionMutation) SetRefresh(b []byte) {\n\tm.refresh = &b\n}\n\n// Refresh returns the value of the \"refresh\" field in the mutation.\nfunc (m *OfflineSessionMutation) Refresh() (r []byte, exists bool) {\n\tv := m.refresh\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldRefresh returns the old \"refresh\" field's value of the OfflineSession entity.\n// If the OfflineSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OfflineSessionMutation) OldRefresh(ctx context.Context) (v []byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldRefresh is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldRefresh requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldRefresh: %w\", err)\n\t}\n\treturn oldValue.Refresh, nil\n}\n\n// ResetRefresh resets all changes to the \"refresh\" field.\nfunc (m *OfflineSessionMutation) ResetRefresh() {\n\tm.refresh = nil\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (m *OfflineSessionMutation) SetConnectorData(b []byte) {\n\tm.connector_data = &b\n}\n\n// ConnectorData returns the value of the \"connector_data\" field in the mutation.\nfunc (m *OfflineSessionMutation) ConnectorData() (r []byte, exists bool) {\n\tv := m.connector_data\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorData returns the old \"connector_data\" field's value of the OfflineSession entity.\n// If the OfflineSession object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *OfflineSessionMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorData is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorData requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorData: %w\", err)\n\t}\n\treturn oldValue.ConnectorData, nil\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (m *OfflineSessionMutation) ClearConnectorData() {\n\tm.connector_data = nil\n\tm.clearedFields[offlinesession.FieldConnectorData] = struct{}{}\n}\n\n// ConnectorDataCleared returns if the \"connector_data\" field was cleared in this mutation.\nfunc (m *OfflineSessionMutation) ConnectorDataCleared() bool {\n\t_, ok := m.clearedFields[offlinesession.FieldConnectorData]\n\treturn ok\n}\n\n// ResetConnectorData resets all changes to the \"connector_data\" field.\nfunc (m *OfflineSessionMutation) ResetConnectorData() {\n\tm.connector_data = nil\n\tdelete(m.clearedFields, offlinesession.FieldConnectorData)\n}\n\n// Where appends a list predicates to the OfflineSessionMutation builder.\nfunc (m *OfflineSessionMutation) Where(ps ...predicate.OfflineSession) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the OfflineSessionMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *OfflineSessionMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.OfflineSession, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *OfflineSessionMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *OfflineSessionMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (OfflineSession).\nfunc (m *OfflineSessionMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *OfflineSessionMutation) Fields() []string {\n\tfields := make([]string, 0, 4)\n\tif m.user_id != nil {\n\t\tfields = append(fields, offlinesession.FieldUserID)\n\t}\n\tif m.conn_id != nil {\n\t\tfields = append(fields, offlinesession.FieldConnID)\n\t}\n\tif m.refresh != nil {\n\t\tfields = append(fields, offlinesession.FieldRefresh)\n\t}\n\tif m.connector_data != nil {\n\t\tfields = append(fields, offlinesession.FieldConnectorData)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *OfflineSessionMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase offlinesession.FieldUserID:\n\t\treturn m.UserID()\n\tcase offlinesession.FieldConnID:\n\t\treturn m.ConnID()\n\tcase offlinesession.FieldRefresh:\n\t\treturn m.Refresh()\n\tcase offlinesession.FieldConnectorData:\n\t\treturn m.ConnectorData()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *OfflineSessionMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase offlinesession.FieldUserID:\n\t\treturn m.OldUserID(ctx)\n\tcase offlinesession.FieldConnID:\n\t\treturn m.OldConnID(ctx)\n\tcase offlinesession.FieldRefresh:\n\t\treturn m.OldRefresh(ctx)\n\tcase offlinesession.FieldConnectorData:\n\t\treturn m.OldConnectorData(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown OfflineSession field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *OfflineSessionMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase offlinesession.FieldUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetUserID(v)\n\t\treturn nil\n\tcase offlinesession.FieldConnID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnID(v)\n\t\treturn nil\n\tcase offlinesession.FieldRefresh:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetRefresh(v)\n\t\treturn nil\n\tcase offlinesession.FieldConnectorData:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorData(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown OfflineSession field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *OfflineSessionMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *OfflineSessionMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *OfflineSessionMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown OfflineSession numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *OfflineSessionMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(offlinesession.FieldConnectorData) {\n\t\tfields = append(fields, offlinesession.FieldConnectorData)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *OfflineSessionMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *OfflineSessionMutation) ClearField(name string) error {\n\tswitch name {\n\tcase offlinesession.FieldConnectorData:\n\t\tm.ClearConnectorData()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown OfflineSession nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *OfflineSessionMutation) ResetField(name string) error {\n\tswitch name {\n\tcase offlinesession.FieldUserID:\n\t\tm.ResetUserID()\n\t\treturn nil\n\tcase offlinesession.FieldConnID:\n\t\tm.ResetConnID()\n\t\treturn nil\n\tcase offlinesession.FieldRefresh:\n\t\tm.ResetRefresh()\n\t\treturn nil\n\tcase offlinesession.FieldConnectorData:\n\t\tm.ResetConnectorData()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown OfflineSession field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *OfflineSessionMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *OfflineSessionMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *OfflineSessionMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *OfflineSessionMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *OfflineSessionMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *OfflineSessionMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *OfflineSessionMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown OfflineSession unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *OfflineSessionMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown OfflineSession edge %s\", name)\n}\n\n// PasswordMutation represents an operation that mutates the Password nodes in the graph.\ntype PasswordMutation struct {\n\tconfig\n\top                 Op\n\ttyp                string\n\tid                 *int\n\temail              *string\n\thash               *[]byte\n\tusername           *string\n\tname               *string\n\tpreferred_username *string\n\temail_verified     *bool\n\tuser_id            *string\n\tgroups             *[]string\n\tappendgroups       []string\n\tclearedFields      map[string]struct{}\n\tdone               bool\n\toldValue           func(context.Context) (*Password, error)\n\tpredicates         []predicate.Password\n}\n\nvar _ ent.Mutation = (*PasswordMutation)(nil)\n\n// passwordOption allows management of the mutation configuration using functional options.\ntype passwordOption func(*PasswordMutation)\n\n// newPasswordMutation creates new mutation for the Password entity.\nfunc newPasswordMutation(c config, op Op, opts ...passwordOption) *PasswordMutation {\n\tm := &PasswordMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypePassword,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withPasswordID sets the ID field of the mutation.\nfunc withPasswordID(id int) passwordOption {\n\treturn func(m *PasswordMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *Password\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*Password, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().Password.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withPassword sets the old Password of the mutation.\nfunc withPassword(node *Password) passwordOption {\n\treturn func(m *PasswordMutation) {\n\t\tm.oldValue = func(context.Context) (*Password, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m PasswordMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m PasswordMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *PasswordMutation) ID() (id int, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *PasswordMutation) IDs(ctx context.Context) ([]int, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []int{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().Password.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetEmail sets the \"email\" field.\nfunc (m *PasswordMutation) SetEmail(s string) {\n\tm.email = &s\n}\n\n// Email returns the value of the \"email\" field in the mutation.\nfunc (m *PasswordMutation) Email() (r string, exists bool) {\n\tv := m.email\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldEmail returns the old \"email\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldEmail(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldEmail is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldEmail requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldEmail: %w\", err)\n\t}\n\treturn oldValue.Email, nil\n}\n\n// ResetEmail resets all changes to the \"email\" field.\nfunc (m *PasswordMutation) ResetEmail() {\n\tm.email = nil\n}\n\n// SetHash sets the \"hash\" field.\nfunc (m *PasswordMutation) SetHash(b []byte) {\n\tm.hash = &b\n}\n\n// Hash returns the value of the \"hash\" field in the mutation.\nfunc (m *PasswordMutation) Hash() (r []byte, exists bool) {\n\tv := m.hash\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldHash returns the old \"hash\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldHash(ctx context.Context) (v []byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldHash is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldHash requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldHash: %w\", err)\n\t}\n\treturn oldValue.Hash, nil\n}\n\n// ResetHash resets all changes to the \"hash\" field.\nfunc (m *PasswordMutation) ResetHash() {\n\tm.hash = nil\n}\n\n// SetUsername sets the \"username\" field.\nfunc (m *PasswordMutation) SetUsername(s string) {\n\tm.username = &s\n}\n\n// Username returns the value of the \"username\" field in the mutation.\nfunc (m *PasswordMutation) Username() (r string, exists bool) {\n\tv := m.username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldUsername returns the old \"username\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldUsername: %w\", err)\n\t}\n\treturn oldValue.Username, nil\n}\n\n// ResetUsername resets all changes to the \"username\" field.\nfunc (m *PasswordMutation) ResetUsername() {\n\tm.username = nil\n}\n\n// SetName sets the \"name\" field.\nfunc (m *PasswordMutation) SetName(s string) {\n\tm.name = &s\n}\n\n// Name returns the value of the \"name\" field in the mutation.\nfunc (m *PasswordMutation) Name() (r string, exists bool) {\n\tv := m.name\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldName returns the old \"name\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldName(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldName is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldName requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldName: %w\", err)\n\t}\n\treturn oldValue.Name, nil\n}\n\n// ResetName resets all changes to the \"name\" field.\nfunc (m *PasswordMutation) ResetName() {\n\tm.name = nil\n}\n\n// SetPreferredUsername sets the \"preferred_username\" field.\nfunc (m *PasswordMutation) SetPreferredUsername(s string) {\n\tm.preferred_username = &s\n}\n\n// PreferredUsername returns the value of the \"preferred_username\" field in the mutation.\nfunc (m *PasswordMutation) PreferredUsername() (r string, exists bool) {\n\tv := m.preferred_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldPreferredUsername returns the old \"preferred_username\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldPreferredUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldPreferredUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldPreferredUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldPreferredUsername: %w\", err)\n\t}\n\treturn oldValue.PreferredUsername, nil\n}\n\n// ResetPreferredUsername resets all changes to the \"preferred_username\" field.\nfunc (m *PasswordMutation) ResetPreferredUsername() {\n\tm.preferred_username = nil\n}\n\n// SetEmailVerified sets the \"email_verified\" field.\nfunc (m *PasswordMutation) SetEmailVerified(b bool) {\n\tm.email_verified = &b\n}\n\n// EmailVerified returns the value of the \"email_verified\" field in the mutation.\nfunc (m *PasswordMutation) EmailVerified() (r bool, exists bool) {\n\tv := m.email_verified\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldEmailVerified returns the old \"email_verified\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldEmailVerified(ctx context.Context) (v *bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldEmailVerified is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldEmailVerified requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldEmailVerified: %w\", err)\n\t}\n\treturn oldValue.EmailVerified, nil\n}\n\n// ClearEmailVerified clears the value of the \"email_verified\" field.\nfunc (m *PasswordMutation) ClearEmailVerified() {\n\tm.email_verified = nil\n\tm.clearedFields[password.FieldEmailVerified] = struct{}{}\n}\n\n// EmailVerifiedCleared returns if the \"email_verified\" field was cleared in this mutation.\nfunc (m *PasswordMutation) EmailVerifiedCleared() bool {\n\t_, ok := m.clearedFields[password.FieldEmailVerified]\n\treturn ok\n}\n\n// ResetEmailVerified resets all changes to the \"email_verified\" field.\nfunc (m *PasswordMutation) ResetEmailVerified() {\n\tm.email_verified = nil\n\tdelete(m.clearedFields, password.FieldEmailVerified)\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (m *PasswordMutation) SetUserID(s string) {\n\tm.user_id = &s\n}\n\n// UserID returns the value of the \"user_id\" field in the mutation.\nfunc (m *PasswordMutation) UserID() (r string, exists bool) {\n\tv := m.user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldUserID returns the old \"user_id\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldUserID: %w\", err)\n\t}\n\treturn oldValue.UserID, nil\n}\n\n// ResetUserID resets all changes to the \"user_id\" field.\nfunc (m *PasswordMutation) ResetUserID() {\n\tm.user_id = nil\n}\n\n// SetGroups sets the \"groups\" field.\nfunc (m *PasswordMutation) SetGroups(s []string) {\n\tm.groups = &s\n\tm.appendgroups = nil\n}\n\n// Groups returns the value of the \"groups\" field in the mutation.\nfunc (m *PasswordMutation) Groups() (r []string, exists bool) {\n\tv := m.groups\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldGroups returns the old \"groups\" field's value of the Password entity.\n// If the Password object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *PasswordMutation) OldGroups(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldGroups is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldGroups requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldGroups: %w\", err)\n\t}\n\treturn oldValue.Groups, nil\n}\n\n// AppendGroups adds s to the \"groups\" field.\nfunc (m *PasswordMutation) AppendGroups(s []string) {\n\tm.appendgroups = append(m.appendgroups, s...)\n}\n\n// AppendedGroups returns the list of values that were appended to the \"groups\" field in this mutation.\nfunc (m *PasswordMutation) AppendedGroups() ([]string, bool) {\n\tif len(m.appendgroups) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendgroups, true\n}\n\n// ClearGroups clears the value of the \"groups\" field.\nfunc (m *PasswordMutation) ClearGroups() {\n\tm.groups = nil\n\tm.appendgroups = nil\n\tm.clearedFields[password.FieldGroups] = struct{}{}\n}\n\n// GroupsCleared returns if the \"groups\" field was cleared in this mutation.\nfunc (m *PasswordMutation) GroupsCleared() bool {\n\t_, ok := m.clearedFields[password.FieldGroups]\n\treturn ok\n}\n\n// ResetGroups resets all changes to the \"groups\" field.\nfunc (m *PasswordMutation) ResetGroups() {\n\tm.groups = nil\n\tm.appendgroups = nil\n\tdelete(m.clearedFields, password.FieldGroups)\n}\n\n// Where appends a list predicates to the PasswordMutation builder.\nfunc (m *PasswordMutation) Where(ps ...predicate.Password) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the PasswordMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *PasswordMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.Password, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *PasswordMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *PasswordMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (Password).\nfunc (m *PasswordMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *PasswordMutation) Fields() []string {\n\tfields := make([]string, 0, 8)\n\tif m.email != nil {\n\t\tfields = append(fields, password.FieldEmail)\n\t}\n\tif m.hash != nil {\n\t\tfields = append(fields, password.FieldHash)\n\t}\n\tif m.username != nil {\n\t\tfields = append(fields, password.FieldUsername)\n\t}\n\tif m.name != nil {\n\t\tfields = append(fields, password.FieldName)\n\t}\n\tif m.preferred_username != nil {\n\t\tfields = append(fields, password.FieldPreferredUsername)\n\t}\n\tif m.email_verified != nil {\n\t\tfields = append(fields, password.FieldEmailVerified)\n\t}\n\tif m.user_id != nil {\n\t\tfields = append(fields, password.FieldUserID)\n\t}\n\tif m.groups != nil {\n\t\tfields = append(fields, password.FieldGroups)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *PasswordMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase password.FieldEmail:\n\t\treturn m.Email()\n\tcase password.FieldHash:\n\t\treturn m.Hash()\n\tcase password.FieldUsername:\n\t\treturn m.Username()\n\tcase password.FieldName:\n\t\treturn m.Name()\n\tcase password.FieldPreferredUsername:\n\t\treturn m.PreferredUsername()\n\tcase password.FieldEmailVerified:\n\t\treturn m.EmailVerified()\n\tcase password.FieldUserID:\n\t\treturn m.UserID()\n\tcase password.FieldGroups:\n\t\treturn m.Groups()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *PasswordMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase password.FieldEmail:\n\t\treturn m.OldEmail(ctx)\n\tcase password.FieldHash:\n\t\treturn m.OldHash(ctx)\n\tcase password.FieldUsername:\n\t\treturn m.OldUsername(ctx)\n\tcase password.FieldName:\n\t\treturn m.OldName(ctx)\n\tcase password.FieldPreferredUsername:\n\t\treturn m.OldPreferredUsername(ctx)\n\tcase password.FieldEmailVerified:\n\t\treturn m.OldEmailVerified(ctx)\n\tcase password.FieldUserID:\n\t\treturn m.OldUserID(ctx)\n\tcase password.FieldGroups:\n\t\treturn m.OldGroups(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown Password field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *PasswordMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase password.FieldEmail:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetEmail(v)\n\t\treturn nil\n\tcase password.FieldHash:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetHash(v)\n\t\treturn nil\n\tcase password.FieldUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetUsername(v)\n\t\treturn nil\n\tcase password.FieldName:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetName(v)\n\t\treturn nil\n\tcase password.FieldPreferredUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetPreferredUsername(v)\n\t\treturn nil\n\tcase password.FieldEmailVerified:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetEmailVerified(v)\n\t\treturn nil\n\tcase password.FieldUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetUserID(v)\n\t\treturn nil\n\tcase password.FieldGroups:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetGroups(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Password field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *PasswordMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *PasswordMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *PasswordMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown Password numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *PasswordMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(password.FieldEmailVerified) {\n\t\tfields = append(fields, password.FieldEmailVerified)\n\t}\n\tif m.FieldCleared(password.FieldGroups) {\n\t\tfields = append(fields, password.FieldGroups)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *PasswordMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *PasswordMutation) ClearField(name string) error {\n\tswitch name {\n\tcase password.FieldEmailVerified:\n\t\tm.ClearEmailVerified()\n\t\treturn nil\n\tcase password.FieldGroups:\n\t\tm.ClearGroups()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Password nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *PasswordMutation) ResetField(name string) error {\n\tswitch name {\n\tcase password.FieldEmail:\n\t\tm.ResetEmail()\n\t\treturn nil\n\tcase password.FieldHash:\n\t\tm.ResetHash()\n\t\treturn nil\n\tcase password.FieldUsername:\n\t\tm.ResetUsername()\n\t\treturn nil\n\tcase password.FieldName:\n\t\tm.ResetName()\n\t\treturn nil\n\tcase password.FieldPreferredUsername:\n\t\tm.ResetPreferredUsername()\n\t\treturn nil\n\tcase password.FieldEmailVerified:\n\t\tm.ResetEmailVerified()\n\t\treturn nil\n\tcase password.FieldUserID:\n\t\tm.ResetUserID()\n\t\treturn nil\n\tcase password.FieldGroups:\n\t\tm.ResetGroups()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown Password field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *PasswordMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *PasswordMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *PasswordMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *PasswordMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *PasswordMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *PasswordMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *PasswordMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown Password unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *PasswordMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown Password edge %s\", name)\n}\n\n// RefreshTokenMutation represents an operation that mutates the RefreshToken nodes in the graph.\ntype RefreshTokenMutation struct {\n\tconfig\n\top                        Op\n\ttyp                       string\n\tid                        *string\n\tclient_id                 *string\n\tscopes                    *[]string\n\tappendscopes              []string\n\tnonce                     *string\n\tclaims_user_id            *string\n\tclaims_username           *string\n\tclaims_email              *string\n\tclaims_email_verified     *bool\n\tclaims_groups             *[]string\n\tappendclaims_groups       []string\n\tclaims_preferred_username *string\n\tconnector_id              *string\n\tconnector_data            *[]byte\n\ttoken                     *string\n\tobsolete_token            *string\n\tcreated_at                *time.Time\n\tlast_used                 *time.Time\n\tclearedFields             map[string]struct{}\n\tdone                      bool\n\toldValue                  func(context.Context) (*RefreshToken, error)\n\tpredicates                []predicate.RefreshToken\n}\n\nvar _ ent.Mutation = (*RefreshTokenMutation)(nil)\n\n// refreshtokenOption allows management of the mutation configuration using functional options.\ntype refreshtokenOption func(*RefreshTokenMutation)\n\n// newRefreshTokenMutation creates new mutation for the RefreshToken entity.\nfunc newRefreshTokenMutation(c config, op Op, opts ...refreshtokenOption) *RefreshTokenMutation {\n\tm := &RefreshTokenMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeRefreshToken,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withRefreshTokenID sets the ID field of the mutation.\nfunc withRefreshTokenID(id string) refreshtokenOption {\n\treturn func(m *RefreshTokenMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *RefreshToken\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*RefreshToken, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().RefreshToken.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withRefreshToken sets the old RefreshToken of the mutation.\nfunc withRefreshToken(node *RefreshToken) refreshtokenOption {\n\treturn func(m *RefreshTokenMutation) {\n\t\tm.oldValue = func(context.Context) (*RefreshToken, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m RefreshTokenMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m RefreshTokenMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of RefreshToken entities.\nfunc (m *RefreshTokenMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *RefreshTokenMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *RefreshTokenMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().RefreshToken.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (m *RefreshTokenMutation) SetClientID(s string) {\n\tm.client_id = &s\n}\n\n// ClientID returns the value of the \"client_id\" field in the mutation.\nfunc (m *RefreshTokenMutation) ClientID() (r string, exists bool) {\n\tv := m.client_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClientID returns the old \"client_id\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldClientID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClientID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClientID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClientID: %w\", err)\n\t}\n\treturn oldValue.ClientID, nil\n}\n\n// ResetClientID resets all changes to the \"client_id\" field.\nfunc (m *RefreshTokenMutation) ResetClientID() {\n\tm.client_id = nil\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (m *RefreshTokenMutation) SetScopes(s []string) {\n\tm.scopes = &s\n\tm.appendscopes = nil\n}\n\n// Scopes returns the value of the \"scopes\" field in the mutation.\nfunc (m *RefreshTokenMutation) Scopes() (r []string, exists bool) {\n\tv := m.scopes\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldScopes returns the old \"scopes\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldScopes(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldScopes is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldScopes requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldScopes: %w\", err)\n\t}\n\treturn oldValue.Scopes, nil\n}\n\n// AppendScopes adds s to the \"scopes\" field.\nfunc (m *RefreshTokenMutation) AppendScopes(s []string) {\n\tm.appendscopes = append(m.appendscopes, s...)\n}\n\n// AppendedScopes returns the list of values that were appended to the \"scopes\" field in this mutation.\nfunc (m *RefreshTokenMutation) AppendedScopes() ([]string, bool) {\n\tif len(m.appendscopes) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendscopes, true\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (m *RefreshTokenMutation) ClearScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tm.clearedFields[refreshtoken.FieldScopes] = struct{}{}\n}\n\n// ScopesCleared returns if the \"scopes\" field was cleared in this mutation.\nfunc (m *RefreshTokenMutation) ScopesCleared() bool {\n\t_, ok := m.clearedFields[refreshtoken.FieldScopes]\n\treturn ok\n}\n\n// ResetScopes resets all changes to the \"scopes\" field.\nfunc (m *RefreshTokenMutation) ResetScopes() {\n\tm.scopes = nil\n\tm.appendscopes = nil\n\tdelete(m.clearedFields, refreshtoken.FieldScopes)\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (m *RefreshTokenMutation) SetNonce(s string) {\n\tm.nonce = &s\n}\n\n// Nonce returns the value of the \"nonce\" field in the mutation.\nfunc (m *RefreshTokenMutation) Nonce() (r string, exists bool) {\n\tv := m.nonce\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldNonce returns the old \"nonce\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldNonce(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldNonce is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldNonce requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldNonce: %w\", err)\n\t}\n\treturn oldValue.Nonce, nil\n}\n\n// ResetNonce resets all changes to the \"nonce\" field.\nfunc (m *RefreshTokenMutation) ResetNonce() {\n\tm.nonce = nil\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (m *RefreshTokenMutation) SetClaimsUserID(s string) {\n\tm.claims_user_id = &s\n}\n\n// ClaimsUserID returns the value of the \"claims_user_id\" field in the mutation.\nfunc (m *RefreshTokenMutation) ClaimsUserID() (r string, exists bool) {\n\tv := m.claims_user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUserID returns the old \"claims_user_id\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldClaimsUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUserID: %w\", err)\n\t}\n\treturn oldValue.ClaimsUserID, nil\n}\n\n// ResetClaimsUserID resets all changes to the \"claims_user_id\" field.\nfunc (m *RefreshTokenMutation) ResetClaimsUserID() {\n\tm.claims_user_id = nil\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (m *RefreshTokenMutation) SetClaimsUsername(s string) {\n\tm.claims_username = &s\n}\n\n// ClaimsUsername returns the value of the \"claims_username\" field in the mutation.\nfunc (m *RefreshTokenMutation) ClaimsUsername() (r string, exists bool) {\n\tv := m.claims_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUsername returns the old \"claims_username\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldClaimsUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsUsername, nil\n}\n\n// ResetClaimsUsername resets all changes to the \"claims_username\" field.\nfunc (m *RefreshTokenMutation) ResetClaimsUsername() {\n\tm.claims_username = nil\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (m *RefreshTokenMutation) SetClaimsEmail(s string) {\n\tm.claims_email = &s\n}\n\n// ClaimsEmail returns the value of the \"claims_email\" field in the mutation.\nfunc (m *RefreshTokenMutation) ClaimsEmail() (r string, exists bool) {\n\tv := m.claims_email\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmail returns the old \"claims_email\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldClaimsEmail(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmail is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmail requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmail: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmail, nil\n}\n\n// ResetClaimsEmail resets all changes to the \"claims_email\" field.\nfunc (m *RefreshTokenMutation) ResetClaimsEmail() {\n\tm.claims_email = nil\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (m *RefreshTokenMutation) SetClaimsEmailVerified(b bool) {\n\tm.claims_email_verified = &b\n}\n\n// ClaimsEmailVerified returns the value of the \"claims_email_verified\" field in the mutation.\nfunc (m *RefreshTokenMutation) ClaimsEmailVerified() (r bool, exists bool) {\n\tv := m.claims_email_verified\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmailVerified returns the old \"claims_email_verified\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmailVerified: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmailVerified, nil\n}\n\n// ResetClaimsEmailVerified resets all changes to the \"claims_email_verified\" field.\nfunc (m *RefreshTokenMutation) ResetClaimsEmailVerified() {\n\tm.claims_email_verified = nil\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (m *RefreshTokenMutation) SetClaimsGroups(s []string) {\n\tm.claims_groups = &s\n\tm.appendclaims_groups = nil\n}\n\n// ClaimsGroups returns the value of the \"claims_groups\" field in the mutation.\nfunc (m *RefreshTokenMutation) ClaimsGroups() (r []string, exists bool) {\n\tv := m.claims_groups\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsGroups returns the old \"claims_groups\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsGroups is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsGroups requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsGroups: %w\", err)\n\t}\n\treturn oldValue.ClaimsGroups, nil\n}\n\n// AppendClaimsGroups adds s to the \"claims_groups\" field.\nfunc (m *RefreshTokenMutation) AppendClaimsGroups(s []string) {\n\tm.appendclaims_groups = append(m.appendclaims_groups, s...)\n}\n\n// AppendedClaimsGroups returns the list of values that were appended to the \"claims_groups\" field in this mutation.\nfunc (m *RefreshTokenMutation) AppendedClaimsGroups() ([]string, bool) {\n\tif len(m.appendclaims_groups) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendclaims_groups, true\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (m *RefreshTokenMutation) ClearClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tm.clearedFields[refreshtoken.FieldClaimsGroups] = struct{}{}\n}\n\n// ClaimsGroupsCleared returns if the \"claims_groups\" field was cleared in this mutation.\nfunc (m *RefreshTokenMutation) ClaimsGroupsCleared() bool {\n\t_, ok := m.clearedFields[refreshtoken.FieldClaimsGroups]\n\treturn ok\n}\n\n// ResetClaimsGroups resets all changes to the \"claims_groups\" field.\nfunc (m *RefreshTokenMutation) ResetClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tdelete(m.clearedFields, refreshtoken.FieldClaimsGroups)\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (m *RefreshTokenMutation) SetClaimsPreferredUsername(s string) {\n\tm.claims_preferred_username = &s\n}\n\n// ClaimsPreferredUsername returns the value of the \"claims_preferred_username\" field in the mutation.\nfunc (m *RefreshTokenMutation) ClaimsPreferredUsername() (r string, exists bool) {\n\tv := m.claims_preferred_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsPreferredUsername returns the old \"claims_preferred_username\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsPreferredUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsPreferredUsername, nil\n}\n\n// ResetClaimsPreferredUsername resets all changes to the \"claims_preferred_username\" field.\nfunc (m *RefreshTokenMutation) ResetClaimsPreferredUsername() {\n\tm.claims_preferred_username = nil\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (m *RefreshTokenMutation) SetConnectorID(s string) {\n\tm.connector_id = &s\n}\n\n// ConnectorID returns the value of the \"connector_id\" field in the mutation.\nfunc (m *RefreshTokenMutation) ConnectorID() (r string, exists bool) {\n\tv := m.connector_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorID returns the old \"connector_id\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldConnectorID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorID: %w\", err)\n\t}\n\treturn oldValue.ConnectorID, nil\n}\n\n// ResetConnectorID resets all changes to the \"connector_id\" field.\nfunc (m *RefreshTokenMutation) ResetConnectorID() {\n\tm.connector_id = nil\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (m *RefreshTokenMutation) SetConnectorData(b []byte) {\n\tm.connector_data = &b\n}\n\n// ConnectorData returns the value of the \"connector_data\" field in the mutation.\nfunc (m *RefreshTokenMutation) ConnectorData() (r []byte, exists bool) {\n\tv := m.connector_data\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorData returns the old \"connector_data\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldConnectorData(ctx context.Context) (v *[]byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorData is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorData requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorData: %w\", err)\n\t}\n\treturn oldValue.ConnectorData, nil\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (m *RefreshTokenMutation) ClearConnectorData() {\n\tm.connector_data = nil\n\tm.clearedFields[refreshtoken.FieldConnectorData] = struct{}{}\n}\n\n// ConnectorDataCleared returns if the \"connector_data\" field was cleared in this mutation.\nfunc (m *RefreshTokenMutation) ConnectorDataCleared() bool {\n\t_, ok := m.clearedFields[refreshtoken.FieldConnectorData]\n\treturn ok\n}\n\n// ResetConnectorData resets all changes to the \"connector_data\" field.\nfunc (m *RefreshTokenMutation) ResetConnectorData() {\n\tm.connector_data = nil\n\tdelete(m.clearedFields, refreshtoken.FieldConnectorData)\n}\n\n// SetToken sets the \"token\" field.\nfunc (m *RefreshTokenMutation) SetToken(s string) {\n\tm.token = &s\n}\n\n// Token returns the value of the \"token\" field in the mutation.\nfunc (m *RefreshTokenMutation) Token() (r string, exists bool) {\n\tv := m.token\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldToken returns the old \"token\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldToken(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldToken is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldToken requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldToken: %w\", err)\n\t}\n\treturn oldValue.Token, nil\n}\n\n// ResetToken resets all changes to the \"token\" field.\nfunc (m *RefreshTokenMutation) ResetToken() {\n\tm.token = nil\n}\n\n// SetObsoleteToken sets the \"obsolete_token\" field.\nfunc (m *RefreshTokenMutation) SetObsoleteToken(s string) {\n\tm.obsolete_token = &s\n}\n\n// ObsoleteToken returns the value of the \"obsolete_token\" field in the mutation.\nfunc (m *RefreshTokenMutation) ObsoleteToken() (r string, exists bool) {\n\tv := m.obsolete_token\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldObsoleteToken returns the old \"obsolete_token\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldObsoleteToken(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldObsoleteToken is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldObsoleteToken requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldObsoleteToken: %w\", err)\n\t}\n\treturn oldValue.ObsoleteToken, nil\n}\n\n// ResetObsoleteToken resets all changes to the \"obsolete_token\" field.\nfunc (m *RefreshTokenMutation) ResetObsoleteToken() {\n\tm.obsolete_token = nil\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (m *RefreshTokenMutation) SetCreatedAt(t time.Time) {\n\tm.created_at = &t\n}\n\n// CreatedAt returns the value of the \"created_at\" field in the mutation.\nfunc (m *RefreshTokenMutation) CreatedAt() (r time.Time, exists bool) {\n\tv := m.created_at\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCreatedAt returns the old \"created_at\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCreatedAt is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCreatedAt requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCreatedAt: %w\", err)\n\t}\n\treturn oldValue.CreatedAt, nil\n}\n\n// ResetCreatedAt resets all changes to the \"created_at\" field.\nfunc (m *RefreshTokenMutation) ResetCreatedAt() {\n\tm.created_at = nil\n}\n\n// SetLastUsed sets the \"last_used\" field.\nfunc (m *RefreshTokenMutation) SetLastUsed(t time.Time) {\n\tm.last_used = &t\n}\n\n// LastUsed returns the value of the \"last_used\" field in the mutation.\nfunc (m *RefreshTokenMutation) LastUsed() (r time.Time, exists bool) {\n\tv := m.last_used\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldLastUsed returns the old \"last_used\" field's value of the RefreshToken entity.\n// If the RefreshToken object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *RefreshTokenMutation) OldLastUsed(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldLastUsed is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldLastUsed requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldLastUsed: %w\", err)\n\t}\n\treturn oldValue.LastUsed, nil\n}\n\n// ResetLastUsed resets all changes to the \"last_used\" field.\nfunc (m *RefreshTokenMutation) ResetLastUsed() {\n\tm.last_used = nil\n}\n\n// Where appends a list predicates to the RefreshTokenMutation builder.\nfunc (m *RefreshTokenMutation) Where(ps ...predicate.RefreshToken) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the RefreshTokenMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *RefreshTokenMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.RefreshToken, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *RefreshTokenMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *RefreshTokenMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (RefreshToken).\nfunc (m *RefreshTokenMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *RefreshTokenMutation) Fields() []string {\n\tfields := make([]string, 0, 15)\n\tif m.client_id != nil {\n\t\tfields = append(fields, refreshtoken.FieldClientID)\n\t}\n\tif m.scopes != nil {\n\t\tfields = append(fields, refreshtoken.FieldScopes)\n\t}\n\tif m.nonce != nil {\n\t\tfields = append(fields, refreshtoken.FieldNonce)\n\t}\n\tif m.claims_user_id != nil {\n\t\tfields = append(fields, refreshtoken.FieldClaimsUserID)\n\t}\n\tif m.claims_username != nil {\n\t\tfields = append(fields, refreshtoken.FieldClaimsUsername)\n\t}\n\tif m.claims_email != nil {\n\t\tfields = append(fields, refreshtoken.FieldClaimsEmail)\n\t}\n\tif m.claims_email_verified != nil {\n\t\tfields = append(fields, refreshtoken.FieldClaimsEmailVerified)\n\t}\n\tif m.claims_groups != nil {\n\t\tfields = append(fields, refreshtoken.FieldClaimsGroups)\n\t}\n\tif m.claims_preferred_username != nil {\n\t\tfields = append(fields, refreshtoken.FieldClaimsPreferredUsername)\n\t}\n\tif m.connector_id != nil {\n\t\tfields = append(fields, refreshtoken.FieldConnectorID)\n\t}\n\tif m.connector_data != nil {\n\t\tfields = append(fields, refreshtoken.FieldConnectorData)\n\t}\n\tif m.token != nil {\n\t\tfields = append(fields, refreshtoken.FieldToken)\n\t}\n\tif m.obsolete_token != nil {\n\t\tfields = append(fields, refreshtoken.FieldObsoleteToken)\n\t}\n\tif m.created_at != nil {\n\t\tfields = append(fields, refreshtoken.FieldCreatedAt)\n\t}\n\tif m.last_used != nil {\n\t\tfields = append(fields, refreshtoken.FieldLastUsed)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *RefreshTokenMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase refreshtoken.FieldClientID:\n\t\treturn m.ClientID()\n\tcase refreshtoken.FieldScopes:\n\t\treturn m.Scopes()\n\tcase refreshtoken.FieldNonce:\n\t\treturn m.Nonce()\n\tcase refreshtoken.FieldClaimsUserID:\n\t\treturn m.ClaimsUserID()\n\tcase refreshtoken.FieldClaimsUsername:\n\t\treturn m.ClaimsUsername()\n\tcase refreshtoken.FieldClaimsEmail:\n\t\treturn m.ClaimsEmail()\n\tcase refreshtoken.FieldClaimsEmailVerified:\n\t\treturn m.ClaimsEmailVerified()\n\tcase refreshtoken.FieldClaimsGroups:\n\t\treturn m.ClaimsGroups()\n\tcase refreshtoken.FieldClaimsPreferredUsername:\n\t\treturn m.ClaimsPreferredUsername()\n\tcase refreshtoken.FieldConnectorID:\n\t\treturn m.ConnectorID()\n\tcase refreshtoken.FieldConnectorData:\n\t\treturn m.ConnectorData()\n\tcase refreshtoken.FieldToken:\n\t\treturn m.Token()\n\tcase refreshtoken.FieldObsoleteToken:\n\t\treturn m.ObsoleteToken()\n\tcase refreshtoken.FieldCreatedAt:\n\t\treturn m.CreatedAt()\n\tcase refreshtoken.FieldLastUsed:\n\t\treturn m.LastUsed()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *RefreshTokenMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase refreshtoken.FieldClientID:\n\t\treturn m.OldClientID(ctx)\n\tcase refreshtoken.FieldScopes:\n\t\treturn m.OldScopes(ctx)\n\tcase refreshtoken.FieldNonce:\n\t\treturn m.OldNonce(ctx)\n\tcase refreshtoken.FieldClaimsUserID:\n\t\treturn m.OldClaimsUserID(ctx)\n\tcase refreshtoken.FieldClaimsUsername:\n\t\treturn m.OldClaimsUsername(ctx)\n\tcase refreshtoken.FieldClaimsEmail:\n\t\treturn m.OldClaimsEmail(ctx)\n\tcase refreshtoken.FieldClaimsEmailVerified:\n\t\treturn m.OldClaimsEmailVerified(ctx)\n\tcase refreshtoken.FieldClaimsGroups:\n\t\treturn m.OldClaimsGroups(ctx)\n\tcase refreshtoken.FieldClaimsPreferredUsername:\n\t\treturn m.OldClaimsPreferredUsername(ctx)\n\tcase refreshtoken.FieldConnectorID:\n\t\treturn m.OldConnectorID(ctx)\n\tcase refreshtoken.FieldConnectorData:\n\t\treturn m.OldConnectorData(ctx)\n\tcase refreshtoken.FieldToken:\n\t\treturn m.OldToken(ctx)\n\tcase refreshtoken.FieldObsoleteToken:\n\t\treturn m.OldObsoleteToken(ctx)\n\tcase refreshtoken.FieldCreatedAt:\n\t\treturn m.OldCreatedAt(ctx)\n\tcase refreshtoken.FieldLastUsed:\n\t\treturn m.OldLastUsed(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown RefreshToken field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *RefreshTokenMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase refreshtoken.FieldClientID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClientID(v)\n\t\treturn nil\n\tcase refreshtoken.FieldScopes:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetScopes(v)\n\t\treturn nil\n\tcase refreshtoken.FieldNonce:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetNonce(v)\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUserID(v)\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUsername(v)\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsEmail:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmail(v)\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsEmailVerified:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmailVerified(v)\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsGroups:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsGroups(v)\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsPreferredUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsPreferredUsername(v)\n\t\treturn nil\n\tcase refreshtoken.FieldConnectorID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorID(v)\n\t\treturn nil\n\tcase refreshtoken.FieldConnectorData:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorData(v)\n\t\treturn nil\n\tcase refreshtoken.FieldToken:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetToken(v)\n\t\treturn nil\n\tcase refreshtoken.FieldObsoleteToken:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetObsoleteToken(v)\n\t\treturn nil\n\tcase refreshtoken.FieldCreatedAt:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCreatedAt(v)\n\t\treturn nil\n\tcase refreshtoken.FieldLastUsed:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetLastUsed(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown RefreshToken field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *RefreshTokenMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *RefreshTokenMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *RefreshTokenMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown RefreshToken numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *RefreshTokenMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(refreshtoken.FieldScopes) {\n\t\tfields = append(fields, refreshtoken.FieldScopes)\n\t}\n\tif m.FieldCleared(refreshtoken.FieldClaimsGroups) {\n\t\tfields = append(fields, refreshtoken.FieldClaimsGroups)\n\t}\n\tif m.FieldCleared(refreshtoken.FieldConnectorData) {\n\t\tfields = append(fields, refreshtoken.FieldConnectorData)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *RefreshTokenMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *RefreshTokenMutation) ClearField(name string) error {\n\tswitch name {\n\tcase refreshtoken.FieldScopes:\n\t\tm.ClearScopes()\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsGroups:\n\t\tm.ClearClaimsGroups()\n\t\treturn nil\n\tcase refreshtoken.FieldConnectorData:\n\t\tm.ClearConnectorData()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown RefreshToken nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *RefreshTokenMutation) ResetField(name string) error {\n\tswitch name {\n\tcase refreshtoken.FieldClientID:\n\t\tm.ResetClientID()\n\t\treturn nil\n\tcase refreshtoken.FieldScopes:\n\t\tm.ResetScopes()\n\t\treturn nil\n\tcase refreshtoken.FieldNonce:\n\t\tm.ResetNonce()\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsUserID:\n\t\tm.ResetClaimsUserID()\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsUsername:\n\t\tm.ResetClaimsUsername()\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsEmail:\n\t\tm.ResetClaimsEmail()\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsEmailVerified:\n\t\tm.ResetClaimsEmailVerified()\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsGroups:\n\t\tm.ResetClaimsGroups()\n\t\treturn nil\n\tcase refreshtoken.FieldClaimsPreferredUsername:\n\t\tm.ResetClaimsPreferredUsername()\n\t\treturn nil\n\tcase refreshtoken.FieldConnectorID:\n\t\tm.ResetConnectorID()\n\t\treturn nil\n\tcase refreshtoken.FieldConnectorData:\n\t\tm.ResetConnectorData()\n\t\treturn nil\n\tcase refreshtoken.FieldToken:\n\t\tm.ResetToken()\n\t\treturn nil\n\tcase refreshtoken.FieldObsoleteToken:\n\t\tm.ResetObsoleteToken()\n\t\treturn nil\n\tcase refreshtoken.FieldCreatedAt:\n\t\tm.ResetCreatedAt()\n\t\treturn nil\n\tcase refreshtoken.FieldLastUsed:\n\t\tm.ResetLastUsed()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown RefreshToken field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *RefreshTokenMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *RefreshTokenMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *RefreshTokenMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *RefreshTokenMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *RefreshTokenMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *RefreshTokenMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *RefreshTokenMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown RefreshToken unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *RefreshTokenMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown RefreshToken edge %s\", name)\n}\n\n// UserIdentityMutation represents an operation that mutates the UserIdentity nodes in the graph.\ntype UserIdentityMutation struct {\n\tconfig\n\top                        Op\n\ttyp                       string\n\tid                        *string\n\tuser_id                   *string\n\tconnector_id              *string\n\tclaims_user_id            *string\n\tclaims_username           *string\n\tclaims_preferred_username *string\n\tclaims_email              *string\n\tclaims_email_verified     *bool\n\tclaims_groups             *[]string\n\tappendclaims_groups       []string\n\tconsents                  *[]byte\n\tmfa_secrets               *[]byte\n\tcreated_at                *time.Time\n\tlast_login                *time.Time\n\tblocked_until             *time.Time\n\tclearedFields             map[string]struct{}\n\tdone                      bool\n\toldValue                  func(context.Context) (*UserIdentity, error)\n\tpredicates                []predicate.UserIdentity\n}\n\nvar _ ent.Mutation = (*UserIdentityMutation)(nil)\n\n// useridentityOption allows management of the mutation configuration using functional options.\ntype useridentityOption func(*UserIdentityMutation)\n\n// newUserIdentityMutation creates new mutation for the UserIdentity entity.\nfunc newUserIdentityMutation(c config, op Op, opts ...useridentityOption) *UserIdentityMutation {\n\tm := &UserIdentityMutation{\n\t\tconfig:        c,\n\t\top:            op,\n\t\ttyp:           TypeUserIdentity,\n\t\tclearedFields: make(map[string]struct{}),\n\t}\n\tfor _, opt := range opts {\n\t\topt(m)\n\t}\n\treturn m\n}\n\n// withUserIdentityID sets the ID field of the mutation.\nfunc withUserIdentityID(id string) useridentityOption {\n\treturn func(m *UserIdentityMutation) {\n\t\tvar (\n\t\t\terr   error\n\t\t\tonce  sync.Once\n\t\t\tvalue *UserIdentity\n\t\t)\n\t\tm.oldValue = func(ctx context.Context) (*UserIdentity, error) {\n\t\t\tonce.Do(func() {\n\t\t\t\tif m.done {\n\t\t\t\t\terr = errors.New(\"querying old values post mutation is not allowed\")\n\t\t\t\t} else {\n\t\t\t\t\tvalue, err = m.Client().UserIdentity.Get(ctx, id)\n\t\t\t\t}\n\t\t\t})\n\t\t\treturn value, err\n\t\t}\n\t\tm.id = &id\n\t}\n}\n\n// withUserIdentity sets the old UserIdentity of the mutation.\nfunc withUserIdentity(node *UserIdentity) useridentityOption {\n\treturn func(m *UserIdentityMutation) {\n\t\tm.oldValue = func(context.Context) (*UserIdentity, error) {\n\t\t\treturn node, nil\n\t\t}\n\t\tm.id = &node.ID\n\t}\n}\n\n// Client returns a new `ent.Client` from the mutation. If the mutation was\n// executed in a transaction (ent.Tx), a transactional client is returned.\nfunc (m UserIdentityMutation) Client() *Client {\n\tclient := &Client{config: m.config}\n\tclient.init()\n\treturn client\n}\n\n// Tx returns an `ent.Tx` for mutations that were executed in transactions;\n// it returns an error otherwise.\nfunc (m UserIdentityMutation) Tx() (*Tx, error) {\n\tif _, ok := m.driver.(*txDriver); !ok {\n\t\treturn nil, errors.New(\"db: mutation is not running in a transaction\")\n\t}\n\ttx := &Tx{config: m.config}\n\ttx.init()\n\treturn tx, nil\n}\n\n// SetID sets the value of the id field. Note that this\n// operation is only accepted on creation of UserIdentity entities.\nfunc (m *UserIdentityMutation) SetID(id string) {\n\tm.id = &id\n}\n\n// ID returns the ID value in the mutation. Note that the ID is only available\n// if it was provided to the builder or after it was returned from the database.\nfunc (m *UserIdentityMutation) ID() (id string, exists bool) {\n\tif m.id == nil {\n\t\treturn\n\t}\n\treturn *m.id, true\n}\n\n// IDs queries the database and returns the entity ids that match the mutation's predicate.\n// That means, if the mutation is applied within a transaction with an isolation level such\n// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated\n// or updated by the mutation.\nfunc (m *UserIdentityMutation) IDs(ctx context.Context) ([]string, error) {\n\tswitch {\n\tcase m.op.Is(OpUpdateOne | OpDeleteOne):\n\t\tid, exists := m.ID()\n\t\tif exists {\n\t\t\treturn []string{id}, nil\n\t\t}\n\t\tfallthrough\n\tcase m.op.Is(OpUpdate | OpDelete):\n\t\treturn m.Client().UserIdentity.Query().Where(m.predicates...).IDs(ctx)\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"IDs is not allowed on %s operations\", m.op)\n\t}\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (m *UserIdentityMutation) SetUserID(s string) {\n\tm.user_id = &s\n}\n\n// UserID returns the value of the \"user_id\" field in the mutation.\nfunc (m *UserIdentityMutation) UserID() (r string, exists bool) {\n\tv := m.user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldUserID returns the old \"user_id\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldUserID: %w\", err)\n\t}\n\treturn oldValue.UserID, nil\n}\n\n// ResetUserID resets all changes to the \"user_id\" field.\nfunc (m *UserIdentityMutation) ResetUserID() {\n\tm.user_id = nil\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (m *UserIdentityMutation) SetConnectorID(s string) {\n\tm.connector_id = &s\n}\n\n// ConnectorID returns the value of the \"connector_id\" field in the mutation.\nfunc (m *UserIdentityMutation) ConnectorID() (r string, exists bool) {\n\tv := m.connector_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConnectorID returns the old \"connector_id\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldConnectorID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConnectorID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConnectorID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConnectorID: %w\", err)\n\t}\n\treturn oldValue.ConnectorID, nil\n}\n\n// ResetConnectorID resets all changes to the \"connector_id\" field.\nfunc (m *UserIdentityMutation) ResetConnectorID() {\n\tm.connector_id = nil\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (m *UserIdentityMutation) SetClaimsUserID(s string) {\n\tm.claims_user_id = &s\n}\n\n// ClaimsUserID returns the value of the \"claims_user_id\" field in the mutation.\nfunc (m *UserIdentityMutation) ClaimsUserID() (r string, exists bool) {\n\tv := m.claims_user_id\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUserID returns the old \"claims_user_id\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldClaimsUserID(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUserID is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUserID requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUserID: %w\", err)\n\t}\n\treturn oldValue.ClaimsUserID, nil\n}\n\n// ResetClaimsUserID resets all changes to the \"claims_user_id\" field.\nfunc (m *UserIdentityMutation) ResetClaimsUserID() {\n\tm.claims_user_id = nil\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (m *UserIdentityMutation) SetClaimsUsername(s string) {\n\tm.claims_username = &s\n}\n\n// ClaimsUsername returns the value of the \"claims_username\" field in the mutation.\nfunc (m *UserIdentityMutation) ClaimsUsername() (r string, exists bool) {\n\tv := m.claims_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsUsername returns the old \"claims_username\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldClaimsUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsUsername, nil\n}\n\n// ResetClaimsUsername resets all changes to the \"claims_username\" field.\nfunc (m *UserIdentityMutation) ResetClaimsUsername() {\n\tm.claims_username = nil\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (m *UserIdentityMutation) SetClaimsPreferredUsername(s string) {\n\tm.claims_preferred_username = &s\n}\n\n// ClaimsPreferredUsername returns the value of the \"claims_preferred_username\" field in the mutation.\nfunc (m *UserIdentityMutation) ClaimsPreferredUsername() (r string, exists bool) {\n\tv := m.claims_preferred_username\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsPreferredUsername returns the old \"claims_preferred_username\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldClaimsPreferredUsername(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsPreferredUsername requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsPreferredUsername: %w\", err)\n\t}\n\treturn oldValue.ClaimsPreferredUsername, nil\n}\n\n// ResetClaimsPreferredUsername resets all changes to the \"claims_preferred_username\" field.\nfunc (m *UserIdentityMutation) ResetClaimsPreferredUsername() {\n\tm.claims_preferred_username = nil\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (m *UserIdentityMutation) SetClaimsEmail(s string) {\n\tm.claims_email = &s\n}\n\n// ClaimsEmail returns the value of the \"claims_email\" field in the mutation.\nfunc (m *UserIdentityMutation) ClaimsEmail() (r string, exists bool) {\n\tv := m.claims_email\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmail returns the old \"claims_email\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldClaimsEmail(ctx context.Context) (v string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmail is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmail requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmail: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmail, nil\n}\n\n// ResetClaimsEmail resets all changes to the \"claims_email\" field.\nfunc (m *UserIdentityMutation) ResetClaimsEmail() {\n\tm.claims_email = nil\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (m *UserIdentityMutation) SetClaimsEmailVerified(b bool) {\n\tm.claims_email_verified = &b\n}\n\n// ClaimsEmailVerified returns the value of the \"claims_email_verified\" field in the mutation.\nfunc (m *UserIdentityMutation) ClaimsEmailVerified() (r bool, exists bool) {\n\tv := m.claims_email_verified\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsEmailVerified returns the old \"claims_email_verified\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldClaimsEmailVerified(ctx context.Context) (v bool, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsEmailVerified requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsEmailVerified: %w\", err)\n\t}\n\treturn oldValue.ClaimsEmailVerified, nil\n}\n\n// ResetClaimsEmailVerified resets all changes to the \"claims_email_verified\" field.\nfunc (m *UserIdentityMutation) ResetClaimsEmailVerified() {\n\tm.claims_email_verified = nil\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (m *UserIdentityMutation) SetClaimsGroups(s []string) {\n\tm.claims_groups = &s\n\tm.appendclaims_groups = nil\n}\n\n// ClaimsGroups returns the value of the \"claims_groups\" field in the mutation.\nfunc (m *UserIdentityMutation) ClaimsGroups() (r []string, exists bool) {\n\tv := m.claims_groups\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldClaimsGroups returns the old \"claims_groups\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldClaimsGroups(ctx context.Context) (v []string, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldClaimsGroups is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldClaimsGroups requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldClaimsGroups: %w\", err)\n\t}\n\treturn oldValue.ClaimsGroups, nil\n}\n\n// AppendClaimsGroups adds s to the \"claims_groups\" field.\nfunc (m *UserIdentityMutation) AppendClaimsGroups(s []string) {\n\tm.appendclaims_groups = append(m.appendclaims_groups, s...)\n}\n\n// AppendedClaimsGroups returns the list of values that were appended to the \"claims_groups\" field in this mutation.\nfunc (m *UserIdentityMutation) AppendedClaimsGroups() ([]string, bool) {\n\tif len(m.appendclaims_groups) == 0 {\n\t\treturn nil, false\n\t}\n\treturn m.appendclaims_groups, true\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (m *UserIdentityMutation) ClearClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tm.clearedFields[useridentity.FieldClaimsGroups] = struct{}{}\n}\n\n// ClaimsGroupsCleared returns if the \"claims_groups\" field was cleared in this mutation.\nfunc (m *UserIdentityMutation) ClaimsGroupsCleared() bool {\n\t_, ok := m.clearedFields[useridentity.FieldClaimsGroups]\n\treturn ok\n}\n\n// ResetClaimsGroups resets all changes to the \"claims_groups\" field.\nfunc (m *UserIdentityMutation) ResetClaimsGroups() {\n\tm.claims_groups = nil\n\tm.appendclaims_groups = nil\n\tdelete(m.clearedFields, useridentity.FieldClaimsGroups)\n}\n\n// SetConsents sets the \"consents\" field.\nfunc (m *UserIdentityMutation) SetConsents(b []byte) {\n\tm.consents = &b\n}\n\n// Consents returns the value of the \"consents\" field in the mutation.\nfunc (m *UserIdentityMutation) Consents() (r []byte, exists bool) {\n\tv := m.consents\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldConsents returns the old \"consents\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldConsents(ctx context.Context) (v []byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldConsents is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldConsents requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldConsents: %w\", err)\n\t}\n\treturn oldValue.Consents, nil\n}\n\n// ResetConsents resets all changes to the \"consents\" field.\nfunc (m *UserIdentityMutation) ResetConsents() {\n\tm.consents = nil\n}\n\n// SetMfaSecrets sets the \"mfa_secrets\" field.\nfunc (m *UserIdentityMutation) SetMfaSecrets(b []byte) {\n\tm.mfa_secrets = &b\n}\n\n// MfaSecrets returns the value of the \"mfa_secrets\" field in the mutation.\nfunc (m *UserIdentityMutation) MfaSecrets() (r []byte, exists bool) {\n\tv := m.mfa_secrets\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldMfaSecrets returns the old \"mfa_secrets\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldMfaSecrets(ctx context.Context) (v *[]byte, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldMfaSecrets is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldMfaSecrets requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldMfaSecrets: %w\", err)\n\t}\n\treturn oldValue.MfaSecrets, nil\n}\n\n// ClearMfaSecrets clears the value of the \"mfa_secrets\" field.\nfunc (m *UserIdentityMutation) ClearMfaSecrets() {\n\tm.mfa_secrets = nil\n\tm.clearedFields[useridentity.FieldMfaSecrets] = struct{}{}\n}\n\n// MfaSecretsCleared returns if the \"mfa_secrets\" field was cleared in this mutation.\nfunc (m *UserIdentityMutation) MfaSecretsCleared() bool {\n\t_, ok := m.clearedFields[useridentity.FieldMfaSecrets]\n\treturn ok\n}\n\n// ResetMfaSecrets resets all changes to the \"mfa_secrets\" field.\nfunc (m *UserIdentityMutation) ResetMfaSecrets() {\n\tm.mfa_secrets = nil\n\tdelete(m.clearedFields, useridentity.FieldMfaSecrets)\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (m *UserIdentityMutation) SetCreatedAt(t time.Time) {\n\tm.created_at = &t\n}\n\n// CreatedAt returns the value of the \"created_at\" field in the mutation.\nfunc (m *UserIdentityMutation) CreatedAt() (r time.Time, exists bool) {\n\tv := m.created_at\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldCreatedAt returns the old \"created_at\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldCreatedAt is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldCreatedAt requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldCreatedAt: %w\", err)\n\t}\n\treturn oldValue.CreatedAt, nil\n}\n\n// ResetCreatedAt resets all changes to the \"created_at\" field.\nfunc (m *UserIdentityMutation) ResetCreatedAt() {\n\tm.created_at = nil\n}\n\n// SetLastLogin sets the \"last_login\" field.\nfunc (m *UserIdentityMutation) SetLastLogin(t time.Time) {\n\tm.last_login = &t\n}\n\n// LastLogin returns the value of the \"last_login\" field in the mutation.\nfunc (m *UserIdentityMutation) LastLogin() (r time.Time, exists bool) {\n\tv := m.last_login\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldLastLogin returns the old \"last_login\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldLastLogin(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldLastLogin is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldLastLogin requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldLastLogin: %w\", err)\n\t}\n\treturn oldValue.LastLogin, nil\n}\n\n// ResetLastLogin resets all changes to the \"last_login\" field.\nfunc (m *UserIdentityMutation) ResetLastLogin() {\n\tm.last_login = nil\n}\n\n// SetBlockedUntil sets the \"blocked_until\" field.\nfunc (m *UserIdentityMutation) SetBlockedUntil(t time.Time) {\n\tm.blocked_until = &t\n}\n\n// BlockedUntil returns the value of the \"blocked_until\" field in the mutation.\nfunc (m *UserIdentityMutation) BlockedUntil() (r time.Time, exists bool) {\n\tv := m.blocked_until\n\tif v == nil {\n\t\treturn\n\t}\n\treturn *v, true\n}\n\n// OldBlockedUntil returns the old \"blocked_until\" field's value of the UserIdentity entity.\n// If the UserIdentity object wasn't provided to the builder, the object is fetched from the database.\n// An error is returned if the mutation operation is not UpdateOne, or the database query fails.\nfunc (m *UserIdentityMutation) OldBlockedUntil(ctx context.Context) (v time.Time, err error) {\n\tif !m.op.Is(OpUpdateOne) {\n\t\treturn v, errors.New(\"OldBlockedUntil is only allowed on UpdateOne operations\")\n\t}\n\tif m.id == nil || m.oldValue == nil {\n\t\treturn v, errors.New(\"OldBlockedUntil requires an ID field in the mutation\")\n\t}\n\toldValue, err := m.oldValue(ctx)\n\tif err != nil {\n\t\treturn v, fmt.Errorf(\"querying old value for OldBlockedUntil: %w\", err)\n\t}\n\treturn oldValue.BlockedUntil, nil\n}\n\n// ResetBlockedUntil resets all changes to the \"blocked_until\" field.\nfunc (m *UserIdentityMutation) ResetBlockedUntil() {\n\tm.blocked_until = nil\n}\n\n// Where appends a list predicates to the UserIdentityMutation builder.\nfunc (m *UserIdentityMutation) Where(ps ...predicate.UserIdentity) {\n\tm.predicates = append(m.predicates, ps...)\n}\n\n// WhereP appends storage-level predicates to the UserIdentityMutation builder. Using this method,\n// users can use type-assertion to append predicates that do not depend on any generated package.\nfunc (m *UserIdentityMutation) WhereP(ps ...func(*sql.Selector)) {\n\tp := make([]predicate.UserIdentity, len(ps))\n\tfor i := range ps {\n\t\tp[i] = ps[i]\n\t}\n\tm.Where(p...)\n}\n\n// Op returns the operation name.\nfunc (m *UserIdentityMutation) Op() Op {\n\treturn m.op\n}\n\n// SetOp allows setting the mutation operation.\nfunc (m *UserIdentityMutation) SetOp(op Op) {\n\tm.op = op\n}\n\n// Type returns the node type of this mutation (UserIdentity).\nfunc (m *UserIdentityMutation) Type() string {\n\treturn m.typ\n}\n\n// Fields returns all fields that were changed during this mutation. Note that in\n// order to get all numeric fields that were incremented/decremented, call\n// AddedFields().\nfunc (m *UserIdentityMutation) Fields() []string {\n\tfields := make([]string, 0, 13)\n\tif m.user_id != nil {\n\t\tfields = append(fields, useridentity.FieldUserID)\n\t}\n\tif m.connector_id != nil {\n\t\tfields = append(fields, useridentity.FieldConnectorID)\n\t}\n\tif m.claims_user_id != nil {\n\t\tfields = append(fields, useridentity.FieldClaimsUserID)\n\t}\n\tif m.claims_username != nil {\n\t\tfields = append(fields, useridentity.FieldClaimsUsername)\n\t}\n\tif m.claims_preferred_username != nil {\n\t\tfields = append(fields, useridentity.FieldClaimsPreferredUsername)\n\t}\n\tif m.claims_email != nil {\n\t\tfields = append(fields, useridentity.FieldClaimsEmail)\n\t}\n\tif m.claims_email_verified != nil {\n\t\tfields = append(fields, useridentity.FieldClaimsEmailVerified)\n\t}\n\tif m.claims_groups != nil {\n\t\tfields = append(fields, useridentity.FieldClaimsGroups)\n\t}\n\tif m.consents != nil {\n\t\tfields = append(fields, useridentity.FieldConsents)\n\t}\n\tif m.mfa_secrets != nil {\n\t\tfields = append(fields, useridentity.FieldMfaSecrets)\n\t}\n\tif m.created_at != nil {\n\t\tfields = append(fields, useridentity.FieldCreatedAt)\n\t}\n\tif m.last_login != nil {\n\t\tfields = append(fields, useridentity.FieldLastLogin)\n\t}\n\tif m.blocked_until != nil {\n\t\tfields = append(fields, useridentity.FieldBlockedUntil)\n\t}\n\treturn fields\n}\n\n// Field returns the value of a field with the given name. The second boolean\n// return value indicates that this field was not set, or was not defined in the\n// schema.\nfunc (m *UserIdentityMutation) Field(name string) (ent.Value, bool) {\n\tswitch name {\n\tcase useridentity.FieldUserID:\n\t\treturn m.UserID()\n\tcase useridentity.FieldConnectorID:\n\t\treturn m.ConnectorID()\n\tcase useridentity.FieldClaimsUserID:\n\t\treturn m.ClaimsUserID()\n\tcase useridentity.FieldClaimsUsername:\n\t\treturn m.ClaimsUsername()\n\tcase useridentity.FieldClaimsPreferredUsername:\n\t\treturn m.ClaimsPreferredUsername()\n\tcase useridentity.FieldClaimsEmail:\n\t\treturn m.ClaimsEmail()\n\tcase useridentity.FieldClaimsEmailVerified:\n\t\treturn m.ClaimsEmailVerified()\n\tcase useridentity.FieldClaimsGroups:\n\t\treturn m.ClaimsGroups()\n\tcase useridentity.FieldConsents:\n\t\treturn m.Consents()\n\tcase useridentity.FieldMfaSecrets:\n\t\treturn m.MfaSecrets()\n\tcase useridentity.FieldCreatedAt:\n\t\treturn m.CreatedAt()\n\tcase useridentity.FieldLastLogin:\n\t\treturn m.LastLogin()\n\tcase useridentity.FieldBlockedUntil:\n\t\treturn m.BlockedUntil()\n\t}\n\treturn nil, false\n}\n\n// OldField returns the old value of the field from the database. An error is\n// returned if the mutation operation is not UpdateOne, or the query to the\n// database failed.\nfunc (m *UserIdentityMutation) OldField(ctx context.Context, name string) (ent.Value, error) {\n\tswitch name {\n\tcase useridentity.FieldUserID:\n\t\treturn m.OldUserID(ctx)\n\tcase useridentity.FieldConnectorID:\n\t\treturn m.OldConnectorID(ctx)\n\tcase useridentity.FieldClaimsUserID:\n\t\treturn m.OldClaimsUserID(ctx)\n\tcase useridentity.FieldClaimsUsername:\n\t\treturn m.OldClaimsUsername(ctx)\n\tcase useridentity.FieldClaimsPreferredUsername:\n\t\treturn m.OldClaimsPreferredUsername(ctx)\n\tcase useridentity.FieldClaimsEmail:\n\t\treturn m.OldClaimsEmail(ctx)\n\tcase useridentity.FieldClaimsEmailVerified:\n\t\treturn m.OldClaimsEmailVerified(ctx)\n\tcase useridentity.FieldClaimsGroups:\n\t\treturn m.OldClaimsGroups(ctx)\n\tcase useridentity.FieldConsents:\n\t\treturn m.OldConsents(ctx)\n\tcase useridentity.FieldMfaSecrets:\n\t\treturn m.OldMfaSecrets(ctx)\n\tcase useridentity.FieldCreatedAt:\n\t\treturn m.OldCreatedAt(ctx)\n\tcase useridentity.FieldLastLogin:\n\t\treturn m.OldLastLogin(ctx)\n\tcase useridentity.FieldBlockedUntil:\n\t\treturn m.OldBlockedUntil(ctx)\n\t}\n\treturn nil, fmt.Errorf(\"unknown UserIdentity field %s\", name)\n}\n\n// SetField sets the value of a field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *UserIdentityMutation) SetField(name string, value ent.Value) error {\n\tswitch name {\n\tcase useridentity.FieldUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetUserID(v)\n\t\treturn nil\n\tcase useridentity.FieldConnectorID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConnectorID(v)\n\t\treturn nil\n\tcase useridentity.FieldClaimsUserID:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUserID(v)\n\t\treturn nil\n\tcase useridentity.FieldClaimsUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsUsername(v)\n\t\treturn nil\n\tcase useridentity.FieldClaimsPreferredUsername:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsPreferredUsername(v)\n\t\treturn nil\n\tcase useridentity.FieldClaimsEmail:\n\t\tv, ok := value.(string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmail(v)\n\t\treturn nil\n\tcase useridentity.FieldClaimsEmailVerified:\n\t\tv, ok := value.(bool)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsEmailVerified(v)\n\t\treturn nil\n\tcase useridentity.FieldClaimsGroups:\n\t\tv, ok := value.([]string)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetClaimsGroups(v)\n\t\treturn nil\n\tcase useridentity.FieldConsents:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetConsents(v)\n\t\treturn nil\n\tcase useridentity.FieldMfaSecrets:\n\t\tv, ok := value.([]byte)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetMfaSecrets(v)\n\t\treturn nil\n\tcase useridentity.FieldCreatedAt:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetCreatedAt(v)\n\t\treturn nil\n\tcase useridentity.FieldLastLogin:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetLastLogin(v)\n\t\treturn nil\n\tcase useridentity.FieldBlockedUntil:\n\t\tv, ok := value.(time.Time)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"unexpected type %T for field %s\", value, name)\n\t\t}\n\t\tm.SetBlockedUntil(v)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown UserIdentity field %s\", name)\n}\n\n// AddedFields returns all numeric fields that were incremented/decremented during\n// this mutation.\nfunc (m *UserIdentityMutation) AddedFields() []string {\n\treturn nil\n}\n\n// AddedField returns the numeric value that was incremented/decremented on a field\n// with the given name. The second boolean return value indicates that this field\n// was not set, or was not defined in the schema.\nfunc (m *UserIdentityMutation) AddedField(name string) (ent.Value, bool) {\n\treturn nil, false\n}\n\n// AddField adds the value to the field with the given name. It returns an error if\n// the field is not defined in the schema, or if the type mismatched the field\n// type.\nfunc (m *UserIdentityMutation) AddField(name string, value ent.Value) error {\n\tswitch name {\n\t}\n\treturn fmt.Errorf(\"unknown UserIdentity numeric field %s\", name)\n}\n\n// ClearedFields returns all nullable fields that were cleared during this\n// mutation.\nfunc (m *UserIdentityMutation) ClearedFields() []string {\n\tvar fields []string\n\tif m.FieldCleared(useridentity.FieldClaimsGroups) {\n\t\tfields = append(fields, useridentity.FieldClaimsGroups)\n\t}\n\tif m.FieldCleared(useridentity.FieldMfaSecrets) {\n\t\tfields = append(fields, useridentity.FieldMfaSecrets)\n\t}\n\treturn fields\n}\n\n// FieldCleared returns a boolean indicating if a field with the given name was\n// cleared in this mutation.\nfunc (m *UserIdentityMutation) FieldCleared(name string) bool {\n\t_, ok := m.clearedFields[name]\n\treturn ok\n}\n\n// ClearField clears the value of the field with the given name. It returns an\n// error if the field is not defined in the schema.\nfunc (m *UserIdentityMutation) ClearField(name string) error {\n\tswitch name {\n\tcase useridentity.FieldClaimsGroups:\n\t\tm.ClearClaimsGroups()\n\t\treturn nil\n\tcase useridentity.FieldMfaSecrets:\n\t\tm.ClearMfaSecrets()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown UserIdentity nullable field %s\", name)\n}\n\n// ResetField resets all changes in the mutation for the field with the given name.\n// It returns an error if the field is not defined in the schema.\nfunc (m *UserIdentityMutation) ResetField(name string) error {\n\tswitch name {\n\tcase useridentity.FieldUserID:\n\t\tm.ResetUserID()\n\t\treturn nil\n\tcase useridentity.FieldConnectorID:\n\t\tm.ResetConnectorID()\n\t\treturn nil\n\tcase useridentity.FieldClaimsUserID:\n\t\tm.ResetClaimsUserID()\n\t\treturn nil\n\tcase useridentity.FieldClaimsUsername:\n\t\tm.ResetClaimsUsername()\n\t\treturn nil\n\tcase useridentity.FieldClaimsPreferredUsername:\n\t\tm.ResetClaimsPreferredUsername()\n\t\treturn nil\n\tcase useridentity.FieldClaimsEmail:\n\t\tm.ResetClaimsEmail()\n\t\treturn nil\n\tcase useridentity.FieldClaimsEmailVerified:\n\t\tm.ResetClaimsEmailVerified()\n\t\treturn nil\n\tcase useridentity.FieldClaimsGroups:\n\t\tm.ResetClaimsGroups()\n\t\treturn nil\n\tcase useridentity.FieldConsents:\n\t\tm.ResetConsents()\n\t\treturn nil\n\tcase useridentity.FieldMfaSecrets:\n\t\tm.ResetMfaSecrets()\n\t\treturn nil\n\tcase useridentity.FieldCreatedAt:\n\t\tm.ResetCreatedAt()\n\t\treturn nil\n\tcase useridentity.FieldLastLogin:\n\t\tm.ResetLastLogin()\n\t\treturn nil\n\tcase useridentity.FieldBlockedUntil:\n\t\tm.ResetBlockedUntil()\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"unknown UserIdentity field %s\", name)\n}\n\n// AddedEdges returns all edge names that were set/added in this mutation.\nfunc (m *UserIdentityMutation) AddedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// AddedIDs returns all IDs (to other nodes) that were added for the given edge\n// name in this mutation.\nfunc (m *UserIdentityMutation) AddedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// RemovedEdges returns all edge names that were removed in this mutation.\nfunc (m *UserIdentityMutation) RemovedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with\n// the given name in this mutation.\nfunc (m *UserIdentityMutation) RemovedIDs(name string) []ent.Value {\n\treturn nil\n}\n\n// ClearedEdges returns all edge names that were cleared in this mutation.\nfunc (m *UserIdentityMutation) ClearedEdges() []string {\n\tedges := make([]string, 0, 0)\n\treturn edges\n}\n\n// EdgeCleared returns a boolean which indicates if the edge with the given name\n// was cleared in this mutation.\nfunc (m *UserIdentityMutation) EdgeCleared(name string) bool {\n\treturn false\n}\n\n// ClearEdge clears the value of the edge with the given name. It returns an error\n// if that edge is not defined in the schema.\nfunc (m *UserIdentityMutation) ClearEdge(name string) error {\n\treturn fmt.Errorf(\"unknown UserIdentity unique edge %s\", name)\n}\n\n// ResetEdge resets all changes to the edge with the given name in this mutation.\n// It returns an error if the edge is not defined in the schema.\nfunc (m *UserIdentityMutation) ResetEdge(name string) error {\n\treturn fmt.Errorf(\"unknown UserIdentity edge %s\", name)\n}\n"
  },
  {
    "path": "storage/ent/db/oauth2client/oauth2client.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage oauth2client\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the oauth2client type in the database.\n\tLabel = \"oauth2client\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldSecret holds the string denoting the secret field in the database.\n\tFieldSecret = \"secret\"\n\t// FieldRedirectUris holds the string denoting the redirect_uris field in the database.\n\tFieldRedirectUris = \"redirect_uris\"\n\t// FieldTrustedPeers holds the string denoting the trusted_peers field in the database.\n\tFieldTrustedPeers = \"trusted_peers\"\n\t// FieldPublic holds the string denoting the public field in the database.\n\tFieldPublic = \"public\"\n\t// FieldName holds the string denoting the name field in the database.\n\tFieldName = \"name\"\n\t// FieldLogoURL holds the string denoting the logo_url field in the database.\n\tFieldLogoURL = \"logo_url\"\n\t// FieldAllowedConnectors holds the string denoting the allowed_connectors field in the database.\n\tFieldAllowedConnectors = \"allowed_connectors\"\n\t// FieldMfaChain holds the string denoting the mfa_chain field in the database.\n\tFieldMfaChain = \"mfa_chain\"\n\t// Table holds the table name of the oauth2client in the database.\n\tTable = \"oauth2clients\"\n)\n\n// Columns holds all SQL columns for oauth2client fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldSecret,\n\tFieldRedirectUris,\n\tFieldTrustedPeers,\n\tFieldPublic,\n\tFieldName,\n\tFieldLogoURL,\n\tFieldAllowedConnectors,\n\tFieldMfaChain,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// SecretValidator is a validator for the \"secret\" field. It is called by the builders before save.\n\tSecretValidator func(string) error\n\t// NameValidator is a validator for the \"name\" field. It is called by the builders before save.\n\tNameValidator func(string) error\n\t// LogoURLValidator is a validator for the \"logo_url\" field. It is called by the builders before save.\n\tLogoURLValidator func(string) error\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the OAuth2Client queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// BySecret orders the results by the secret field.\nfunc BySecret(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldSecret, opts...).ToFunc()\n}\n\n// ByPublic orders the results by the public field.\nfunc ByPublic(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldPublic, opts...).ToFunc()\n}\n\n// ByName orders the results by the name field.\nfunc ByName(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldName, opts...).ToFunc()\n}\n\n// ByLogoURL orders the results by the logo_url field.\nfunc ByLogoURL(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldLogoURL, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/oauth2client/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage oauth2client\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldContainsFold(FieldID, id))\n}\n\n// Secret applies equality check predicate on the \"secret\" field. It's identical to SecretEQ.\nfunc Secret(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldSecret, v))\n}\n\n// Public applies equality check predicate on the \"public\" field. It's identical to PublicEQ.\nfunc Public(v bool) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldPublic, v))\n}\n\n// Name applies equality check predicate on the \"name\" field. It's identical to NameEQ.\nfunc Name(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldName, v))\n}\n\n// LogoURL applies equality check predicate on the \"logo_url\" field. It's identical to LogoURLEQ.\nfunc LogoURL(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldLogoURL, v))\n}\n\n// SecretEQ applies the EQ predicate on the \"secret\" field.\nfunc SecretEQ(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldSecret, v))\n}\n\n// SecretNEQ applies the NEQ predicate on the \"secret\" field.\nfunc SecretNEQ(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNEQ(FieldSecret, v))\n}\n\n// SecretIn applies the In predicate on the \"secret\" field.\nfunc SecretIn(vs ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIn(FieldSecret, vs...))\n}\n\n// SecretNotIn applies the NotIn predicate on the \"secret\" field.\nfunc SecretNotIn(vs ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotIn(FieldSecret, vs...))\n}\n\n// SecretGT applies the GT predicate on the \"secret\" field.\nfunc SecretGT(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGT(FieldSecret, v))\n}\n\n// SecretGTE applies the GTE predicate on the \"secret\" field.\nfunc SecretGTE(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGTE(FieldSecret, v))\n}\n\n// SecretLT applies the LT predicate on the \"secret\" field.\nfunc SecretLT(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLT(FieldSecret, v))\n}\n\n// SecretLTE applies the LTE predicate on the \"secret\" field.\nfunc SecretLTE(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLTE(FieldSecret, v))\n}\n\n// SecretContains applies the Contains predicate on the \"secret\" field.\nfunc SecretContains(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldContains(FieldSecret, v))\n}\n\n// SecretHasPrefix applies the HasPrefix predicate on the \"secret\" field.\nfunc SecretHasPrefix(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldHasPrefix(FieldSecret, v))\n}\n\n// SecretHasSuffix applies the HasSuffix predicate on the \"secret\" field.\nfunc SecretHasSuffix(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldHasSuffix(FieldSecret, v))\n}\n\n// SecretEqualFold applies the EqualFold predicate on the \"secret\" field.\nfunc SecretEqualFold(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEqualFold(FieldSecret, v))\n}\n\n// SecretContainsFold applies the ContainsFold predicate on the \"secret\" field.\nfunc SecretContainsFold(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldContainsFold(FieldSecret, v))\n}\n\n// RedirectUrisIsNil applies the IsNil predicate on the \"redirect_uris\" field.\nfunc RedirectUrisIsNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIsNull(FieldRedirectUris))\n}\n\n// RedirectUrisNotNil applies the NotNil predicate on the \"redirect_uris\" field.\nfunc RedirectUrisNotNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotNull(FieldRedirectUris))\n}\n\n// TrustedPeersIsNil applies the IsNil predicate on the \"trusted_peers\" field.\nfunc TrustedPeersIsNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIsNull(FieldTrustedPeers))\n}\n\n// TrustedPeersNotNil applies the NotNil predicate on the \"trusted_peers\" field.\nfunc TrustedPeersNotNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotNull(FieldTrustedPeers))\n}\n\n// PublicEQ applies the EQ predicate on the \"public\" field.\nfunc PublicEQ(v bool) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldPublic, v))\n}\n\n// PublicNEQ applies the NEQ predicate on the \"public\" field.\nfunc PublicNEQ(v bool) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNEQ(FieldPublic, v))\n}\n\n// NameEQ applies the EQ predicate on the \"name\" field.\nfunc NameEQ(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldName, v))\n}\n\n// NameNEQ applies the NEQ predicate on the \"name\" field.\nfunc NameNEQ(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNEQ(FieldName, v))\n}\n\n// NameIn applies the In predicate on the \"name\" field.\nfunc NameIn(vs ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIn(FieldName, vs...))\n}\n\n// NameNotIn applies the NotIn predicate on the \"name\" field.\nfunc NameNotIn(vs ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotIn(FieldName, vs...))\n}\n\n// NameGT applies the GT predicate on the \"name\" field.\nfunc NameGT(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGT(FieldName, v))\n}\n\n// NameGTE applies the GTE predicate on the \"name\" field.\nfunc NameGTE(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGTE(FieldName, v))\n}\n\n// NameLT applies the LT predicate on the \"name\" field.\nfunc NameLT(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLT(FieldName, v))\n}\n\n// NameLTE applies the LTE predicate on the \"name\" field.\nfunc NameLTE(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLTE(FieldName, v))\n}\n\n// NameContains applies the Contains predicate on the \"name\" field.\nfunc NameContains(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldContains(FieldName, v))\n}\n\n// NameHasPrefix applies the HasPrefix predicate on the \"name\" field.\nfunc NameHasPrefix(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldHasPrefix(FieldName, v))\n}\n\n// NameHasSuffix applies the HasSuffix predicate on the \"name\" field.\nfunc NameHasSuffix(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldHasSuffix(FieldName, v))\n}\n\n// NameEqualFold applies the EqualFold predicate on the \"name\" field.\nfunc NameEqualFold(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEqualFold(FieldName, v))\n}\n\n// NameContainsFold applies the ContainsFold predicate on the \"name\" field.\nfunc NameContainsFold(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldContainsFold(FieldName, v))\n}\n\n// LogoURLEQ applies the EQ predicate on the \"logo_url\" field.\nfunc LogoURLEQ(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEQ(FieldLogoURL, v))\n}\n\n// LogoURLNEQ applies the NEQ predicate on the \"logo_url\" field.\nfunc LogoURLNEQ(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNEQ(FieldLogoURL, v))\n}\n\n// LogoURLIn applies the In predicate on the \"logo_url\" field.\nfunc LogoURLIn(vs ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIn(FieldLogoURL, vs...))\n}\n\n// LogoURLNotIn applies the NotIn predicate on the \"logo_url\" field.\nfunc LogoURLNotIn(vs ...string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotIn(FieldLogoURL, vs...))\n}\n\n// LogoURLGT applies the GT predicate on the \"logo_url\" field.\nfunc LogoURLGT(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGT(FieldLogoURL, v))\n}\n\n// LogoURLGTE applies the GTE predicate on the \"logo_url\" field.\nfunc LogoURLGTE(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldGTE(FieldLogoURL, v))\n}\n\n// LogoURLLT applies the LT predicate on the \"logo_url\" field.\nfunc LogoURLLT(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLT(FieldLogoURL, v))\n}\n\n// LogoURLLTE applies the LTE predicate on the \"logo_url\" field.\nfunc LogoURLLTE(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldLTE(FieldLogoURL, v))\n}\n\n// LogoURLContains applies the Contains predicate on the \"logo_url\" field.\nfunc LogoURLContains(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldContains(FieldLogoURL, v))\n}\n\n// LogoURLHasPrefix applies the HasPrefix predicate on the \"logo_url\" field.\nfunc LogoURLHasPrefix(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldHasPrefix(FieldLogoURL, v))\n}\n\n// LogoURLHasSuffix applies the HasSuffix predicate on the \"logo_url\" field.\nfunc LogoURLHasSuffix(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldHasSuffix(FieldLogoURL, v))\n}\n\n// LogoURLEqualFold applies the EqualFold predicate on the \"logo_url\" field.\nfunc LogoURLEqualFold(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldEqualFold(FieldLogoURL, v))\n}\n\n// LogoURLContainsFold applies the ContainsFold predicate on the \"logo_url\" field.\nfunc LogoURLContainsFold(v string) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldContainsFold(FieldLogoURL, v))\n}\n\n// AllowedConnectorsIsNil applies the IsNil predicate on the \"allowed_connectors\" field.\nfunc AllowedConnectorsIsNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIsNull(FieldAllowedConnectors))\n}\n\n// AllowedConnectorsNotNil applies the NotNil predicate on the \"allowed_connectors\" field.\nfunc AllowedConnectorsNotNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotNull(FieldAllowedConnectors))\n}\n\n// MfaChainIsNil applies the IsNil predicate on the \"mfa_chain\" field.\nfunc MfaChainIsNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldIsNull(FieldMfaChain))\n}\n\n// MfaChainNotNil applies the NotNil predicate on the \"mfa_chain\" field.\nfunc MfaChainNotNil() predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.FieldNotNull(FieldMfaChain))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.OAuth2Client) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.OAuth2Client) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.OAuth2Client) predicate.OAuth2Client {\n\treturn predicate.OAuth2Client(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/oauth2client.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n)\n\n// OAuth2Client is the model entity for the OAuth2Client schema.\ntype OAuth2Client struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// Secret holds the value of the \"secret\" field.\n\tSecret string `json:\"secret,omitempty\"`\n\t// RedirectUris holds the value of the \"redirect_uris\" field.\n\tRedirectUris []string `json:\"redirect_uris,omitempty\"`\n\t// TrustedPeers holds the value of the \"trusted_peers\" field.\n\tTrustedPeers []string `json:\"trusted_peers,omitempty\"`\n\t// Public holds the value of the \"public\" field.\n\tPublic bool `json:\"public,omitempty\"`\n\t// Name holds the value of the \"name\" field.\n\tName string `json:\"name,omitempty\"`\n\t// LogoURL holds the value of the \"logo_url\" field.\n\tLogoURL string `json:\"logo_url,omitempty\"`\n\t// AllowedConnectors holds the value of the \"allowed_connectors\" field.\n\tAllowedConnectors []string `json:\"allowed_connectors,omitempty\"`\n\t// MfaChain holds the value of the \"mfa_chain\" field.\n\tMfaChain     []string `json:\"mfa_chain,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*OAuth2Client) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase oauth2client.FieldRedirectUris, oauth2client.FieldTrustedPeers, oauth2client.FieldAllowedConnectors, oauth2client.FieldMfaChain:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase oauth2client.FieldPublic:\n\t\t\tvalues[i] = new(sql.NullBool)\n\t\tcase oauth2client.FieldID, oauth2client.FieldSecret, oauth2client.FieldName, oauth2client.FieldLogoURL:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the OAuth2Client fields.\nfunc (_m *OAuth2Client) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase oauth2client.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase oauth2client.FieldSecret:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field secret\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Secret = value.String\n\t\t\t}\n\t\tcase oauth2client.FieldRedirectUris:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field redirect_uris\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.RedirectUris); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field redirect_uris: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase oauth2client.FieldTrustedPeers:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field trusted_peers\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.TrustedPeers); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field trusted_peers: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase oauth2client.FieldPublic:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field public\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Public = value.Bool\n\t\t\t}\n\t\tcase oauth2client.FieldName:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field name\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Name = value.String\n\t\t\t}\n\t\tcase oauth2client.FieldLogoURL:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field logo_url\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.LogoURL = value.String\n\t\t\t}\n\t\tcase oauth2client.FieldAllowedConnectors:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field allowed_connectors\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.AllowedConnectors); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field allowed_connectors: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase oauth2client.FieldMfaChain:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field mfa_chain\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.MfaChain); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field mfa_chain: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the OAuth2Client.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *OAuth2Client) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this OAuth2Client.\n// Note that you need to call OAuth2Client.Unwrap() before calling this method if this OAuth2Client\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *OAuth2Client) Update() *OAuth2ClientUpdateOne {\n\treturn NewOAuth2ClientClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the OAuth2Client entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *OAuth2Client) Unwrap() *OAuth2Client {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: OAuth2Client is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *OAuth2Client) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"OAuth2Client(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"secret=\")\n\tbuilder.WriteString(_m.Secret)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"redirect_uris=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.RedirectUris))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"trusted_peers=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.TrustedPeers))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"public=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Public))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"name=\")\n\tbuilder.WriteString(_m.Name)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"logo_url=\")\n\tbuilder.WriteString(_m.LogoURL)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"allowed_connectors=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.AllowedConnectors))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"mfa_chain=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.MfaChain))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// OAuth2Clients is a parsable slice of OAuth2Client.\ntype OAuth2Clients []*OAuth2Client\n"
  },
  {
    "path": "storage/ent/db/oauth2client_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n)\n\n// OAuth2ClientCreate is the builder for creating a OAuth2Client entity.\ntype OAuth2ClientCreate struct {\n\tconfig\n\tmutation *OAuth2ClientMutation\n\thooks    []Hook\n}\n\n// SetSecret sets the \"secret\" field.\nfunc (_c *OAuth2ClientCreate) SetSecret(v string) *OAuth2ClientCreate {\n\t_c.mutation.SetSecret(v)\n\treturn _c\n}\n\n// SetRedirectUris sets the \"redirect_uris\" field.\nfunc (_c *OAuth2ClientCreate) SetRedirectUris(v []string) *OAuth2ClientCreate {\n\t_c.mutation.SetRedirectUris(v)\n\treturn _c\n}\n\n// SetTrustedPeers sets the \"trusted_peers\" field.\nfunc (_c *OAuth2ClientCreate) SetTrustedPeers(v []string) *OAuth2ClientCreate {\n\t_c.mutation.SetTrustedPeers(v)\n\treturn _c\n}\n\n// SetPublic sets the \"public\" field.\nfunc (_c *OAuth2ClientCreate) SetPublic(v bool) *OAuth2ClientCreate {\n\t_c.mutation.SetPublic(v)\n\treturn _c\n}\n\n// SetName sets the \"name\" field.\nfunc (_c *OAuth2ClientCreate) SetName(v string) *OAuth2ClientCreate {\n\t_c.mutation.SetName(v)\n\treturn _c\n}\n\n// SetLogoURL sets the \"logo_url\" field.\nfunc (_c *OAuth2ClientCreate) SetLogoURL(v string) *OAuth2ClientCreate {\n\t_c.mutation.SetLogoURL(v)\n\treturn _c\n}\n\n// SetAllowedConnectors sets the \"allowed_connectors\" field.\nfunc (_c *OAuth2ClientCreate) SetAllowedConnectors(v []string) *OAuth2ClientCreate {\n\t_c.mutation.SetAllowedConnectors(v)\n\treturn _c\n}\n\n// SetMfaChain sets the \"mfa_chain\" field.\nfunc (_c *OAuth2ClientCreate) SetMfaChain(v []string) *OAuth2ClientCreate {\n\t_c.mutation.SetMfaChain(v)\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *OAuth2ClientCreate) SetID(v string) *OAuth2ClientCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the OAuth2ClientMutation object of the builder.\nfunc (_c *OAuth2ClientCreate) Mutation() *OAuth2ClientMutation {\n\treturn _c.mutation\n}\n\n// Save creates the OAuth2Client in the database.\nfunc (_c *OAuth2ClientCreate) Save(ctx context.Context) (*OAuth2Client, error) {\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *OAuth2ClientCreate) SaveX(ctx context.Context) *OAuth2Client {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *OAuth2ClientCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *OAuth2ClientCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *OAuth2ClientCreate) check() error {\n\tif _, ok := _c.mutation.Secret(); !ok {\n\t\treturn &ValidationError{Name: \"secret\", err: errors.New(`db: missing required field \"OAuth2Client.secret\"`)}\n\t}\n\tif v, ok := _c.mutation.Secret(); ok {\n\t\tif err := oauth2client.SecretValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"secret\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.secret\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Public(); !ok {\n\t\treturn &ValidationError{Name: \"public\", err: errors.New(`db: missing required field \"OAuth2Client.public\"`)}\n\t}\n\tif _, ok := _c.mutation.Name(); !ok {\n\t\treturn &ValidationError{Name: \"name\", err: errors.New(`db: missing required field \"OAuth2Client.name\"`)}\n\t}\n\tif v, ok := _c.mutation.Name(); ok {\n\t\tif err := oauth2client.NameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"name\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.name\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.LogoURL(); !ok {\n\t\treturn &ValidationError{Name: \"logo_url\", err: errors.New(`db: missing required field \"OAuth2Client.logo_url\"`)}\n\t}\n\tif v, ok := _c.mutation.LogoURL(); ok {\n\t\tif err := oauth2client.LogoURLValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"logo_url\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.logo_url\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := oauth2client.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *OAuth2ClientCreate) sqlSave(ctx context.Context) (*OAuth2Client, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected OAuth2Client.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *OAuth2ClientCreate) createSpec() (*OAuth2Client, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &OAuth2Client{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(oauth2client.Table, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.Secret(); ok {\n\t\t_spec.SetField(oauth2client.FieldSecret, field.TypeString, value)\n\t\t_node.Secret = value\n\t}\n\tif value, ok := _c.mutation.RedirectUris(); ok {\n\t\t_spec.SetField(oauth2client.FieldRedirectUris, field.TypeJSON, value)\n\t\t_node.RedirectUris = value\n\t}\n\tif value, ok := _c.mutation.TrustedPeers(); ok {\n\t\t_spec.SetField(oauth2client.FieldTrustedPeers, field.TypeJSON, value)\n\t\t_node.TrustedPeers = value\n\t}\n\tif value, ok := _c.mutation.Public(); ok {\n\t\t_spec.SetField(oauth2client.FieldPublic, field.TypeBool, value)\n\t\t_node.Public = value\n\t}\n\tif value, ok := _c.mutation.Name(); ok {\n\t\t_spec.SetField(oauth2client.FieldName, field.TypeString, value)\n\t\t_node.Name = value\n\t}\n\tif value, ok := _c.mutation.LogoURL(); ok {\n\t\t_spec.SetField(oauth2client.FieldLogoURL, field.TypeString, value)\n\t\t_node.LogoURL = value\n\t}\n\tif value, ok := _c.mutation.AllowedConnectors(); ok {\n\t\t_spec.SetField(oauth2client.FieldAllowedConnectors, field.TypeJSON, value)\n\t\t_node.AllowedConnectors = value\n\t}\n\tif value, ok := _c.mutation.MfaChain(); ok {\n\t\t_spec.SetField(oauth2client.FieldMfaChain, field.TypeJSON, value)\n\t\t_node.MfaChain = value\n\t}\n\treturn _node, _spec\n}\n\n// OAuth2ClientCreateBulk is the builder for creating many OAuth2Client entities in bulk.\ntype OAuth2ClientCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*OAuth2ClientCreate\n}\n\n// Save creates the OAuth2Client entities in the database.\nfunc (_c *OAuth2ClientCreateBulk) Save(ctx context.Context) ([]*OAuth2Client, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*OAuth2Client, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*OAuth2ClientMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *OAuth2ClientCreateBulk) SaveX(ctx context.Context) []*OAuth2Client {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *OAuth2ClientCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *OAuth2ClientCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/oauth2client_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// OAuth2ClientDelete is the builder for deleting a OAuth2Client entity.\ntype OAuth2ClientDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *OAuth2ClientMutation\n}\n\n// Where appends a list predicates to the OAuth2ClientDelete builder.\nfunc (_d *OAuth2ClientDelete) Where(ps ...predicate.OAuth2Client) *OAuth2ClientDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *OAuth2ClientDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *OAuth2ClientDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *OAuth2ClientDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(oauth2client.Table, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// OAuth2ClientDeleteOne is the builder for deleting a single OAuth2Client entity.\ntype OAuth2ClientDeleteOne struct {\n\t_d *OAuth2ClientDelete\n}\n\n// Where appends a list predicates to the OAuth2ClientDelete builder.\nfunc (_d *OAuth2ClientDeleteOne) Where(ps ...predicate.OAuth2Client) *OAuth2ClientDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *OAuth2ClientDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{oauth2client.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *OAuth2ClientDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/oauth2client_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// OAuth2ClientQuery is the builder for querying OAuth2Client entities.\ntype OAuth2ClientQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []oauth2client.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.OAuth2Client\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the OAuth2ClientQuery builder.\nfunc (_q *OAuth2ClientQuery) Where(ps ...predicate.OAuth2Client) *OAuth2ClientQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *OAuth2ClientQuery) Limit(limit int) *OAuth2ClientQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *OAuth2ClientQuery) Offset(offset int) *OAuth2ClientQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *OAuth2ClientQuery) Unique(unique bool) *OAuth2ClientQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *OAuth2ClientQuery) Order(o ...oauth2client.OrderOption) *OAuth2ClientQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first OAuth2Client entity from the query.\n// Returns a *NotFoundError when no OAuth2Client was found.\nfunc (_q *OAuth2ClientQuery) First(ctx context.Context) (*OAuth2Client, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{oauth2client.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) FirstX(ctx context.Context) *OAuth2Client {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first OAuth2Client ID from the query.\n// Returns a *NotFoundError when no OAuth2Client ID was found.\nfunc (_q *OAuth2ClientQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{oauth2client.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single OAuth2Client entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one OAuth2Client entity is found.\n// Returns a *NotFoundError when no OAuth2Client entities are found.\nfunc (_q *OAuth2ClientQuery) Only(ctx context.Context) (*OAuth2Client, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{oauth2client.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{oauth2client.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) OnlyX(ctx context.Context) *OAuth2Client {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only OAuth2Client ID in the query.\n// Returns a *NotSingularError when more than one OAuth2Client ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *OAuth2ClientQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{oauth2client.Label}\n\tdefault:\n\t\terr = &NotSingularError{oauth2client.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of OAuth2Clients.\nfunc (_q *OAuth2ClientQuery) All(ctx context.Context) ([]*OAuth2Client, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*OAuth2Client, *OAuth2ClientQuery]()\n\treturn withInterceptors[[]*OAuth2Client](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) AllX(ctx context.Context) []*OAuth2Client {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of OAuth2Client IDs.\nfunc (_q *OAuth2ClientQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(oauth2client.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *OAuth2ClientQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*OAuth2ClientQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *OAuth2ClientQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *OAuth2ClientQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the OAuth2ClientQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *OAuth2ClientQuery) Clone() *OAuth2ClientQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &OAuth2ClientQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]oauth2client.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.OAuth2Client{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tSecret string `json:\"secret,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.OAuth2Client.Query().\n//\t\tGroupBy(oauth2client.FieldSecret).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *OAuth2ClientQuery) GroupBy(field string, fields ...string) *OAuth2ClientGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &OAuth2ClientGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = oauth2client.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tSecret string `json:\"secret,omitempty\"`\n//\t}\n//\n//\tclient.OAuth2Client.Query().\n//\t\tSelect(oauth2client.FieldSecret).\n//\t\tScan(ctx, &v)\nfunc (_q *OAuth2ClientQuery) Select(fields ...string) *OAuth2ClientSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &OAuth2ClientSelect{OAuth2ClientQuery: _q}\n\tsbuild.label = oauth2client.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a OAuth2ClientSelect configured with the given aggregations.\nfunc (_q *OAuth2ClientQuery) Aggregate(fns ...AggregateFunc) *OAuth2ClientSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *OAuth2ClientQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !oauth2client.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *OAuth2ClientQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*OAuth2Client, error) {\n\tvar (\n\t\tnodes = []*OAuth2Client{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*OAuth2Client).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &OAuth2Client{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *OAuth2ClientQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *OAuth2ClientQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(oauth2client.Table, oauth2client.Columns, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, oauth2client.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != oauth2client.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *OAuth2ClientQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(oauth2client.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = oauth2client.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// OAuth2ClientGroupBy is the group-by builder for OAuth2Client entities.\ntype OAuth2ClientGroupBy struct {\n\tselector\n\tbuild *OAuth2ClientQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *OAuth2ClientGroupBy) Aggregate(fns ...AggregateFunc) *OAuth2ClientGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *OAuth2ClientGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*OAuth2ClientQuery, *OAuth2ClientGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *OAuth2ClientGroupBy) sqlScan(ctx context.Context, root *OAuth2ClientQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// OAuth2ClientSelect is the builder for selecting fields of OAuth2Client entities.\ntype OAuth2ClientSelect struct {\n\t*OAuth2ClientQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *OAuth2ClientSelect) Aggregate(fns ...AggregateFunc) *OAuth2ClientSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *OAuth2ClientSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*OAuth2ClientQuery, *OAuth2ClientSelect](ctx, _s.OAuth2ClientQuery, _s, _s.inters, v)\n}\n\nfunc (_s *OAuth2ClientSelect) sqlScan(ctx context.Context, root *OAuth2ClientQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/oauth2client_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// OAuth2ClientUpdate is the builder for updating OAuth2Client entities.\ntype OAuth2ClientUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *OAuth2ClientMutation\n}\n\n// Where appends a list predicates to the OAuth2ClientUpdate builder.\nfunc (_u *OAuth2ClientUpdate) Where(ps ...predicate.OAuth2Client) *OAuth2ClientUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetSecret sets the \"secret\" field.\nfunc (_u *OAuth2ClientUpdate) SetSecret(v string) *OAuth2ClientUpdate {\n\t_u.mutation.SetSecret(v)\n\treturn _u\n}\n\n// SetNillableSecret sets the \"secret\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdate) SetNillableSecret(v *string) *OAuth2ClientUpdate {\n\tif v != nil {\n\t\t_u.SetSecret(*v)\n\t}\n\treturn _u\n}\n\n// SetRedirectUris sets the \"redirect_uris\" field.\nfunc (_u *OAuth2ClientUpdate) SetRedirectUris(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.SetRedirectUris(v)\n\treturn _u\n}\n\n// AppendRedirectUris appends value to the \"redirect_uris\" field.\nfunc (_u *OAuth2ClientUpdate) AppendRedirectUris(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.AppendRedirectUris(v)\n\treturn _u\n}\n\n// ClearRedirectUris clears the value of the \"redirect_uris\" field.\nfunc (_u *OAuth2ClientUpdate) ClearRedirectUris() *OAuth2ClientUpdate {\n\t_u.mutation.ClearRedirectUris()\n\treturn _u\n}\n\n// SetTrustedPeers sets the \"trusted_peers\" field.\nfunc (_u *OAuth2ClientUpdate) SetTrustedPeers(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.SetTrustedPeers(v)\n\treturn _u\n}\n\n// AppendTrustedPeers appends value to the \"trusted_peers\" field.\nfunc (_u *OAuth2ClientUpdate) AppendTrustedPeers(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.AppendTrustedPeers(v)\n\treturn _u\n}\n\n// ClearTrustedPeers clears the value of the \"trusted_peers\" field.\nfunc (_u *OAuth2ClientUpdate) ClearTrustedPeers() *OAuth2ClientUpdate {\n\t_u.mutation.ClearTrustedPeers()\n\treturn _u\n}\n\n// SetPublic sets the \"public\" field.\nfunc (_u *OAuth2ClientUpdate) SetPublic(v bool) *OAuth2ClientUpdate {\n\t_u.mutation.SetPublic(v)\n\treturn _u\n}\n\n// SetNillablePublic sets the \"public\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdate) SetNillablePublic(v *bool) *OAuth2ClientUpdate {\n\tif v != nil {\n\t\t_u.SetPublic(*v)\n\t}\n\treturn _u\n}\n\n// SetName sets the \"name\" field.\nfunc (_u *OAuth2ClientUpdate) SetName(v string) *OAuth2ClientUpdate {\n\t_u.mutation.SetName(v)\n\treturn _u\n}\n\n// SetNillableName sets the \"name\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdate) SetNillableName(v *string) *OAuth2ClientUpdate {\n\tif v != nil {\n\t\t_u.SetName(*v)\n\t}\n\treturn _u\n}\n\n// SetLogoURL sets the \"logo_url\" field.\nfunc (_u *OAuth2ClientUpdate) SetLogoURL(v string) *OAuth2ClientUpdate {\n\t_u.mutation.SetLogoURL(v)\n\treturn _u\n}\n\n// SetNillableLogoURL sets the \"logo_url\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdate) SetNillableLogoURL(v *string) *OAuth2ClientUpdate {\n\tif v != nil {\n\t\t_u.SetLogoURL(*v)\n\t}\n\treturn _u\n}\n\n// SetAllowedConnectors sets the \"allowed_connectors\" field.\nfunc (_u *OAuth2ClientUpdate) SetAllowedConnectors(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.SetAllowedConnectors(v)\n\treturn _u\n}\n\n// AppendAllowedConnectors appends value to the \"allowed_connectors\" field.\nfunc (_u *OAuth2ClientUpdate) AppendAllowedConnectors(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.AppendAllowedConnectors(v)\n\treturn _u\n}\n\n// ClearAllowedConnectors clears the value of the \"allowed_connectors\" field.\nfunc (_u *OAuth2ClientUpdate) ClearAllowedConnectors() *OAuth2ClientUpdate {\n\t_u.mutation.ClearAllowedConnectors()\n\treturn _u\n}\n\n// SetMfaChain sets the \"mfa_chain\" field.\nfunc (_u *OAuth2ClientUpdate) SetMfaChain(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.SetMfaChain(v)\n\treturn _u\n}\n\n// AppendMfaChain appends value to the \"mfa_chain\" field.\nfunc (_u *OAuth2ClientUpdate) AppendMfaChain(v []string) *OAuth2ClientUpdate {\n\t_u.mutation.AppendMfaChain(v)\n\treturn _u\n}\n\n// ClearMfaChain clears the value of the \"mfa_chain\" field.\nfunc (_u *OAuth2ClientUpdate) ClearMfaChain() *OAuth2ClientUpdate {\n\t_u.mutation.ClearMfaChain()\n\treturn _u\n}\n\n// Mutation returns the OAuth2ClientMutation object of the builder.\nfunc (_u *OAuth2ClientUpdate) Mutation() *OAuth2ClientMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *OAuth2ClientUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *OAuth2ClientUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *OAuth2ClientUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *OAuth2ClientUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *OAuth2ClientUpdate) check() error {\n\tif v, ok := _u.mutation.Secret(); ok {\n\t\tif err := oauth2client.SecretValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"secret\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.secret\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Name(); ok {\n\t\tif err := oauth2client.NameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"name\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.name\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.LogoURL(); ok {\n\t\tif err := oauth2client.LogoURLValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"logo_url\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.logo_url\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *OAuth2ClientUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(oauth2client.Table, oauth2client.Columns, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.Secret(); ok {\n\t\t_spec.SetField(oauth2client.FieldSecret, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.RedirectUris(); ok {\n\t\t_spec.SetField(oauth2client.FieldRedirectUris, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedRedirectUris(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldRedirectUris, value)\n\t\t})\n\t}\n\tif _u.mutation.RedirectUrisCleared() {\n\t\t_spec.ClearField(oauth2client.FieldRedirectUris, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.TrustedPeers(); ok {\n\t\t_spec.SetField(oauth2client.FieldTrustedPeers, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedTrustedPeers(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldTrustedPeers, value)\n\t\t})\n\t}\n\tif _u.mutation.TrustedPeersCleared() {\n\t\t_spec.ClearField(oauth2client.FieldTrustedPeers, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Public(); ok {\n\t\t_spec.SetField(oauth2client.FieldPublic, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.Name(); ok {\n\t\t_spec.SetField(oauth2client.FieldName, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.LogoURL(); ok {\n\t\t_spec.SetField(oauth2client.FieldLogoURL, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.AllowedConnectors(); ok {\n\t\t_spec.SetField(oauth2client.FieldAllowedConnectors, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedAllowedConnectors(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldAllowedConnectors, value)\n\t\t})\n\t}\n\tif _u.mutation.AllowedConnectorsCleared() {\n\t\t_spec.ClearField(oauth2client.FieldAllowedConnectors, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.MfaChain(); ok {\n\t\t_spec.SetField(oauth2client.FieldMfaChain, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedMfaChain(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldMfaChain, value)\n\t\t})\n\t}\n\tif _u.mutation.MfaChainCleared() {\n\t\t_spec.ClearField(oauth2client.FieldMfaChain, field.TypeJSON)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{oauth2client.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// OAuth2ClientUpdateOne is the builder for updating a single OAuth2Client entity.\ntype OAuth2ClientUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *OAuth2ClientMutation\n}\n\n// SetSecret sets the \"secret\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetSecret(v string) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetSecret(v)\n\treturn _u\n}\n\n// SetNillableSecret sets the \"secret\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdateOne) SetNillableSecret(v *string) *OAuth2ClientUpdateOne {\n\tif v != nil {\n\t\t_u.SetSecret(*v)\n\t}\n\treturn _u\n}\n\n// SetRedirectUris sets the \"redirect_uris\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetRedirectUris(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetRedirectUris(v)\n\treturn _u\n}\n\n// AppendRedirectUris appends value to the \"redirect_uris\" field.\nfunc (_u *OAuth2ClientUpdateOne) AppendRedirectUris(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.AppendRedirectUris(v)\n\treturn _u\n}\n\n// ClearRedirectUris clears the value of the \"redirect_uris\" field.\nfunc (_u *OAuth2ClientUpdateOne) ClearRedirectUris() *OAuth2ClientUpdateOne {\n\t_u.mutation.ClearRedirectUris()\n\treturn _u\n}\n\n// SetTrustedPeers sets the \"trusted_peers\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetTrustedPeers(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetTrustedPeers(v)\n\treturn _u\n}\n\n// AppendTrustedPeers appends value to the \"trusted_peers\" field.\nfunc (_u *OAuth2ClientUpdateOne) AppendTrustedPeers(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.AppendTrustedPeers(v)\n\treturn _u\n}\n\n// ClearTrustedPeers clears the value of the \"trusted_peers\" field.\nfunc (_u *OAuth2ClientUpdateOne) ClearTrustedPeers() *OAuth2ClientUpdateOne {\n\t_u.mutation.ClearTrustedPeers()\n\treturn _u\n}\n\n// SetPublic sets the \"public\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetPublic(v bool) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetPublic(v)\n\treturn _u\n}\n\n// SetNillablePublic sets the \"public\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdateOne) SetNillablePublic(v *bool) *OAuth2ClientUpdateOne {\n\tif v != nil {\n\t\t_u.SetPublic(*v)\n\t}\n\treturn _u\n}\n\n// SetName sets the \"name\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetName(v string) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetName(v)\n\treturn _u\n}\n\n// SetNillableName sets the \"name\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdateOne) SetNillableName(v *string) *OAuth2ClientUpdateOne {\n\tif v != nil {\n\t\t_u.SetName(*v)\n\t}\n\treturn _u\n}\n\n// SetLogoURL sets the \"logo_url\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetLogoURL(v string) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetLogoURL(v)\n\treturn _u\n}\n\n// SetNillableLogoURL sets the \"logo_url\" field if the given value is not nil.\nfunc (_u *OAuth2ClientUpdateOne) SetNillableLogoURL(v *string) *OAuth2ClientUpdateOne {\n\tif v != nil {\n\t\t_u.SetLogoURL(*v)\n\t}\n\treturn _u\n}\n\n// SetAllowedConnectors sets the \"allowed_connectors\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetAllowedConnectors(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetAllowedConnectors(v)\n\treturn _u\n}\n\n// AppendAllowedConnectors appends value to the \"allowed_connectors\" field.\nfunc (_u *OAuth2ClientUpdateOne) AppendAllowedConnectors(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.AppendAllowedConnectors(v)\n\treturn _u\n}\n\n// ClearAllowedConnectors clears the value of the \"allowed_connectors\" field.\nfunc (_u *OAuth2ClientUpdateOne) ClearAllowedConnectors() *OAuth2ClientUpdateOne {\n\t_u.mutation.ClearAllowedConnectors()\n\treturn _u\n}\n\n// SetMfaChain sets the \"mfa_chain\" field.\nfunc (_u *OAuth2ClientUpdateOne) SetMfaChain(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.SetMfaChain(v)\n\treturn _u\n}\n\n// AppendMfaChain appends value to the \"mfa_chain\" field.\nfunc (_u *OAuth2ClientUpdateOne) AppendMfaChain(v []string) *OAuth2ClientUpdateOne {\n\t_u.mutation.AppendMfaChain(v)\n\treturn _u\n}\n\n// ClearMfaChain clears the value of the \"mfa_chain\" field.\nfunc (_u *OAuth2ClientUpdateOne) ClearMfaChain() *OAuth2ClientUpdateOne {\n\t_u.mutation.ClearMfaChain()\n\treturn _u\n}\n\n// Mutation returns the OAuth2ClientMutation object of the builder.\nfunc (_u *OAuth2ClientUpdateOne) Mutation() *OAuth2ClientMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the OAuth2ClientUpdate builder.\nfunc (_u *OAuth2ClientUpdateOne) Where(ps ...predicate.OAuth2Client) *OAuth2ClientUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *OAuth2ClientUpdateOne) Select(field string, fields ...string) *OAuth2ClientUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated OAuth2Client entity.\nfunc (_u *OAuth2ClientUpdateOne) Save(ctx context.Context) (*OAuth2Client, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *OAuth2ClientUpdateOne) SaveX(ctx context.Context) *OAuth2Client {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *OAuth2ClientUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *OAuth2ClientUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *OAuth2ClientUpdateOne) check() error {\n\tif v, ok := _u.mutation.Secret(); ok {\n\t\tif err := oauth2client.SecretValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"secret\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.secret\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Name(); ok {\n\t\tif err := oauth2client.NameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"name\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.name\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.LogoURL(); ok {\n\t\tif err := oauth2client.LogoURLValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"logo_url\", err: fmt.Errorf(`db: validator failed for field \"OAuth2Client.logo_url\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *OAuth2ClientUpdateOne) sqlSave(ctx context.Context) (_node *OAuth2Client, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(oauth2client.Table, oauth2client.Columns, sqlgraph.NewFieldSpec(oauth2client.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"OAuth2Client.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, oauth2client.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !oauth2client.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != oauth2client.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.Secret(); ok {\n\t\t_spec.SetField(oauth2client.FieldSecret, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.RedirectUris(); ok {\n\t\t_spec.SetField(oauth2client.FieldRedirectUris, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedRedirectUris(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldRedirectUris, value)\n\t\t})\n\t}\n\tif _u.mutation.RedirectUrisCleared() {\n\t\t_spec.ClearField(oauth2client.FieldRedirectUris, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.TrustedPeers(); ok {\n\t\t_spec.SetField(oauth2client.FieldTrustedPeers, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedTrustedPeers(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldTrustedPeers, value)\n\t\t})\n\t}\n\tif _u.mutation.TrustedPeersCleared() {\n\t\t_spec.ClearField(oauth2client.FieldTrustedPeers, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Public(); ok {\n\t\t_spec.SetField(oauth2client.FieldPublic, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.Name(); ok {\n\t\t_spec.SetField(oauth2client.FieldName, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.LogoURL(); ok {\n\t\t_spec.SetField(oauth2client.FieldLogoURL, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.AllowedConnectors(); ok {\n\t\t_spec.SetField(oauth2client.FieldAllowedConnectors, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedAllowedConnectors(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldAllowedConnectors, value)\n\t\t})\n\t}\n\tif _u.mutation.AllowedConnectorsCleared() {\n\t\t_spec.ClearField(oauth2client.FieldAllowedConnectors, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.MfaChain(); ok {\n\t\t_spec.SetField(oauth2client.FieldMfaChain, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedMfaChain(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, oauth2client.FieldMfaChain, value)\n\t\t})\n\t}\n\tif _u.mutation.MfaChainCleared() {\n\t\t_spec.ClearField(oauth2client.FieldMfaChain, field.TypeJSON)\n\t}\n\t_node = &OAuth2Client{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{oauth2client.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/offlinesession/offlinesession.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage offlinesession\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the offlinesession type in the database.\n\tLabel = \"offline_session\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldUserID holds the string denoting the user_id field in the database.\n\tFieldUserID = \"user_id\"\n\t// FieldConnID holds the string denoting the conn_id field in the database.\n\tFieldConnID = \"conn_id\"\n\t// FieldRefresh holds the string denoting the refresh field in the database.\n\tFieldRefresh = \"refresh\"\n\t// FieldConnectorData holds the string denoting the connector_data field in the database.\n\tFieldConnectorData = \"connector_data\"\n\t// Table holds the table name of the offlinesession in the database.\n\tTable = \"offline_sessions\"\n)\n\n// Columns holds all SQL columns for offlinesession fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldUserID,\n\tFieldConnID,\n\tFieldRefresh,\n\tFieldConnectorData,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tUserIDValidator func(string) error\n\t// ConnIDValidator is a validator for the \"conn_id\" field. It is called by the builders before save.\n\tConnIDValidator func(string) error\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the OfflineSession queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByUserID orders the results by the user_id field.\nfunc ByUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldUserID, opts...).ToFunc()\n}\n\n// ByConnID orders the results by the conn_id field.\nfunc ByConnID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldConnID, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/offlinesession/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage offlinesession\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldContainsFold(FieldID, id))\n}\n\n// UserID applies equality check predicate on the \"user_id\" field. It's identical to UserIDEQ.\nfunc UserID(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldUserID, v))\n}\n\n// ConnID applies equality check predicate on the \"conn_id\" field. It's identical to ConnIDEQ.\nfunc ConnID(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldConnID, v))\n}\n\n// Refresh applies equality check predicate on the \"refresh\" field. It's identical to RefreshEQ.\nfunc Refresh(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldRefresh, v))\n}\n\n// ConnectorData applies equality check predicate on the \"connector_data\" field. It's identical to ConnectorDataEQ.\nfunc ConnectorData(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// UserIDEQ applies the EQ predicate on the \"user_id\" field.\nfunc UserIDEQ(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldUserID, v))\n}\n\n// UserIDNEQ applies the NEQ predicate on the \"user_id\" field.\nfunc UserIDNEQ(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNEQ(FieldUserID, v))\n}\n\n// UserIDIn applies the In predicate on the \"user_id\" field.\nfunc UserIDIn(vs ...string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldIn(FieldUserID, vs...))\n}\n\n// UserIDNotIn applies the NotIn predicate on the \"user_id\" field.\nfunc UserIDNotIn(vs ...string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNotIn(FieldUserID, vs...))\n}\n\n// UserIDGT applies the GT predicate on the \"user_id\" field.\nfunc UserIDGT(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGT(FieldUserID, v))\n}\n\n// UserIDGTE applies the GTE predicate on the \"user_id\" field.\nfunc UserIDGTE(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGTE(FieldUserID, v))\n}\n\n// UserIDLT applies the LT predicate on the \"user_id\" field.\nfunc UserIDLT(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLT(FieldUserID, v))\n}\n\n// UserIDLTE applies the LTE predicate on the \"user_id\" field.\nfunc UserIDLTE(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLTE(FieldUserID, v))\n}\n\n// UserIDContains applies the Contains predicate on the \"user_id\" field.\nfunc UserIDContains(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldContains(FieldUserID, v))\n}\n\n// UserIDHasPrefix applies the HasPrefix predicate on the \"user_id\" field.\nfunc UserIDHasPrefix(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldHasPrefix(FieldUserID, v))\n}\n\n// UserIDHasSuffix applies the HasSuffix predicate on the \"user_id\" field.\nfunc UserIDHasSuffix(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldHasSuffix(FieldUserID, v))\n}\n\n// UserIDEqualFold applies the EqualFold predicate on the \"user_id\" field.\nfunc UserIDEqualFold(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEqualFold(FieldUserID, v))\n}\n\n// UserIDContainsFold applies the ContainsFold predicate on the \"user_id\" field.\nfunc UserIDContainsFold(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldContainsFold(FieldUserID, v))\n}\n\n// ConnIDEQ applies the EQ predicate on the \"conn_id\" field.\nfunc ConnIDEQ(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldConnID, v))\n}\n\n// ConnIDNEQ applies the NEQ predicate on the \"conn_id\" field.\nfunc ConnIDNEQ(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNEQ(FieldConnID, v))\n}\n\n// ConnIDIn applies the In predicate on the \"conn_id\" field.\nfunc ConnIDIn(vs ...string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldIn(FieldConnID, vs...))\n}\n\n// ConnIDNotIn applies the NotIn predicate on the \"conn_id\" field.\nfunc ConnIDNotIn(vs ...string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNotIn(FieldConnID, vs...))\n}\n\n// ConnIDGT applies the GT predicate on the \"conn_id\" field.\nfunc ConnIDGT(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGT(FieldConnID, v))\n}\n\n// ConnIDGTE applies the GTE predicate on the \"conn_id\" field.\nfunc ConnIDGTE(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGTE(FieldConnID, v))\n}\n\n// ConnIDLT applies the LT predicate on the \"conn_id\" field.\nfunc ConnIDLT(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLT(FieldConnID, v))\n}\n\n// ConnIDLTE applies the LTE predicate on the \"conn_id\" field.\nfunc ConnIDLTE(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLTE(FieldConnID, v))\n}\n\n// ConnIDContains applies the Contains predicate on the \"conn_id\" field.\nfunc ConnIDContains(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldContains(FieldConnID, v))\n}\n\n// ConnIDHasPrefix applies the HasPrefix predicate on the \"conn_id\" field.\nfunc ConnIDHasPrefix(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldHasPrefix(FieldConnID, v))\n}\n\n// ConnIDHasSuffix applies the HasSuffix predicate on the \"conn_id\" field.\nfunc ConnIDHasSuffix(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldHasSuffix(FieldConnID, v))\n}\n\n// ConnIDEqualFold applies the EqualFold predicate on the \"conn_id\" field.\nfunc ConnIDEqualFold(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEqualFold(FieldConnID, v))\n}\n\n// ConnIDContainsFold applies the ContainsFold predicate on the \"conn_id\" field.\nfunc ConnIDContainsFold(v string) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldContainsFold(FieldConnID, v))\n}\n\n// RefreshEQ applies the EQ predicate on the \"refresh\" field.\nfunc RefreshEQ(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldRefresh, v))\n}\n\n// RefreshNEQ applies the NEQ predicate on the \"refresh\" field.\nfunc RefreshNEQ(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNEQ(FieldRefresh, v))\n}\n\n// RefreshIn applies the In predicate on the \"refresh\" field.\nfunc RefreshIn(vs ...[]byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldIn(FieldRefresh, vs...))\n}\n\n// RefreshNotIn applies the NotIn predicate on the \"refresh\" field.\nfunc RefreshNotIn(vs ...[]byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNotIn(FieldRefresh, vs...))\n}\n\n// RefreshGT applies the GT predicate on the \"refresh\" field.\nfunc RefreshGT(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGT(FieldRefresh, v))\n}\n\n// RefreshGTE applies the GTE predicate on the \"refresh\" field.\nfunc RefreshGTE(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGTE(FieldRefresh, v))\n}\n\n// RefreshLT applies the LT predicate on the \"refresh\" field.\nfunc RefreshLT(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLT(FieldRefresh, v))\n}\n\n// RefreshLTE applies the LTE predicate on the \"refresh\" field.\nfunc RefreshLTE(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLTE(FieldRefresh, v))\n}\n\n// ConnectorDataEQ applies the EQ predicate on the \"connector_data\" field.\nfunc ConnectorDataEQ(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataNEQ applies the NEQ predicate on the \"connector_data\" field.\nfunc ConnectorDataNEQ(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataIn applies the In predicate on the \"connector_data\" field.\nfunc ConnectorDataIn(vs ...[]byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataNotIn applies the NotIn predicate on the \"connector_data\" field.\nfunc ConnectorDataNotIn(vs ...[]byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNotIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataGT applies the GT predicate on the \"connector_data\" field.\nfunc ConnectorDataGT(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGT(FieldConnectorData, v))\n}\n\n// ConnectorDataGTE applies the GTE predicate on the \"connector_data\" field.\nfunc ConnectorDataGTE(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldGTE(FieldConnectorData, v))\n}\n\n// ConnectorDataLT applies the LT predicate on the \"connector_data\" field.\nfunc ConnectorDataLT(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLT(FieldConnectorData, v))\n}\n\n// ConnectorDataLTE applies the LTE predicate on the \"connector_data\" field.\nfunc ConnectorDataLTE(v []byte) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldLTE(FieldConnectorData, v))\n}\n\n// ConnectorDataIsNil applies the IsNil predicate on the \"connector_data\" field.\nfunc ConnectorDataIsNil() predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldIsNull(FieldConnectorData))\n}\n\n// ConnectorDataNotNil applies the NotNil predicate on the \"connector_data\" field.\nfunc ConnectorDataNotNil() predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.FieldNotNull(FieldConnectorData))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.OfflineSession) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.OfflineSession) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.OfflineSession) predicate.OfflineSession {\n\treturn predicate.OfflineSession(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/offlinesession.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n)\n\n// OfflineSession is the model entity for the OfflineSession schema.\ntype OfflineSession struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// UserID holds the value of the \"user_id\" field.\n\tUserID string `json:\"user_id,omitempty\"`\n\t// ConnID holds the value of the \"conn_id\" field.\n\tConnID string `json:\"conn_id,omitempty\"`\n\t// Refresh holds the value of the \"refresh\" field.\n\tRefresh []byte `json:\"refresh,omitempty\"`\n\t// ConnectorData holds the value of the \"connector_data\" field.\n\tConnectorData *[]byte `json:\"connector_data,omitempty\"`\n\tselectValues  sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*OfflineSession) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase offlinesession.FieldRefresh, offlinesession.FieldConnectorData:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase offlinesession.FieldID, offlinesession.FieldUserID, offlinesession.FieldConnID:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the OfflineSession fields.\nfunc (_m *OfflineSession) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase offlinesession.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase offlinesession.FieldUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.UserID = value.String\n\t\t\t}\n\t\tcase offlinesession.FieldConnID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field conn_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ConnID = value.String\n\t\t\t}\n\t\tcase offlinesession.FieldRefresh:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field refresh\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.Refresh = *value\n\t\t\t}\n\t\tcase offlinesession.FieldConnectorData:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_data\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.ConnectorData = value\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the OfflineSession.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *OfflineSession) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this OfflineSession.\n// Note that you need to call OfflineSession.Unwrap() before calling this method if this OfflineSession\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *OfflineSession) Update() *OfflineSessionUpdateOne {\n\treturn NewOfflineSessionClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the OfflineSession entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *OfflineSession) Unwrap() *OfflineSession {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: OfflineSession is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *OfflineSession) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"OfflineSession(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"user_id=\")\n\tbuilder.WriteString(_m.UserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"conn_id=\")\n\tbuilder.WriteString(_m.ConnID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"refresh=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Refresh))\n\tbuilder.WriteString(\", \")\n\tif v := _m.ConnectorData; v != nil {\n\t\tbuilder.WriteString(\"connector_data=\")\n\t\tbuilder.WriteString(fmt.Sprintf(\"%v\", *v))\n\t}\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// OfflineSessions is a parsable slice of OfflineSession.\ntype OfflineSessions []*OfflineSession\n"
  },
  {
    "path": "storage/ent/db/offlinesession_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n)\n\n// OfflineSessionCreate is the builder for creating a OfflineSession entity.\ntype OfflineSessionCreate struct {\n\tconfig\n\tmutation *OfflineSessionMutation\n\thooks    []Hook\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_c *OfflineSessionCreate) SetUserID(v string) *OfflineSessionCreate {\n\t_c.mutation.SetUserID(v)\n\treturn _c\n}\n\n// SetConnID sets the \"conn_id\" field.\nfunc (_c *OfflineSessionCreate) SetConnID(v string) *OfflineSessionCreate {\n\t_c.mutation.SetConnID(v)\n\treturn _c\n}\n\n// SetRefresh sets the \"refresh\" field.\nfunc (_c *OfflineSessionCreate) SetRefresh(v []byte) *OfflineSessionCreate {\n\t_c.mutation.SetRefresh(v)\n\treturn _c\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_c *OfflineSessionCreate) SetConnectorData(v []byte) *OfflineSessionCreate {\n\t_c.mutation.SetConnectorData(v)\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *OfflineSessionCreate) SetID(v string) *OfflineSessionCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the OfflineSessionMutation object of the builder.\nfunc (_c *OfflineSessionCreate) Mutation() *OfflineSessionMutation {\n\treturn _c.mutation\n}\n\n// Save creates the OfflineSession in the database.\nfunc (_c *OfflineSessionCreate) Save(ctx context.Context) (*OfflineSession, error) {\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *OfflineSessionCreate) SaveX(ctx context.Context) *OfflineSession {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *OfflineSessionCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *OfflineSessionCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *OfflineSessionCreate) check() error {\n\tif _, ok := _c.mutation.UserID(); !ok {\n\t\treturn &ValidationError{Name: \"user_id\", err: errors.New(`db: missing required field \"OfflineSession.user_id\"`)}\n\t}\n\tif v, ok := _c.mutation.UserID(); ok {\n\t\tif err := offlinesession.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"OfflineSession.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ConnID(); !ok {\n\t\treturn &ValidationError{Name: \"conn_id\", err: errors.New(`db: missing required field \"OfflineSession.conn_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ConnID(); ok {\n\t\tif err := offlinesession.ConnIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"conn_id\", err: fmt.Errorf(`db: validator failed for field \"OfflineSession.conn_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Refresh(); !ok {\n\t\treturn &ValidationError{Name: \"refresh\", err: errors.New(`db: missing required field \"OfflineSession.refresh\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := offlinesession.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"OfflineSession.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *OfflineSessionCreate) sqlSave(ctx context.Context) (*OfflineSession, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected OfflineSession.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *OfflineSessionCreate) createSpec() (*OfflineSession, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &OfflineSession{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(offlinesession.Table, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.UserID(); ok {\n\t\t_spec.SetField(offlinesession.FieldUserID, field.TypeString, value)\n\t\t_node.UserID = value\n\t}\n\tif value, ok := _c.mutation.ConnID(); ok {\n\t\t_spec.SetField(offlinesession.FieldConnID, field.TypeString, value)\n\t\t_node.ConnID = value\n\t}\n\tif value, ok := _c.mutation.Refresh(); ok {\n\t\t_spec.SetField(offlinesession.FieldRefresh, field.TypeBytes, value)\n\t\t_node.Refresh = value\n\t}\n\tif value, ok := _c.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(offlinesession.FieldConnectorData, field.TypeBytes, value)\n\t\t_node.ConnectorData = &value\n\t}\n\treturn _node, _spec\n}\n\n// OfflineSessionCreateBulk is the builder for creating many OfflineSession entities in bulk.\ntype OfflineSessionCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*OfflineSessionCreate\n}\n\n// Save creates the OfflineSession entities in the database.\nfunc (_c *OfflineSessionCreateBulk) Save(ctx context.Context) ([]*OfflineSession, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*OfflineSession, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*OfflineSessionMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *OfflineSessionCreateBulk) SaveX(ctx context.Context) []*OfflineSession {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *OfflineSessionCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *OfflineSessionCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/offlinesession_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// OfflineSessionDelete is the builder for deleting a OfflineSession entity.\ntype OfflineSessionDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *OfflineSessionMutation\n}\n\n// Where appends a list predicates to the OfflineSessionDelete builder.\nfunc (_d *OfflineSessionDelete) Where(ps ...predicate.OfflineSession) *OfflineSessionDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *OfflineSessionDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *OfflineSessionDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *OfflineSessionDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(offlinesession.Table, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// OfflineSessionDeleteOne is the builder for deleting a single OfflineSession entity.\ntype OfflineSessionDeleteOne struct {\n\t_d *OfflineSessionDelete\n}\n\n// Where appends a list predicates to the OfflineSessionDelete builder.\nfunc (_d *OfflineSessionDeleteOne) Where(ps ...predicate.OfflineSession) *OfflineSessionDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *OfflineSessionDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{offlinesession.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *OfflineSessionDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/offlinesession_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// OfflineSessionQuery is the builder for querying OfflineSession entities.\ntype OfflineSessionQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []offlinesession.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.OfflineSession\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the OfflineSessionQuery builder.\nfunc (_q *OfflineSessionQuery) Where(ps ...predicate.OfflineSession) *OfflineSessionQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *OfflineSessionQuery) Limit(limit int) *OfflineSessionQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *OfflineSessionQuery) Offset(offset int) *OfflineSessionQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *OfflineSessionQuery) Unique(unique bool) *OfflineSessionQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *OfflineSessionQuery) Order(o ...offlinesession.OrderOption) *OfflineSessionQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first OfflineSession entity from the query.\n// Returns a *NotFoundError when no OfflineSession was found.\nfunc (_q *OfflineSessionQuery) First(ctx context.Context) (*OfflineSession, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{offlinesession.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) FirstX(ctx context.Context) *OfflineSession {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first OfflineSession ID from the query.\n// Returns a *NotFoundError when no OfflineSession ID was found.\nfunc (_q *OfflineSessionQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{offlinesession.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single OfflineSession entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one OfflineSession entity is found.\n// Returns a *NotFoundError when no OfflineSession entities are found.\nfunc (_q *OfflineSessionQuery) Only(ctx context.Context) (*OfflineSession, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{offlinesession.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{offlinesession.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) OnlyX(ctx context.Context) *OfflineSession {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only OfflineSession ID in the query.\n// Returns a *NotSingularError when more than one OfflineSession ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *OfflineSessionQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{offlinesession.Label}\n\tdefault:\n\t\terr = &NotSingularError{offlinesession.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of OfflineSessions.\nfunc (_q *OfflineSessionQuery) All(ctx context.Context) ([]*OfflineSession, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*OfflineSession, *OfflineSessionQuery]()\n\treturn withInterceptors[[]*OfflineSession](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) AllX(ctx context.Context) []*OfflineSession {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of OfflineSession IDs.\nfunc (_q *OfflineSessionQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(offlinesession.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *OfflineSessionQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*OfflineSessionQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *OfflineSessionQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *OfflineSessionQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the OfflineSessionQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *OfflineSessionQuery) Clone() *OfflineSessionQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &OfflineSessionQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]offlinesession.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.OfflineSession{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserID string `json:\"user_id,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.OfflineSession.Query().\n//\t\tGroupBy(offlinesession.FieldUserID).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *OfflineSessionQuery) GroupBy(field string, fields ...string) *OfflineSessionGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &OfflineSessionGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = offlinesession.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserID string `json:\"user_id,omitempty\"`\n//\t}\n//\n//\tclient.OfflineSession.Query().\n//\t\tSelect(offlinesession.FieldUserID).\n//\t\tScan(ctx, &v)\nfunc (_q *OfflineSessionQuery) Select(fields ...string) *OfflineSessionSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &OfflineSessionSelect{OfflineSessionQuery: _q}\n\tsbuild.label = offlinesession.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a OfflineSessionSelect configured with the given aggregations.\nfunc (_q *OfflineSessionQuery) Aggregate(fns ...AggregateFunc) *OfflineSessionSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *OfflineSessionQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !offlinesession.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *OfflineSessionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*OfflineSession, error) {\n\tvar (\n\t\tnodes = []*OfflineSession{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*OfflineSession).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &OfflineSession{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *OfflineSessionQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *OfflineSessionQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(offlinesession.Table, offlinesession.Columns, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, offlinesession.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != offlinesession.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *OfflineSessionQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(offlinesession.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = offlinesession.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// OfflineSessionGroupBy is the group-by builder for OfflineSession entities.\ntype OfflineSessionGroupBy struct {\n\tselector\n\tbuild *OfflineSessionQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *OfflineSessionGroupBy) Aggregate(fns ...AggregateFunc) *OfflineSessionGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *OfflineSessionGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*OfflineSessionQuery, *OfflineSessionGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *OfflineSessionGroupBy) sqlScan(ctx context.Context, root *OfflineSessionQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// OfflineSessionSelect is the builder for selecting fields of OfflineSession entities.\ntype OfflineSessionSelect struct {\n\t*OfflineSessionQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *OfflineSessionSelect) Aggregate(fns ...AggregateFunc) *OfflineSessionSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *OfflineSessionSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*OfflineSessionQuery, *OfflineSessionSelect](ctx, _s.OfflineSessionQuery, _s, _s.inters, v)\n}\n\nfunc (_s *OfflineSessionSelect) sqlScan(ctx context.Context, root *OfflineSessionQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/offlinesession_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// OfflineSessionUpdate is the builder for updating OfflineSession entities.\ntype OfflineSessionUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *OfflineSessionMutation\n}\n\n// Where appends a list predicates to the OfflineSessionUpdate builder.\nfunc (_u *OfflineSessionUpdate) Where(ps ...predicate.OfflineSession) *OfflineSessionUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *OfflineSessionUpdate) SetUserID(v string) *OfflineSessionUpdate {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *OfflineSessionUpdate) SetNillableUserID(v *string) *OfflineSessionUpdate {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnID sets the \"conn_id\" field.\nfunc (_u *OfflineSessionUpdate) SetConnID(v string) *OfflineSessionUpdate {\n\t_u.mutation.SetConnID(v)\n\treturn _u\n}\n\n// SetNillableConnID sets the \"conn_id\" field if the given value is not nil.\nfunc (_u *OfflineSessionUpdate) SetNillableConnID(v *string) *OfflineSessionUpdate {\n\tif v != nil {\n\t\t_u.SetConnID(*v)\n\t}\n\treturn _u\n}\n\n// SetRefresh sets the \"refresh\" field.\nfunc (_u *OfflineSessionUpdate) SetRefresh(v []byte) *OfflineSessionUpdate {\n\t_u.mutation.SetRefresh(v)\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *OfflineSessionUpdate) SetConnectorData(v []byte) *OfflineSessionUpdate {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *OfflineSessionUpdate) ClearConnectorData() *OfflineSessionUpdate {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// Mutation returns the OfflineSessionMutation object of the builder.\nfunc (_u *OfflineSessionUpdate) Mutation() *OfflineSessionMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *OfflineSessionUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *OfflineSessionUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *OfflineSessionUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *OfflineSessionUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *OfflineSessionUpdate) check() error {\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := offlinesession.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"OfflineSession.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnID(); ok {\n\t\tif err := offlinesession.ConnIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"conn_id\", err: fmt.Errorf(`db: validator failed for field \"OfflineSession.conn_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *OfflineSessionUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(offlinesession.Table, offlinesession.Columns, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(offlinesession.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnID(); ok {\n\t\t_spec.SetField(offlinesession.FieldConnID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Refresh(); ok {\n\t\t_spec.SetField(offlinesession.FieldRefresh, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(offlinesession.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(offlinesession.FieldConnectorData, field.TypeBytes)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{offlinesession.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// OfflineSessionUpdateOne is the builder for updating a single OfflineSession entity.\ntype OfflineSessionUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *OfflineSessionMutation\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *OfflineSessionUpdateOne) SetUserID(v string) *OfflineSessionUpdateOne {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *OfflineSessionUpdateOne) SetNillableUserID(v *string) *OfflineSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnID sets the \"conn_id\" field.\nfunc (_u *OfflineSessionUpdateOne) SetConnID(v string) *OfflineSessionUpdateOne {\n\t_u.mutation.SetConnID(v)\n\treturn _u\n}\n\n// SetNillableConnID sets the \"conn_id\" field if the given value is not nil.\nfunc (_u *OfflineSessionUpdateOne) SetNillableConnID(v *string) *OfflineSessionUpdateOne {\n\tif v != nil {\n\t\t_u.SetConnID(*v)\n\t}\n\treturn _u\n}\n\n// SetRefresh sets the \"refresh\" field.\nfunc (_u *OfflineSessionUpdateOne) SetRefresh(v []byte) *OfflineSessionUpdateOne {\n\t_u.mutation.SetRefresh(v)\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *OfflineSessionUpdateOne) SetConnectorData(v []byte) *OfflineSessionUpdateOne {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *OfflineSessionUpdateOne) ClearConnectorData() *OfflineSessionUpdateOne {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// Mutation returns the OfflineSessionMutation object of the builder.\nfunc (_u *OfflineSessionUpdateOne) Mutation() *OfflineSessionMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the OfflineSessionUpdate builder.\nfunc (_u *OfflineSessionUpdateOne) Where(ps ...predicate.OfflineSession) *OfflineSessionUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *OfflineSessionUpdateOne) Select(field string, fields ...string) *OfflineSessionUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated OfflineSession entity.\nfunc (_u *OfflineSessionUpdateOne) Save(ctx context.Context) (*OfflineSession, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *OfflineSessionUpdateOne) SaveX(ctx context.Context) *OfflineSession {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *OfflineSessionUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *OfflineSessionUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *OfflineSessionUpdateOne) check() error {\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := offlinesession.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"OfflineSession.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnID(); ok {\n\t\tif err := offlinesession.ConnIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"conn_id\", err: fmt.Errorf(`db: validator failed for field \"OfflineSession.conn_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *OfflineSessionUpdateOne) sqlSave(ctx context.Context) (_node *OfflineSession, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(offlinesession.Table, offlinesession.Columns, sqlgraph.NewFieldSpec(offlinesession.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"OfflineSession.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, offlinesession.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !offlinesession.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != offlinesession.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(offlinesession.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnID(); ok {\n\t\t_spec.SetField(offlinesession.FieldConnID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Refresh(); ok {\n\t\t_spec.SetField(offlinesession.FieldRefresh, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(offlinesession.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(offlinesession.FieldConnectorData, field.TypeBytes)\n\t}\n\t_node = &OfflineSession{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{offlinesession.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/password/password.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage password\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the password type in the database.\n\tLabel = \"password\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldEmail holds the string denoting the email field in the database.\n\tFieldEmail = \"email\"\n\t// FieldHash holds the string denoting the hash field in the database.\n\tFieldHash = \"hash\"\n\t// FieldUsername holds the string denoting the username field in the database.\n\tFieldUsername = \"username\"\n\t// FieldName holds the string denoting the name field in the database.\n\tFieldName = \"name\"\n\t// FieldPreferredUsername holds the string denoting the preferred_username field in the database.\n\tFieldPreferredUsername = \"preferred_username\"\n\t// FieldEmailVerified holds the string denoting the email_verified field in the database.\n\tFieldEmailVerified = \"email_verified\"\n\t// FieldUserID holds the string denoting the user_id field in the database.\n\tFieldUserID = \"user_id\"\n\t// FieldGroups holds the string denoting the groups field in the database.\n\tFieldGroups = \"groups\"\n\t// Table holds the table name of the password in the database.\n\tTable = \"passwords\"\n)\n\n// Columns holds all SQL columns for password fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldEmail,\n\tFieldHash,\n\tFieldUsername,\n\tFieldName,\n\tFieldPreferredUsername,\n\tFieldEmailVerified,\n\tFieldUserID,\n\tFieldGroups,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// EmailValidator is a validator for the \"email\" field. It is called by the builders before save.\n\tEmailValidator func(string) error\n\t// UsernameValidator is a validator for the \"username\" field. It is called by the builders before save.\n\tUsernameValidator func(string) error\n\t// DefaultName holds the default value on creation for the \"name\" field.\n\tDefaultName string\n\t// DefaultPreferredUsername holds the default value on creation for the \"preferred_username\" field.\n\tDefaultPreferredUsername string\n\t// UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tUserIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the Password queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByEmail orders the results by the email field.\nfunc ByEmail(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldEmail, opts...).ToFunc()\n}\n\n// ByUsername orders the results by the username field.\nfunc ByUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldUsername, opts...).ToFunc()\n}\n\n// ByName orders the results by the name field.\nfunc ByName(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldName, opts...).ToFunc()\n}\n\n// ByPreferredUsername orders the results by the preferred_username field.\nfunc ByPreferredUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldPreferredUsername, opts...).ToFunc()\n}\n\n// ByEmailVerified orders the results by the email_verified field.\nfunc ByEmailVerified(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldEmailVerified, opts...).ToFunc()\n}\n\n// ByUserID orders the results by the user_id field.\nfunc ByUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldUserID, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/password/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage password\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id int) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id int) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id int) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...int) predicate.Password {\n\treturn predicate.Password(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...int) predicate.Password {\n\treturn predicate.Password(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id int) predicate.Password {\n\treturn predicate.Password(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id int) predicate.Password {\n\treturn predicate.Password(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id int) predicate.Password {\n\treturn predicate.Password(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id int) predicate.Password {\n\treturn predicate.Password(sql.FieldLTE(FieldID, id))\n}\n\n// Email applies equality check predicate on the \"email\" field. It's identical to EmailEQ.\nfunc Email(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldEmail, v))\n}\n\n// Hash applies equality check predicate on the \"hash\" field. It's identical to HashEQ.\nfunc Hash(v []byte) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldHash, v))\n}\n\n// Username applies equality check predicate on the \"username\" field. It's identical to UsernameEQ.\nfunc Username(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldUsername, v))\n}\n\n// Name applies equality check predicate on the \"name\" field. It's identical to NameEQ.\nfunc Name(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldName, v))\n}\n\n// PreferredUsername applies equality check predicate on the \"preferred_username\" field. It's identical to PreferredUsernameEQ.\nfunc PreferredUsername(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldPreferredUsername, v))\n}\n\n// EmailVerified applies equality check predicate on the \"email_verified\" field. It's identical to EmailVerifiedEQ.\nfunc EmailVerified(v bool) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldEmailVerified, v))\n}\n\n// UserID applies equality check predicate on the \"user_id\" field. It's identical to UserIDEQ.\nfunc UserID(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldUserID, v))\n}\n\n// EmailEQ applies the EQ predicate on the \"email\" field.\nfunc EmailEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldEmail, v))\n}\n\n// EmailNEQ applies the NEQ predicate on the \"email\" field.\nfunc EmailNEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldEmail, v))\n}\n\n// EmailIn applies the In predicate on the \"email\" field.\nfunc EmailIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldIn(FieldEmail, vs...))\n}\n\n// EmailNotIn applies the NotIn predicate on the \"email\" field.\nfunc EmailNotIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldNotIn(FieldEmail, vs...))\n}\n\n// EmailGT applies the GT predicate on the \"email\" field.\nfunc EmailGT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGT(FieldEmail, v))\n}\n\n// EmailGTE applies the GTE predicate on the \"email\" field.\nfunc EmailGTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGTE(FieldEmail, v))\n}\n\n// EmailLT applies the LT predicate on the \"email\" field.\nfunc EmailLT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLT(FieldEmail, v))\n}\n\n// EmailLTE applies the LTE predicate on the \"email\" field.\nfunc EmailLTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLTE(FieldEmail, v))\n}\n\n// EmailContains applies the Contains predicate on the \"email\" field.\nfunc EmailContains(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContains(FieldEmail, v))\n}\n\n// EmailHasPrefix applies the HasPrefix predicate on the \"email\" field.\nfunc EmailHasPrefix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasPrefix(FieldEmail, v))\n}\n\n// EmailHasSuffix applies the HasSuffix predicate on the \"email\" field.\nfunc EmailHasSuffix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasSuffix(FieldEmail, v))\n}\n\n// EmailEqualFold applies the EqualFold predicate on the \"email\" field.\nfunc EmailEqualFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEqualFold(FieldEmail, v))\n}\n\n// EmailContainsFold applies the ContainsFold predicate on the \"email\" field.\nfunc EmailContainsFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContainsFold(FieldEmail, v))\n}\n\n// HashEQ applies the EQ predicate on the \"hash\" field.\nfunc HashEQ(v []byte) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldHash, v))\n}\n\n// HashNEQ applies the NEQ predicate on the \"hash\" field.\nfunc HashNEQ(v []byte) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldHash, v))\n}\n\n// HashIn applies the In predicate on the \"hash\" field.\nfunc HashIn(vs ...[]byte) predicate.Password {\n\treturn predicate.Password(sql.FieldIn(FieldHash, vs...))\n}\n\n// HashNotIn applies the NotIn predicate on the \"hash\" field.\nfunc HashNotIn(vs ...[]byte) predicate.Password {\n\treturn predicate.Password(sql.FieldNotIn(FieldHash, vs...))\n}\n\n// HashGT applies the GT predicate on the \"hash\" field.\nfunc HashGT(v []byte) predicate.Password {\n\treturn predicate.Password(sql.FieldGT(FieldHash, v))\n}\n\n// HashGTE applies the GTE predicate on the \"hash\" field.\nfunc HashGTE(v []byte) predicate.Password {\n\treturn predicate.Password(sql.FieldGTE(FieldHash, v))\n}\n\n// HashLT applies the LT predicate on the \"hash\" field.\nfunc HashLT(v []byte) predicate.Password {\n\treturn predicate.Password(sql.FieldLT(FieldHash, v))\n}\n\n// HashLTE applies the LTE predicate on the \"hash\" field.\nfunc HashLTE(v []byte) predicate.Password {\n\treturn predicate.Password(sql.FieldLTE(FieldHash, v))\n}\n\n// UsernameEQ applies the EQ predicate on the \"username\" field.\nfunc UsernameEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldUsername, v))\n}\n\n// UsernameNEQ applies the NEQ predicate on the \"username\" field.\nfunc UsernameNEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldUsername, v))\n}\n\n// UsernameIn applies the In predicate on the \"username\" field.\nfunc UsernameIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldIn(FieldUsername, vs...))\n}\n\n// UsernameNotIn applies the NotIn predicate on the \"username\" field.\nfunc UsernameNotIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldNotIn(FieldUsername, vs...))\n}\n\n// UsernameGT applies the GT predicate on the \"username\" field.\nfunc UsernameGT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGT(FieldUsername, v))\n}\n\n// UsernameGTE applies the GTE predicate on the \"username\" field.\nfunc UsernameGTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGTE(FieldUsername, v))\n}\n\n// UsernameLT applies the LT predicate on the \"username\" field.\nfunc UsernameLT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLT(FieldUsername, v))\n}\n\n// UsernameLTE applies the LTE predicate on the \"username\" field.\nfunc UsernameLTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLTE(FieldUsername, v))\n}\n\n// UsernameContains applies the Contains predicate on the \"username\" field.\nfunc UsernameContains(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContains(FieldUsername, v))\n}\n\n// UsernameHasPrefix applies the HasPrefix predicate on the \"username\" field.\nfunc UsernameHasPrefix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasPrefix(FieldUsername, v))\n}\n\n// UsernameHasSuffix applies the HasSuffix predicate on the \"username\" field.\nfunc UsernameHasSuffix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasSuffix(FieldUsername, v))\n}\n\n// UsernameEqualFold applies the EqualFold predicate on the \"username\" field.\nfunc UsernameEqualFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEqualFold(FieldUsername, v))\n}\n\n// UsernameContainsFold applies the ContainsFold predicate on the \"username\" field.\nfunc UsernameContainsFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContainsFold(FieldUsername, v))\n}\n\n// NameEQ applies the EQ predicate on the \"name\" field.\nfunc NameEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldName, v))\n}\n\n// NameNEQ applies the NEQ predicate on the \"name\" field.\nfunc NameNEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldName, v))\n}\n\n// NameIn applies the In predicate on the \"name\" field.\nfunc NameIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldIn(FieldName, vs...))\n}\n\n// NameNotIn applies the NotIn predicate on the \"name\" field.\nfunc NameNotIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldNotIn(FieldName, vs...))\n}\n\n// NameGT applies the GT predicate on the \"name\" field.\nfunc NameGT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGT(FieldName, v))\n}\n\n// NameGTE applies the GTE predicate on the \"name\" field.\nfunc NameGTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGTE(FieldName, v))\n}\n\n// NameLT applies the LT predicate on the \"name\" field.\nfunc NameLT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLT(FieldName, v))\n}\n\n// NameLTE applies the LTE predicate on the \"name\" field.\nfunc NameLTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLTE(FieldName, v))\n}\n\n// NameContains applies the Contains predicate on the \"name\" field.\nfunc NameContains(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContains(FieldName, v))\n}\n\n// NameHasPrefix applies the HasPrefix predicate on the \"name\" field.\nfunc NameHasPrefix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasPrefix(FieldName, v))\n}\n\n// NameHasSuffix applies the HasSuffix predicate on the \"name\" field.\nfunc NameHasSuffix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasSuffix(FieldName, v))\n}\n\n// NameEqualFold applies the EqualFold predicate on the \"name\" field.\nfunc NameEqualFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEqualFold(FieldName, v))\n}\n\n// NameContainsFold applies the ContainsFold predicate on the \"name\" field.\nfunc NameContainsFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContainsFold(FieldName, v))\n}\n\n// PreferredUsernameEQ applies the EQ predicate on the \"preferred_username\" field.\nfunc PreferredUsernameEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameNEQ applies the NEQ predicate on the \"preferred_username\" field.\nfunc PreferredUsernameNEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameIn applies the In predicate on the \"preferred_username\" field.\nfunc PreferredUsernameIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldIn(FieldPreferredUsername, vs...))\n}\n\n// PreferredUsernameNotIn applies the NotIn predicate on the \"preferred_username\" field.\nfunc PreferredUsernameNotIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldNotIn(FieldPreferredUsername, vs...))\n}\n\n// PreferredUsernameGT applies the GT predicate on the \"preferred_username\" field.\nfunc PreferredUsernameGT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGT(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameGTE applies the GTE predicate on the \"preferred_username\" field.\nfunc PreferredUsernameGTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGTE(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameLT applies the LT predicate on the \"preferred_username\" field.\nfunc PreferredUsernameLT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLT(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameLTE applies the LTE predicate on the \"preferred_username\" field.\nfunc PreferredUsernameLTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLTE(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameContains applies the Contains predicate on the \"preferred_username\" field.\nfunc PreferredUsernameContains(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContains(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameHasPrefix applies the HasPrefix predicate on the \"preferred_username\" field.\nfunc PreferredUsernameHasPrefix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasPrefix(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameHasSuffix applies the HasSuffix predicate on the \"preferred_username\" field.\nfunc PreferredUsernameHasSuffix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasSuffix(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameEqualFold applies the EqualFold predicate on the \"preferred_username\" field.\nfunc PreferredUsernameEqualFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEqualFold(FieldPreferredUsername, v))\n}\n\n// PreferredUsernameContainsFold applies the ContainsFold predicate on the \"preferred_username\" field.\nfunc PreferredUsernameContainsFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContainsFold(FieldPreferredUsername, v))\n}\n\n// EmailVerifiedEQ applies the EQ predicate on the \"email_verified\" field.\nfunc EmailVerifiedEQ(v bool) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldEmailVerified, v))\n}\n\n// EmailVerifiedNEQ applies the NEQ predicate on the \"email_verified\" field.\nfunc EmailVerifiedNEQ(v bool) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldEmailVerified, v))\n}\n\n// EmailVerifiedIsNil applies the IsNil predicate on the \"email_verified\" field.\nfunc EmailVerifiedIsNil() predicate.Password {\n\treturn predicate.Password(sql.FieldIsNull(FieldEmailVerified))\n}\n\n// EmailVerifiedNotNil applies the NotNil predicate on the \"email_verified\" field.\nfunc EmailVerifiedNotNil() predicate.Password {\n\treturn predicate.Password(sql.FieldNotNull(FieldEmailVerified))\n}\n\n// UserIDEQ applies the EQ predicate on the \"user_id\" field.\nfunc UserIDEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEQ(FieldUserID, v))\n}\n\n// UserIDNEQ applies the NEQ predicate on the \"user_id\" field.\nfunc UserIDNEQ(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldNEQ(FieldUserID, v))\n}\n\n// UserIDIn applies the In predicate on the \"user_id\" field.\nfunc UserIDIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldIn(FieldUserID, vs...))\n}\n\n// UserIDNotIn applies the NotIn predicate on the \"user_id\" field.\nfunc UserIDNotIn(vs ...string) predicate.Password {\n\treturn predicate.Password(sql.FieldNotIn(FieldUserID, vs...))\n}\n\n// UserIDGT applies the GT predicate on the \"user_id\" field.\nfunc UserIDGT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGT(FieldUserID, v))\n}\n\n// UserIDGTE applies the GTE predicate on the \"user_id\" field.\nfunc UserIDGTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldGTE(FieldUserID, v))\n}\n\n// UserIDLT applies the LT predicate on the \"user_id\" field.\nfunc UserIDLT(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLT(FieldUserID, v))\n}\n\n// UserIDLTE applies the LTE predicate on the \"user_id\" field.\nfunc UserIDLTE(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldLTE(FieldUserID, v))\n}\n\n// UserIDContains applies the Contains predicate on the \"user_id\" field.\nfunc UserIDContains(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContains(FieldUserID, v))\n}\n\n// UserIDHasPrefix applies the HasPrefix predicate on the \"user_id\" field.\nfunc UserIDHasPrefix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasPrefix(FieldUserID, v))\n}\n\n// UserIDHasSuffix applies the HasSuffix predicate on the \"user_id\" field.\nfunc UserIDHasSuffix(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldHasSuffix(FieldUserID, v))\n}\n\n// UserIDEqualFold applies the EqualFold predicate on the \"user_id\" field.\nfunc UserIDEqualFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldEqualFold(FieldUserID, v))\n}\n\n// UserIDContainsFold applies the ContainsFold predicate on the \"user_id\" field.\nfunc UserIDContainsFold(v string) predicate.Password {\n\treturn predicate.Password(sql.FieldContainsFold(FieldUserID, v))\n}\n\n// GroupsIsNil applies the IsNil predicate on the \"groups\" field.\nfunc GroupsIsNil() predicate.Password {\n\treturn predicate.Password(sql.FieldIsNull(FieldGroups))\n}\n\n// GroupsNotNil applies the NotNil predicate on the \"groups\" field.\nfunc GroupsNotNil() predicate.Password {\n\treturn predicate.Password(sql.FieldNotNull(FieldGroups))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.Password) predicate.Password {\n\treturn predicate.Password(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.Password) predicate.Password {\n\treturn predicate.Password(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.Password) predicate.Password {\n\treturn predicate.Password(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/password.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n)\n\n// Password is the model entity for the Password schema.\ntype Password struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID int `json:\"id,omitempty\"`\n\t// Email holds the value of the \"email\" field.\n\tEmail string `json:\"email,omitempty\"`\n\t// Hash holds the value of the \"hash\" field.\n\tHash []byte `json:\"hash,omitempty\"`\n\t// Username holds the value of the \"username\" field.\n\tUsername string `json:\"username,omitempty\"`\n\t// Name holds the value of the \"name\" field.\n\tName string `json:\"name,omitempty\"`\n\t// PreferredUsername holds the value of the \"preferred_username\" field.\n\tPreferredUsername string `json:\"preferred_username,omitempty\"`\n\t// EmailVerified holds the value of the \"email_verified\" field.\n\tEmailVerified *bool `json:\"email_verified,omitempty\"`\n\t// UserID holds the value of the \"user_id\" field.\n\tUserID string `json:\"user_id,omitempty\"`\n\t// Groups holds the value of the \"groups\" field.\n\tGroups       []string `json:\"groups,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*Password) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase password.FieldHash, password.FieldGroups:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase password.FieldEmailVerified:\n\t\t\tvalues[i] = new(sql.NullBool)\n\t\tcase password.FieldID:\n\t\t\tvalues[i] = new(sql.NullInt64)\n\t\tcase password.FieldEmail, password.FieldUsername, password.FieldName, password.FieldPreferredUsername, password.FieldUserID:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the Password fields.\nfunc (_m *Password) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase password.FieldID:\n\t\t\tvalue, ok := values[i].(*sql.NullInt64)\n\t\t\tif !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", value)\n\t\t\t}\n\t\t\t_m.ID = int(value.Int64)\n\t\tcase password.FieldEmail:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field email\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Email = value.String\n\t\t\t}\n\t\tcase password.FieldHash:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field hash\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.Hash = *value\n\t\t\t}\n\t\tcase password.FieldUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Username = value.String\n\t\t\t}\n\t\tcase password.FieldName:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field name\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Name = value.String\n\t\t\t}\n\t\tcase password.FieldPreferredUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field preferred_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.PreferredUsername = value.String\n\t\t\t}\n\t\tcase password.FieldEmailVerified:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field email_verified\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.EmailVerified = new(bool)\n\t\t\t\t*_m.EmailVerified = value.Bool\n\t\t\t}\n\t\tcase password.FieldUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.UserID = value.String\n\t\t\t}\n\t\tcase password.FieldGroups:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field groups\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.Groups); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field groups: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the Password.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *Password) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this Password.\n// Note that you need to call Password.Unwrap() before calling this method if this Password\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *Password) Update() *PasswordUpdateOne {\n\treturn NewPasswordClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the Password entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *Password) Unwrap() *Password {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: Password is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *Password) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"Password(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"email=\")\n\tbuilder.WriteString(_m.Email)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"hash=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Hash))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"username=\")\n\tbuilder.WriteString(_m.Username)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"name=\")\n\tbuilder.WriteString(_m.Name)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"preferred_username=\")\n\tbuilder.WriteString(_m.PreferredUsername)\n\tbuilder.WriteString(\", \")\n\tif v := _m.EmailVerified; v != nil {\n\t\tbuilder.WriteString(\"email_verified=\")\n\t\tbuilder.WriteString(fmt.Sprintf(\"%v\", *v))\n\t}\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"user_id=\")\n\tbuilder.WriteString(_m.UserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"groups=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Groups))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// Passwords is a parsable slice of Password.\ntype Passwords []*Password\n"
  },
  {
    "path": "storage/ent/db/password_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n)\n\n// PasswordCreate is the builder for creating a Password entity.\ntype PasswordCreate struct {\n\tconfig\n\tmutation *PasswordMutation\n\thooks    []Hook\n}\n\n// SetEmail sets the \"email\" field.\nfunc (_c *PasswordCreate) SetEmail(v string) *PasswordCreate {\n\t_c.mutation.SetEmail(v)\n\treturn _c\n}\n\n// SetHash sets the \"hash\" field.\nfunc (_c *PasswordCreate) SetHash(v []byte) *PasswordCreate {\n\t_c.mutation.SetHash(v)\n\treturn _c\n}\n\n// SetUsername sets the \"username\" field.\nfunc (_c *PasswordCreate) SetUsername(v string) *PasswordCreate {\n\t_c.mutation.SetUsername(v)\n\treturn _c\n}\n\n// SetName sets the \"name\" field.\nfunc (_c *PasswordCreate) SetName(v string) *PasswordCreate {\n\t_c.mutation.SetName(v)\n\treturn _c\n}\n\n// SetNillableName sets the \"name\" field if the given value is not nil.\nfunc (_c *PasswordCreate) SetNillableName(v *string) *PasswordCreate {\n\tif v != nil {\n\t\t_c.SetName(*v)\n\t}\n\treturn _c\n}\n\n// SetPreferredUsername sets the \"preferred_username\" field.\nfunc (_c *PasswordCreate) SetPreferredUsername(v string) *PasswordCreate {\n\t_c.mutation.SetPreferredUsername(v)\n\treturn _c\n}\n\n// SetNillablePreferredUsername sets the \"preferred_username\" field if the given value is not nil.\nfunc (_c *PasswordCreate) SetNillablePreferredUsername(v *string) *PasswordCreate {\n\tif v != nil {\n\t\t_c.SetPreferredUsername(*v)\n\t}\n\treturn _c\n}\n\n// SetEmailVerified sets the \"email_verified\" field.\nfunc (_c *PasswordCreate) SetEmailVerified(v bool) *PasswordCreate {\n\t_c.mutation.SetEmailVerified(v)\n\treturn _c\n}\n\n// SetNillableEmailVerified sets the \"email_verified\" field if the given value is not nil.\nfunc (_c *PasswordCreate) SetNillableEmailVerified(v *bool) *PasswordCreate {\n\tif v != nil {\n\t\t_c.SetEmailVerified(*v)\n\t}\n\treturn _c\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_c *PasswordCreate) SetUserID(v string) *PasswordCreate {\n\t_c.mutation.SetUserID(v)\n\treturn _c\n}\n\n// SetGroups sets the \"groups\" field.\nfunc (_c *PasswordCreate) SetGroups(v []string) *PasswordCreate {\n\t_c.mutation.SetGroups(v)\n\treturn _c\n}\n\n// Mutation returns the PasswordMutation object of the builder.\nfunc (_c *PasswordCreate) Mutation() *PasswordMutation {\n\treturn _c.mutation\n}\n\n// Save creates the Password in the database.\nfunc (_c *PasswordCreate) Save(ctx context.Context) (*Password, error) {\n\t_c.defaults()\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *PasswordCreate) SaveX(ctx context.Context) *Password {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *PasswordCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *PasswordCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// defaults sets the default values of the builder before save.\nfunc (_c *PasswordCreate) defaults() {\n\tif _, ok := _c.mutation.Name(); !ok {\n\t\tv := password.DefaultName\n\t\t_c.mutation.SetName(v)\n\t}\n\tif _, ok := _c.mutation.PreferredUsername(); !ok {\n\t\tv := password.DefaultPreferredUsername\n\t\t_c.mutation.SetPreferredUsername(v)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *PasswordCreate) check() error {\n\tif _, ok := _c.mutation.Email(); !ok {\n\t\treturn &ValidationError{Name: \"email\", err: errors.New(`db: missing required field \"Password.email\"`)}\n\t}\n\tif v, ok := _c.mutation.Email(); ok {\n\t\tif err := password.EmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"email\", err: fmt.Errorf(`db: validator failed for field \"Password.email\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Hash(); !ok {\n\t\treturn &ValidationError{Name: \"hash\", err: errors.New(`db: missing required field \"Password.hash\"`)}\n\t}\n\tif _, ok := _c.mutation.Username(); !ok {\n\t\treturn &ValidationError{Name: \"username\", err: errors.New(`db: missing required field \"Password.username\"`)}\n\t}\n\tif v, ok := _c.mutation.Username(); ok {\n\t\tif err := password.UsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"username\", err: fmt.Errorf(`db: validator failed for field \"Password.username\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Name(); !ok {\n\t\treturn &ValidationError{Name: \"name\", err: errors.New(`db: missing required field \"Password.name\"`)}\n\t}\n\tif _, ok := _c.mutation.PreferredUsername(); !ok {\n\t\treturn &ValidationError{Name: \"preferred_username\", err: errors.New(`db: missing required field \"Password.preferred_username\"`)}\n\t}\n\tif _, ok := _c.mutation.UserID(); !ok {\n\t\treturn &ValidationError{Name: \"user_id\", err: errors.New(`db: missing required field \"Password.user_id\"`)}\n\t}\n\tif v, ok := _c.mutation.UserID(); ok {\n\t\tif err := password.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"Password.user_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *PasswordCreate) sqlSave(ctx context.Context) (*Password, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tid := _spec.ID.Value.(int64)\n\t_node.ID = int(id)\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *PasswordCreate) createSpec() (*Password, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &Password{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(password.Table, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt))\n\t)\n\tif value, ok := _c.mutation.Email(); ok {\n\t\t_spec.SetField(password.FieldEmail, field.TypeString, value)\n\t\t_node.Email = value\n\t}\n\tif value, ok := _c.mutation.Hash(); ok {\n\t\t_spec.SetField(password.FieldHash, field.TypeBytes, value)\n\t\t_node.Hash = value\n\t}\n\tif value, ok := _c.mutation.Username(); ok {\n\t\t_spec.SetField(password.FieldUsername, field.TypeString, value)\n\t\t_node.Username = value\n\t}\n\tif value, ok := _c.mutation.Name(); ok {\n\t\t_spec.SetField(password.FieldName, field.TypeString, value)\n\t\t_node.Name = value\n\t}\n\tif value, ok := _c.mutation.PreferredUsername(); ok {\n\t\t_spec.SetField(password.FieldPreferredUsername, field.TypeString, value)\n\t\t_node.PreferredUsername = value\n\t}\n\tif value, ok := _c.mutation.EmailVerified(); ok {\n\t\t_spec.SetField(password.FieldEmailVerified, field.TypeBool, value)\n\t\t_node.EmailVerified = &value\n\t}\n\tif value, ok := _c.mutation.UserID(); ok {\n\t\t_spec.SetField(password.FieldUserID, field.TypeString, value)\n\t\t_node.UserID = value\n\t}\n\tif value, ok := _c.mutation.Groups(); ok {\n\t\t_spec.SetField(password.FieldGroups, field.TypeJSON, value)\n\t\t_node.Groups = value\n\t}\n\treturn _node, _spec\n}\n\n// PasswordCreateBulk is the builder for creating many Password entities in bulk.\ntype PasswordCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*PasswordCreate\n}\n\n// Save creates the Password entities in the database.\nfunc (_c *PasswordCreateBulk) Save(ctx context.Context) ([]*Password, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*Password, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tbuilder.defaults()\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*PasswordMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tif specs[i].ID.Value != nil {\n\t\t\t\t\tid := specs[i].ID.Value.(int64)\n\t\t\t\t\tnodes[i].ID = int(id)\n\t\t\t\t}\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *PasswordCreateBulk) SaveX(ctx context.Context) []*Password {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *PasswordCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *PasswordCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/password_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// PasswordDelete is the builder for deleting a Password entity.\ntype PasswordDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *PasswordMutation\n}\n\n// Where appends a list predicates to the PasswordDelete builder.\nfunc (_d *PasswordDelete) Where(ps ...predicate.Password) *PasswordDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *PasswordDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *PasswordDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *PasswordDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(password.Table, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// PasswordDeleteOne is the builder for deleting a single Password entity.\ntype PasswordDeleteOne struct {\n\t_d *PasswordDelete\n}\n\n// Where appends a list predicates to the PasswordDelete builder.\nfunc (_d *PasswordDeleteOne) Where(ps ...predicate.Password) *PasswordDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *PasswordDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{password.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *PasswordDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/password_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// PasswordQuery is the builder for querying Password entities.\ntype PasswordQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []password.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.Password\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the PasswordQuery builder.\nfunc (_q *PasswordQuery) Where(ps ...predicate.Password) *PasswordQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *PasswordQuery) Limit(limit int) *PasswordQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *PasswordQuery) Offset(offset int) *PasswordQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *PasswordQuery) Unique(unique bool) *PasswordQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *PasswordQuery) Order(o ...password.OrderOption) *PasswordQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first Password entity from the query.\n// Returns a *NotFoundError when no Password was found.\nfunc (_q *PasswordQuery) First(ctx context.Context) (*Password, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{password.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *PasswordQuery) FirstX(ctx context.Context) *Password {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first Password ID from the query.\n// Returns a *NotFoundError when no Password ID was found.\nfunc (_q *PasswordQuery) FirstID(ctx context.Context) (id int, err error) {\n\tvar ids []int\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{password.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *PasswordQuery) FirstIDX(ctx context.Context) int {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single Password entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one Password entity is found.\n// Returns a *NotFoundError when no Password entities are found.\nfunc (_q *PasswordQuery) Only(ctx context.Context) (*Password, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{password.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{password.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *PasswordQuery) OnlyX(ctx context.Context) *Password {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only Password ID in the query.\n// Returns a *NotSingularError when more than one Password ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *PasswordQuery) OnlyID(ctx context.Context) (id int, err error) {\n\tvar ids []int\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{password.Label}\n\tdefault:\n\t\terr = &NotSingularError{password.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *PasswordQuery) OnlyIDX(ctx context.Context) int {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of Passwords.\nfunc (_q *PasswordQuery) All(ctx context.Context) ([]*Password, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*Password, *PasswordQuery]()\n\treturn withInterceptors[[]*Password](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *PasswordQuery) AllX(ctx context.Context) []*Password {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of Password IDs.\nfunc (_q *PasswordQuery) IDs(ctx context.Context) (ids []int, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(password.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *PasswordQuery) IDsX(ctx context.Context) []int {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *PasswordQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*PasswordQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *PasswordQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *PasswordQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *PasswordQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the PasswordQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *PasswordQuery) Clone() *PasswordQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &PasswordQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]password.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.Password{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tEmail string `json:\"email,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.Password.Query().\n//\t\tGroupBy(password.FieldEmail).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *PasswordQuery) GroupBy(field string, fields ...string) *PasswordGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &PasswordGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = password.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tEmail string `json:\"email,omitempty\"`\n//\t}\n//\n//\tclient.Password.Query().\n//\t\tSelect(password.FieldEmail).\n//\t\tScan(ctx, &v)\nfunc (_q *PasswordQuery) Select(fields ...string) *PasswordSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &PasswordSelect{PasswordQuery: _q}\n\tsbuild.label = password.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a PasswordSelect configured with the given aggregations.\nfunc (_q *PasswordQuery) Aggregate(fns ...AggregateFunc) *PasswordSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *PasswordQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !password.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *PasswordQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*Password, error) {\n\tvar (\n\t\tnodes = []*Password{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*Password).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &Password{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *PasswordQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *PasswordQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(password.Table, password.Columns, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, password.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != password.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *PasswordQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(password.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = password.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// PasswordGroupBy is the group-by builder for Password entities.\ntype PasswordGroupBy struct {\n\tselector\n\tbuild *PasswordQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *PasswordGroupBy) Aggregate(fns ...AggregateFunc) *PasswordGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *PasswordGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*PasswordQuery, *PasswordGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *PasswordGroupBy) sqlScan(ctx context.Context, root *PasswordQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// PasswordSelect is the builder for selecting fields of Password entities.\ntype PasswordSelect struct {\n\t*PasswordQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *PasswordSelect) Aggregate(fns ...AggregateFunc) *PasswordSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *PasswordSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*PasswordQuery, *PasswordSelect](ctx, _s.PasswordQuery, _s, _s.inters, v)\n}\n\nfunc (_s *PasswordSelect) sqlScan(ctx context.Context, root *PasswordQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/password_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// PasswordUpdate is the builder for updating Password entities.\ntype PasswordUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *PasswordMutation\n}\n\n// Where appends a list predicates to the PasswordUpdate builder.\nfunc (_u *PasswordUpdate) Where(ps ...predicate.Password) *PasswordUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetEmail sets the \"email\" field.\nfunc (_u *PasswordUpdate) SetEmail(v string) *PasswordUpdate {\n\t_u.mutation.SetEmail(v)\n\treturn _u\n}\n\n// SetNillableEmail sets the \"email\" field if the given value is not nil.\nfunc (_u *PasswordUpdate) SetNillableEmail(v *string) *PasswordUpdate {\n\tif v != nil {\n\t\t_u.SetEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetHash sets the \"hash\" field.\nfunc (_u *PasswordUpdate) SetHash(v []byte) *PasswordUpdate {\n\t_u.mutation.SetHash(v)\n\treturn _u\n}\n\n// SetUsername sets the \"username\" field.\nfunc (_u *PasswordUpdate) SetUsername(v string) *PasswordUpdate {\n\t_u.mutation.SetUsername(v)\n\treturn _u\n}\n\n// SetNillableUsername sets the \"username\" field if the given value is not nil.\nfunc (_u *PasswordUpdate) SetNillableUsername(v *string) *PasswordUpdate {\n\tif v != nil {\n\t\t_u.SetUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetName sets the \"name\" field.\nfunc (_u *PasswordUpdate) SetName(v string) *PasswordUpdate {\n\t_u.mutation.SetName(v)\n\treturn _u\n}\n\n// SetNillableName sets the \"name\" field if the given value is not nil.\nfunc (_u *PasswordUpdate) SetNillableName(v *string) *PasswordUpdate {\n\tif v != nil {\n\t\t_u.SetName(*v)\n\t}\n\treturn _u\n}\n\n// SetPreferredUsername sets the \"preferred_username\" field.\nfunc (_u *PasswordUpdate) SetPreferredUsername(v string) *PasswordUpdate {\n\t_u.mutation.SetPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillablePreferredUsername sets the \"preferred_username\" field if the given value is not nil.\nfunc (_u *PasswordUpdate) SetNillablePreferredUsername(v *string) *PasswordUpdate {\n\tif v != nil {\n\t\t_u.SetPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetEmailVerified sets the \"email_verified\" field.\nfunc (_u *PasswordUpdate) SetEmailVerified(v bool) *PasswordUpdate {\n\t_u.mutation.SetEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableEmailVerified sets the \"email_verified\" field if the given value is not nil.\nfunc (_u *PasswordUpdate) SetNillableEmailVerified(v *bool) *PasswordUpdate {\n\tif v != nil {\n\t\t_u.SetEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// ClearEmailVerified clears the value of the \"email_verified\" field.\nfunc (_u *PasswordUpdate) ClearEmailVerified() *PasswordUpdate {\n\t_u.mutation.ClearEmailVerified()\n\treturn _u\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *PasswordUpdate) SetUserID(v string) *PasswordUpdate {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *PasswordUpdate) SetNillableUserID(v *string) *PasswordUpdate {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetGroups sets the \"groups\" field.\nfunc (_u *PasswordUpdate) SetGroups(v []string) *PasswordUpdate {\n\t_u.mutation.SetGroups(v)\n\treturn _u\n}\n\n// AppendGroups appends value to the \"groups\" field.\nfunc (_u *PasswordUpdate) AppendGroups(v []string) *PasswordUpdate {\n\t_u.mutation.AppendGroups(v)\n\treturn _u\n}\n\n// ClearGroups clears the value of the \"groups\" field.\nfunc (_u *PasswordUpdate) ClearGroups() *PasswordUpdate {\n\t_u.mutation.ClearGroups()\n\treturn _u\n}\n\n// Mutation returns the PasswordMutation object of the builder.\nfunc (_u *PasswordUpdate) Mutation() *PasswordMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *PasswordUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *PasswordUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *PasswordUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *PasswordUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *PasswordUpdate) check() error {\n\tif v, ok := _u.mutation.Email(); ok {\n\t\tif err := password.EmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"email\", err: fmt.Errorf(`db: validator failed for field \"Password.email\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Username(); ok {\n\t\tif err := password.UsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"username\", err: fmt.Errorf(`db: validator failed for field \"Password.username\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := password.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"Password.user_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *PasswordUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(password.Table, password.Columns, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.Email(); ok {\n\t\t_spec.SetField(password.FieldEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Hash(); ok {\n\t\t_spec.SetField(password.FieldHash, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.Username(); ok {\n\t\t_spec.SetField(password.FieldUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Name(); ok {\n\t\t_spec.SetField(password.FieldName, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.PreferredUsername(); ok {\n\t\t_spec.SetField(password.FieldPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.EmailVerified(); ok {\n\t\t_spec.SetField(password.FieldEmailVerified, field.TypeBool, value)\n\t}\n\tif _u.mutation.EmailVerifiedCleared() {\n\t\t_spec.ClearField(password.FieldEmailVerified, field.TypeBool)\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(password.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Groups(); ok {\n\t\t_spec.SetField(password.FieldGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, password.FieldGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.GroupsCleared() {\n\t\t_spec.ClearField(password.FieldGroups, field.TypeJSON)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{password.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// PasswordUpdateOne is the builder for updating a single Password entity.\ntype PasswordUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *PasswordMutation\n}\n\n// SetEmail sets the \"email\" field.\nfunc (_u *PasswordUpdateOne) SetEmail(v string) *PasswordUpdateOne {\n\t_u.mutation.SetEmail(v)\n\treturn _u\n}\n\n// SetNillableEmail sets the \"email\" field if the given value is not nil.\nfunc (_u *PasswordUpdateOne) SetNillableEmail(v *string) *PasswordUpdateOne {\n\tif v != nil {\n\t\t_u.SetEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetHash sets the \"hash\" field.\nfunc (_u *PasswordUpdateOne) SetHash(v []byte) *PasswordUpdateOne {\n\t_u.mutation.SetHash(v)\n\treturn _u\n}\n\n// SetUsername sets the \"username\" field.\nfunc (_u *PasswordUpdateOne) SetUsername(v string) *PasswordUpdateOne {\n\t_u.mutation.SetUsername(v)\n\treturn _u\n}\n\n// SetNillableUsername sets the \"username\" field if the given value is not nil.\nfunc (_u *PasswordUpdateOne) SetNillableUsername(v *string) *PasswordUpdateOne {\n\tif v != nil {\n\t\t_u.SetUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetName sets the \"name\" field.\nfunc (_u *PasswordUpdateOne) SetName(v string) *PasswordUpdateOne {\n\t_u.mutation.SetName(v)\n\treturn _u\n}\n\n// SetNillableName sets the \"name\" field if the given value is not nil.\nfunc (_u *PasswordUpdateOne) SetNillableName(v *string) *PasswordUpdateOne {\n\tif v != nil {\n\t\t_u.SetName(*v)\n\t}\n\treturn _u\n}\n\n// SetPreferredUsername sets the \"preferred_username\" field.\nfunc (_u *PasswordUpdateOne) SetPreferredUsername(v string) *PasswordUpdateOne {\n\t_u.mutation.SetPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillablePreferredUsername sets the \"preferred_username\" field if the given value is not nil.\nfunc (_u *PasswordUpdateOne) SetNillablePreferredUsername(v *string) *PasswordUpdateOne {\n\tif v != nil {\n\t\t_u.SetPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetEmailVerified sets the \"email_verified\" field.\nfunc (_u *PasswordUpdateOne) SetEmailVerified(v bool) *PasswordUpdateOne {\n\t_u.mutation.SetEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableEmailVerified sets the \"email_verified\" field if the given value is not nil.\nfunc (_u *PasswordUpdateOne) SetNillableEmailVerified(v *bool) *PasswordUpdateOne {\n\tif v != nil {\n\t\t_u.SetEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// ClearEmailVerified clears the value of the \"email_verified\" field.\nfunc (_u *PasswordUpdateOne) ClearEmailVerified() *PasswordUpdateOne {\n\t_u.mutation.ClearEmailVerified()\n\treturn _u\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *PasswordUpdateOne) SetUserID(v string) *PasswordUpdateOne {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *PasswordUpdateOne) SetNillableUserID(v *string) *PasswordUpdateOne {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetGroups sets the \"groups\" field.\nfunc (_u *PasswordUpdateOne) SetGroups(v []string) *PasswordUpdateOne {\n\t_u.mutation.SetGroups(v)\n\treturn _u\n}\n\n// AppendGroups appends value to the \"groups\" field.\nfunc (_u *PasswordUpdateOne) AppendGroups(v []string) *PasswordUpdateOne {\n\t_u.mutation.AppendGroups(v)\n\treturn _u\n}\n\n// ClearGroups clears the value of the \"groups\" field.\nfunc (_u *PasswordUpdateOne) ClearGroups() *PasswordUpdateOne {\n\t_u.mutation.ClearGroups()\n\treturn _u\n}\n\n// Mutation returns the PasswordMutation object of the builder.\nfunc (_u *PasswordUpdateOne) Mutation() *PasswordMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the PasswordUpdate builder.\nfunc (_u *PasswordUpdateOne) Where(ps ...predicate.Password) *PasswordUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *PasswordUpdateOne) Select(field string, fields ...string) *PasswordUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated Password entity.\nfunc (_u *PasswordUpdateOne) Save(ctx context.Context) (*Password, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *PasswordUpdateOne) SaveX(ctx context.Context) *Password {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *PasswordUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *PasswordUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *PasswordUpdateOne) check() error {\n\tif v, ok := _u.mutation.Email(); ok {\n\t\tif err := password.EmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"email\", err: fmt.Errorf(`db: validator failed for field \"Password.email\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Username(); ok {\n\t\tif err := password.UsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"username\", err: fmt.Errorf(`db: validator failed for field \"Password.username\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := password.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"Password.user_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *PasswordUpdateOne) sqlSave(ctx context.Context) (_node *Password, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(password.Table, password.Columns, sqlgraph.NewFieldSpec(password.FieldID, field.TypeInt))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"Password.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, password.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !password.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != password.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.Email(); ok {\n\t\t_spec.SetField(password.FieldEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Hash(); ok {\n\t\t_spec.SetField(password.FieldHash, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.Username(); ok {\n\t\t_spec.SetField(password.FieldUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Name(); ok {\n\t\t_spec.SetField(password.FieldName, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.PreferredUsername(); ok {\n\t\t_spec.SetField(password.FieldPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.EmailVerified(); ok {\n\t\t_spec.SetField(password.FieldEmailVerified, field.TypeBool, value)\n\t}\n\tif _u.mutation.EmailVerifiedCleared() {\n\t\t_spec.ClearField(password.FieldEmailVerified, field.TypeBool)\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(password.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Groups(); ok {\n\t\t_spec.SetField(password.FieldGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, password.FieldGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.GroupsCleared() {\n\t\t_spec.ClearField(password.FieldGroups, field.TypeJSON)\n\t}\n\t_node = &Password{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{password.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/predicate/predicate.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage predicate\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\n// AuthCode is the predicate function for authcode builders.\ntype AuthCode func(*sql.Selector)\n\n// AuthRequest is the predicate function for authrequest builders.\ntype AuthRequest func(*sql.Selector)\n\n// AuthSession is the predicate function for authsession builders.\ntype AuthSession func(*sql.Selector)\n\n// Connector is the predicate function for connector builders.\ntype Connector func(*sql.Selector)\n\n// DeviceRequest is the predicate function for devicerequest builders.\ntype DeviceRequest func(*sql.Selector)\n\n// DeviceToken is the predicate function for devicetoken builders.\ntype DeviceToken func(*sql.Selector)\n\n// Keys is the predicate function for keys builders.\ntype Keys func(*sql.Selector)\n\n// OAuth2Client is the predicate function for oauth2client builders.\ntype OAuth2Client func(*sql.Selector)\n\n// OfflineSession is the predicate function for offlinesession builders.\ntype OfflineSession func(*sql.Selector)\n\n// Password is the predicate function for password builders.\ntype Password func(*sql.Selector)\n\n// RefreshToken is the predicate function for refreshtoken builders.\ntype RefreshToken func(*sql.Selector)\n\n// UserIdentity is the predicate function for useridentity builders.\ntype UserIdentity func(*sql.Selector)\n"
  },
  {
    "path": "storage/ent/db/refreshtoken/refreshtoken.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage refreshtoken\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the refreshtoken type in the database.\n\tLabel = \"refresh_token\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldClientID holds the string denoting the client_id field in the database.\n\tFieldClientID = \"client_id\"\n\t// FieldScopes holds the string denoting the scopes field in the database.\n\tFieldScopes = \"scopes\"\n\t// FieldNonce holds the string denoting the nonce field in the database.\n\tFieldNonce = \"nonce\"\n\t// FieldClaimsUserID holds the string denoting the claims_user_id field in the database.\n\tFieldClaimsUserID = \"claims_user_id\"\n\t// FieldClaimsUsername holds the string denoting the claims_username field in the database.\n\tFieldClaimsUsername = \"claims_username\"\n\t// FieldClaimsEmail holds the string denoting the claims_email field in the database.\n\tFieldClaimsEmail = \"claims_email\"\n\t// FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database.\n\tFieldClaimsEmailVerified = \"claims_email_verified\"\n\t// FieldClaimsGroups holds the string denoting the claims_groups field in the database.\n\tFieldClaimsGroups = \"claims_groups\"\n\t// FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database.\n\tFieldClaimsPreferredUsername = \"claims_preferred_username\"\n\t// FieldConnectorID holds the string denoting the connector_id field in the database.\n\tFieldConnectorID = \"connector_id\"\n\t// FieldConnectorData holds the string denoting the connector_data field in the database.\n\tFieldConnectorData = \"connector_data\"\n\t// FieldToken holds the string denoting the token field in the database.\n\tFieldToken = \"token\"\n\t// FieldObsoleteToken holds the string denoting the obsolete_token field in the database.\n\tFieldObsoleteToken = \"obsolete_token\"\n\t// FieldCreatedAt holds the string denoting the created_at field in the database.\n\tFieldCreatedAt = \"created_at\"\n\t// FieldLastUsed holds the string denoting the last_used field in the database.\n\tFieldLastUsed = \"last_used\"\n\t// Table holds the table name of the refreshtoken in the database.\n\tTable = \"refresh_tokens\"\n)\n\n// Columns holds all SQL columns for refreshtoken fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldClientID,\n\tFieldScopes,\n\tFieldNonce,\n\tFieldClaimsUserID,\n\tFieldClaimsUsername,\n\tFieldClaimsEmail,\n\tFieldClaimsEmailVerified,\n\tFieldClaimsGroups,\n\tFieldClaimsPreferredUsername,\n\tFieldConnectorID,\n\tFieldConnectorData,\n\tFieldToken,\n\tFieldObsoleteToken,\n\tFieldCreatedAt,\n\tFieldLastUsed,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// ClientIDValidator is a validator for the \"client_id\" field. It is called by the builders before save.\n\tClientIDValidator func(string) error\n\t// NonceValidator is a validator for the \"nonce\" field. It is called by the builders before save.\n\tNonceValidator func(string) error\n\t// ClaimsUserIDValidator is a validator for the \"claims_user_id\" field. It is called by the builders before save.\n\tClaimsUserIDValidator func(string) error\n\t// ClaimsUsernameValidator is a validator for the \"claims_username\" field. It is called by the builders before save.\n\tClaimsUsernameValidator func(string) error\n\t// ClaimsEmailValidator is a validator for the \"claims_email\" field. It is called by the builders before save.\n\tClaimsEmailValidator func(string) error\n\t// DefaultClaimsPreferredUsername holds the default value on creation for the \"claims_preferred_username\" field.\n\tDefaultClaimsPreferredUsername string\n\t// ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\tConnectorIDValidator func(string) error\n\t// DefaultToken holds the default value on creation for the \"token\" field.\n\tDefaultToken string\n\t// DefaultObsoleteToken holds the default value on creation for the \"obsolete_token\" field.\n\tDefaultObsoleteToken string\n\t// DefaultCreatedAt holds the default value on creation for the \"created_at\" field.\n\tDefaultCreatedAt func() time.Time\n\t// DefaultLastUsed holds the default value on creation for the \"last_used\" field.\n\tDefaultLastUsed func() time.Time\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the RefreshToken queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByClientID orders the results by the client_id field.\nfunc ByClientID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClientID, opts...).ToFunc()\n}\n\n// ByNonce orders the results by the nonce field.\nfunc ByNonce(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldNonce, opts...).ToFunc()\n}\n\n// ByClaimsUserID orders the results by the claims_user_id field.\nfunc ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUserID, opts...).ToFunc()\n}\n\n// ByClaimsUsername orders the results by the claims_username field.\nfunc ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUsername, opts...).ToFunc()\n}\n\n// ByClaimsEmail orders the results by the claims_email field.\nfunc ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmail, opts...).ToFunc()\n}\n\n// ByClaimsEmailVerified orders the results by the claims_email_verified field.\nfunc ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc()\n}\n\n// ByClaimsPreferredUsername orders the results by the claims_preferred_username field.\nfunc ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc()\n}\n\n// ByConnectorID orders the results by the connector_id field.\nfunc ByConnectorID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldConnectorID, opts...).ToFunc()\n}\n\n// ByToken orders the results by the token field.\nfunc ByToken(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldToken, opts...).ToFunc()\n}\n\n// ByObsoleteToken orders the results by the obsolete_token field.\nfunc ByObsoleteToken(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldObsoleteToken, opts...).ToFunc()\n}\n\n// ByCreatedAt orders the results by the created_at field.\nfunc ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCreatedAt, opts...).ToFunc()\n}\n\n// ByLastUsed orders the results by the last_used field.\nfunc ByLastUsed(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldLastUsed, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/refreshtoken/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage refreshtoken\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldID, id))\n}\n\n// ClientID applies equality check predicate on the \"client_id\" field. It's identical to ClientIDEQ.\nfunc ClientID(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClientID, v))\n}\n\n// Nonce applies equality check predicate on the \"nonce\" field. It's identical to NonceEQ.\nfunc Nonce(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldNonce, v))\n}\n\n// ClaimsUserID applies equality check predicate on the \"claims_user_id\" field. It's identical to ClaimsUserIDEQ.\nfunc ClaimsUserID(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUsername applies equality check predicate on the \"claims_username\" field. It's identical to ClaimsUsernameEQ.\nfunc ClaimsUsername(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsEmail applies equality check predicate on the \"claims_email\" field. It's identical to ClaimsEmailEQ.\nfunc ClaimsEmail(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerified applies equality check predicate on the \"claims_email_verified\" field. It's identical to ClaimsEmailVerifiedEQ.\nfunc ClaimsEmailVerified(v bool) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsPreferredUsername applies equality check predicate on the \"claims_preferred_username\" field. It's identical to ClaimsPreferredUsernameEQ.\nfunc ClaimsPreferredUsername(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ConnectorID applies equality check predicate on the \"connector_id\" field. It's identical to ConnectorIDEQ.\nfunc ConnectorID(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorData applies equality check predicate on the \"connector_data\" field. It's identical to ConnectorDataEQ.\nfunc ConnectorData(v []byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// Token applies equality check predicate on the \"token\" field. It's identical to TokenEQ.\nfunc Token(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldToken, v))\n}\n\n// ObsoleteToken applies equality check predicate on the \"obsolete_token\" field. It's identical to ObsoleteTokenEQ.\nfunc ObsoleteToken(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldObsoleteToken, v))\n}\n\n// CreatedAt applies equality check predicate on the \"created_at\" field. It's identical to CreatedAtEQ.\nfunc CreatedAt(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldCreatedAt, v))\n}\n\n// LastUsed applies equality check predicate on the \"last_used\" field. It's identical to LastUsedEQ.\nfunc LastUsed(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldLastUsed, v))\n}\n\n// ClientIDEQ applies the EQ predicate on the \"client_id\" field.\nfunc ClientIDEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClientID, v))\n}\n\n// ClientIDNEQ applies the NEQ predicate on the \"client_id\" field.\nfunc ClientIDNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldClientID, v))\n}\n\n// ClientIDIn applies the In predicate on the \"client_id\" field.\nfunc ClientIDIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldClientID, vs...))\n}\n\n// ClientIDNotIn applies the NotIn predicate on the \"client_id\" field.\nfunc ClientIDNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldClientID, vs...))\n}\n\n// ClientIDGT applies the GT predicate on the \"client_id\" field.\nfunc ClientIDGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldClientID, v))\n}\n\n// ClientIDGTE applies the GTE predicate on the \"client_id\" field.\nfunc ClientIDGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldClientID, v))\n}\n\n// ClientIDLT applies the LT predicate on the \"client_id\" field.\nfunc ClientIDLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldClientID, v))\n}\n\n// ClientIDLTE applies the LTE predicate on the \"client_id\" field.\nfunc ClientIDLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldClientID, v))\n}\n\n// ClientIDContains applies the Contains predicate on the \"client_id\" field.\nfunc ClientIDContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldClientID, v))\n}\n\n// ClientIDHasPrefix applies the HasPrefix predicate on the \"client_id\" field.\nfunc ClientIDHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldClientID, v))\n}\n\n// ClientIDHasSuffix applies the HasSuffix predicate on the \"client_id\" field.\nfunc ClientIDHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldClientID, v))\n}\n\n// ClientIDEqualFold applies the EqualFold predicate on the \"client_id\" field.\nfunc ClientIDEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldClientID, v))\n}\n\n// ClientIDContainsFold applies the ContainsFold predicate on the \"client_id\" field.\nfunc ClientIDContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldClientID, v))\n}\n\n// ScopesIsNil applies the IsNil predicate on the \"scopes\" field.\nfunc ScopesIsNil() predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIsNull(FieldScopes))\n}\n\n// ScopesNotNil applies the NotNil predicate on the \"scopes\" field.\nfunc ScopesNotNil() predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotNull(FieldScopes))\n}\n\n// NonceEQ applies the EQ predicate on the \"nonce\" field.\nfunc NonceEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldNonce, v))\n}\n\n// NonceNEQ applies the NEQ predicate on the \"nonce\" field.\nfunc NonceNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldNonce, v))\n}\n\n// NonceIn applies the In predicate on the \"nonce\" field.\nfunc NonceIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldNonce, vs...))\n}\n\n// NonceNotIn applies the NotIn predicate on the \"nonce\" field.\nfunc NonceNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldNonce, vs...))\n}\n\n// NonceGT applies the GT predicate on the \"nonce\" field.\nfunc NonceGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldNonce, v))\n}\n\n// NonceGTE applies the GTE predicate on the \"nonce\" field.\nfunc NonceGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldNonce, v))\n}\n\n// NonceLT applies the LT predicate on the \"nonce\" field.\nfunc NonceLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldNonce, v))\n}\n\n// NonceLTE applies the LTE predicate on the \"nonce\" field.\nfunc NonceLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldNonce, v))\n}\n\n// NonceContains applies the Contains predicate on the \"nonce\" field.\nfunc NonceContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldNonce, v))\n}\n\n// NonceHasPrefix applies the HasPrefix predicate on the \"nonce\" field.\nfunc NonceHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldNonce, v))\n}\n\n// NonceHasSuffix applies the HasSuffix predicate on the \"nonce\" field.\nfunc NonceHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldNonce, v))\n}\n\n// NonceEqualFold applies the EqualFold predicate on the \"nonce\" field.\nfunc NonceEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldNonce, v))\n}\n\n// NonceContainsFold applies the ContainsFold predicate on the \"nonce\" field.\nfunc NonceContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldNonce, v))\n}\n\n// ClaimsUserIDEQ applies the EQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDNEQ applies the NEQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDIn applies the In predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDNotIn applies the NotIn predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDGT applies the GT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDGTE applies the GTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLT applies the LT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLTE applies the LTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContains applies the Contains predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasPrefix applies the HasPrefix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasSuffix applies the HasSuffix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDEqualFold applies the EqualFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContainsFold applies the ContainsFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUsernameEQ applies the EQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameNEQ applies the NEQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameIn applies the In predicate on the \"claims_username\" field.\nfunc ClaimsUsernameIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameNotIn applies the NotIn predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameGT applies the GT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameGTE applies the GTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLT applies the LT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLTE applies the LTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContains applies the Contains predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasPrefix applies the HasPrefix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasSuffix applies the HasSuffix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameEqualFold applies the EqualFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContainsFold applies the ContainsFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsUsername, v))\n}\n\n// ClaimsEmailEQ applies the EQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailNEQ applies the NEQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailIn applies the In predicate on the \"claims_email\" field.\nfunc ClaimsEmailIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailNotIn applies the NotIn predicate on the \"claims_email\" field.\nfunc ClaimsEmailNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailGT applies the GT predicate on the \"claims_email\" field.\nfunc ClaimsEmailGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailGTE applies the GTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLT applies the LT predicate on the \"claims_email\" field.\nfunc ClaimsEmailLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLTE applies the LTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContains applies the Contains predicate on the \"claims_email\" field.\nfunc ClaimsEmailContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasPrefix applies the HasPrefix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasSuffix applies the HasSuffix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailEqualFold applies the EqualFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContainsFold applies the ContainsFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerifiedEQ applies the EQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedEQ(v bool) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsEmailVerifiedNEQ applies the NEQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedNEQ(v bool) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsGroupsIsNil applies the IsNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsIsNil() predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIsNull(FieldClaimsGroups))\n}\n\n// ClaimsGroupsNotNil applies the NotNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsNotNil() predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotNull(FieldClaimsGroups))\n}\n\n// ClaimsPreferredUsernameEQ applies the EQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameNEQ applies the NEQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameIn applies the In predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameNotIn applies the NotIn predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameGT applies the GT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameGTE applies the GTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLT applies the LT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLTE applies the LTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContains applies the Contains predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldClaimsPreferredUsername, v))\n}\n\n// ConnectorIDEQ applies the EQ predicate on the \"connector_id\" field.\nfunc ConnectorIDEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDNEQ applies the NEQ predicate on the \"connector_id\" field.\nfunc ConnectorIDNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDIn applies the In predicate on the \"connector_id\" field.\nfunc ConnectorIDIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDNotIn applies the NotIn predicate on the \"connector_id\" field.\nfunc ConnectorIDNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDGT applies the GT predicate on the \"connector_id\" field.\nfunc ConnectorIDGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldConnectorID, v))\n}\n\n// ConnectorIDGTE applies the GTE predicate on the \"connector_id\" field.\nfunc ConnectorIDGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldConnectorID, v))\n}\n\n// ConnectorIDLT applies the LT predicate on the \"connector_id\" field.\nfunc ConnectorIDLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldConnectorID, v))\n}\n\n// ConnectorIDLTE applies the LTE predicate on the \"connector_id\" field.\nfunc ConnectorIDLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldConnectorID, v))\n}\n\n// ConnectorIDContains applies the Contains predicate on the \"connector_id\" field.\nfunc ConnectorIDContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldConnectorID, v))\n}\n\n// ConnectorIDHasPrefix applies the HasPrefix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldConnectorID, v))\n}\n\n// ConnectorIDHasSuffix applies the HasSuffix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldConnectorID, v))\n}\n\n// ConnectorIDEqualFold applies the EqualFold predicate on the \"connector_id\" field.\nfunc ConnectorIDEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldConnectorID, v))\n}\n\n// ConnectorIDContainsFold applies the ContainsFold predicate on the \"connector_id\" field.\nfunc ConnectorIDContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldConnectorID, v))\n}\n\n// ConnectorDataEQ applies the EQ predicate on the \"connector_data\" field.\nfunc ConnectorDataEQ(v []byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataNEQ applies the NEQ predicate on the \"connector_data\" field.\nfunc ConnectorDataNEQ(v []byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldConnectorData, v))\n}\n\n// ConnectorDataIn applies the In predicate on the \"connector_data\" field.\nfunc ConnectorDataIn(vs ...[]byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataNotIn applies the NotIn predicate on the \"connector_data\" field.\nfunc ConnectorDataNotIn(vs ...[]byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldConnectorData, vs...))\n}\n\n// ConnectorDataGT applies the GT predicate on the \"connector_data\" field.\nfunc ConnectorDataGT(v []byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldConnectorData, v))\n}\n\n// ConnectorDataGTE applies the GTE predicate on the \"connector_data\" field.\nfunc ConnectorDataGTE(v []byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldConnectorData, v))\n}\n\n// ConnectorDataLT applies the LT predicate on the \"connector_data\" field.\nfunc ConnectorDataLT(v []byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldConnectorData, v))\n}\n\n// ConnectorDataLTE applies the LTE predicate on the \"connector_data\" field.\nfunc ConnectorDataLTE(v []byte) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldConnectorData, v))\n}\n\n// ConnectorDataIsNil applies the IsNil predicate on the \"connector_data\" field.\nfunc ConnectorDataIsNil() predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIsNull(FieldConnectorData))\n}\n\n// ConnectorDataNotNil applies the NotNil predicate on the \"connector_data\" field.\nfunc ConnectorDataNotNil() predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotNull(FieldConnectorData))\n}\n\n// TokenEQ applies the EQ predicate on the \"token\" field.\nfunc TokenEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldToken, v))\n}\n\n// TokenNEQ applies the NEQ predicate on the \"token\" field.\nfunc TokenNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldToken, v))\n}\n\n// TokenIn applies the In predicate on the \"token\" field.\nfunc TokenIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldToken, vs...))\n}\n\n// TokenNotIn applies the NotIn predicate on the \"token\" field.\nfunc TokenNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldToken, vs...))\n}\n\n// TokenGT applies the GT predicate on the \"token\" field.\nfunc TokenGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldToken, v))\n}\n\n// TokenGTE applies the GTE predicate on the \"token\" field.\nfunc TokenGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldToken, v))\n}\n\n// TokenLT applies the LT predicate on the \"token\" field.\nfunc TokenLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldToken, v))\n}\n\n// TokenLTE applies the LTE predicate on the \"token\" field.\nfunc TokenLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldToken, v))\n}\n\n// TokenContains applies the Contains predicate on the \"token\" field.\nfunc TokenContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldToken, v))\n}\n\n// TokenHasPrefix applies the HasPrefix predicate on the \"token\" field.\nfunc TokenHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldToken, v))\n}\n\n// TokenHasSuffix applies the HasSuffix predicate on the \"token\" field.\nfunc TokenHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldToken, v))\n}\n\n// TokenEqualFold applies the EqualFold predicate on the \"token\" field.\nfunc TokenEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldToken, v))\n}\n\n// TokenContainsFold applies the ContainsFold predicate on the \"token\" field.\nfunc TokenContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldToken, v))\n}\n\n// ObsoleteTokenEQ applies the EQ predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenNEQ applies the NEQ predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenNEQ(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenIn applies the In predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldObsoleteToken, vs...))\n}\n\n// ObsoleteTokenNotIn applies the NotIn predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenNotIn(vs ...string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldObsoleteToken, vs...))\n}\n\n// ObsoleteTokenGT applies the GT predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenGT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenGTE applies the GTE predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenGTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenLT applies the LT predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenLT(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenLTE applies the LTE predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenLTE(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenContains applies the Contains predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenContains(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContains(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenHasPrefix applies the HasPrefix predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenHasPrefix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasPrefix(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenHasSuffix applies the HasSuffix predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenHasSuffix(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldHasSuffix(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenEqualFold applies the EqualFold predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenEqualFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEqualFold(FieldObsoleteToken, v))\n}\n\n// ObsoleteTokenContainsFold applies the ContainsFold predicate on the \"obsolete_token\" field.\nfunc ObsoleteTokenContainsFold(v string) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldContainsFold(FieldObsoleteToken, v))\n}\n\n// CreatedAtEQ applies the EQ predicate on the \"created_at\" field.\nfunc CreatedAtEQ(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldCreatedAt, v))\n}\n\n// CreatedAtNEQ applies the NEQ predicate on the \"created_at\" field.\nfunc CreatedAtNEQ(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldCreatedAt, v))\n}\n\n// CreatedAtIn applies the In predicate on the \"created_at\" field.\nfunc CreatedAtIn(vs ...time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldCreatedAt, vs...))\n}\n\n// CreatedAtNotIn applies the NotIn predicate on the \"created_at\" field.\nfunc CreatedAtNotIn(vs ...time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldCreatedAt, vs...))\n}\n\n// CreatedAtGT applies the GT predicate on the \"created_at\" field.\nfunc CreatedAtGT(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldCreatedAt, v))\n}\n\n// CreatedAtGTE applies the GTE predicate on the \"created_at\" field.\nfunc CreatedAtGTE(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldCreatedAt, v))\n}\n\n// CreatedAtLT applies the LT predicate on the \"created_at\" field.\nfunc CreatedAtLT(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldCreatedAt, v))\n}\n\n// CreatedAtLTE applies the LTE predicate on the \"created_at\" field.\nfunc CreatedAtLTE(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldCreatedAt, v))\n}\n\n// LastUsedEQ applies the EQ predicate on the \"last_used\" field.\nfunc LastUsedEQ(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldEQ(FieldLastUsed, v))\n}\n\n// LastUsedNEQ applies the NEQ predicate on the \"last_used\" field.\nfunc LastUsedNEQ(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNEQ(FieldLastUsed, v))\n}\n\n// LastUsedIn applies the In predicate on the \"last_used\" field.\nfunc LastUsedIn(vs ...time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldIn(FieldLastUsed, vs...))\n}\n\n// LastUsedNotIn applies the NotIn predicate on the \"last_used\" field.\nfunc LastUsedNotIn(vs ...time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldNotIn(FieldLastUsed, vs...))\n}\n\n// LastUsedGT applies the GT predicate on the \"last_used\" field.\nfunc LastUsedGT(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGT(FieldLastUsed, v))\n}\n\n// LastUsedGTE applies the GTE predicate on the \"last_used\" field.\nfunc LastUsedGTE(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldGTE(FieldLastUsed, v))\n}\n\n// LastUsedLT applies the LT predicate on the \"last_used\" field.\nfunc LastUsedLT(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLT(FieldLastUsed, v))\n}\n\n// LastUsedLTE applies the LTE predicate on the \"last_used\" field.\nfunc LastUsedLTE(v time.Time) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.FieldLTE(FieldLastUsed, v))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.RefreshToken) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.RefreshToken) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.RefreshToken) predicate.RefreshToken {\n\treturn predicate.RefreshToken(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/refreshtoken.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n)\n\n// RefreshToken is the model entity for the RefreshToken schema.\ntype RefreshToken struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// ClientID holds the value of the \"client_id\" field.\n\tClientID string `json:\"client_id,omitempty\"`\n\t// Scopes holds the value of the \"scopes\" field.\n\tScopes []string `json:\"scopes,omitempty\"`\n\t// Nonce holds the value of the \"nonce\" field.\n\tNonce string `json:\"nonce,omitempty\"`\n\t// ClaimsUserID holds the value of the \"claims_user_id\" field.\n\tClaimsUserID string `json:\"claims_user_id,omitempty\"`\n\t// ClaimsUsername holds the value of the \"claims_username\" field.\n\tClaimsUsername string `json:\"claims_username,omitempty\"`\n\t// ClaimsEmail holds the value of the \"claims_email\" field.\n\tClaimsEmail string `json:\"claims_email,omitempty\"`\n\t// ClaimsEmailVerified holds the value of the \"claims_email_verified\" field.\n\tClaimsEmailVerified bool `json:\"claims_email_verified,omitempty\"`\n\t// ClaimsGroups holds the value of the \"claims_groups\" field.\n\tClaimsGroups []string `json:\"claims_groups,omitempty\"`\n\t// ClaimsPreferredUsername holds the value of the \"claims_preferred_username\" field.\n\tClaimsPreferredUsername string `json:\"claims_preferred_username,omitempty\"`\n\t// ConnectorID holds the value of the \"connector_id\" field.\n\tConnectorID string `json:\"connector_id,omitempty\"`\n\t// ConnectorData holds the value of the \"connector_data\" field.\n\tConnectorData *[]byte `json:\"connector_data,omitempty\"`\n\t// Token holds the value of the \"token\" field.\n\tToken string `json:\"token,omitempty\"`\n\t// ObsoleteToken holds the value of the \"obsolete_token\" field.\n\tObsoleteToken string `json:\"obsolete_token,omitempty\"`\n\t// CreatedAt holds the value of the \"created_at\" field.\n\tCreatedAt time.Time `json:\"created_at,omitempty\"`\n\t// LastUsed holds the value of the \"last_used\" field.\n\tLastUsed     time.Time `json:\"last_used,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*RefreshToken) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase refreshtoken.FieldScopes, refreshtoken.FieldClaimsGroups, refreshtoken.FieldConnectorData:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase refreshtoken.FieldClaimsEmailVerified:\n\t\t\tvalues[i] = new(sql.NullBool)\n\t\tcase refreshtoken.FieldID, refreshtoken.FieldClientID, refreshtoken.FieldNonce, refreshtoken.FieldClaimsUserID, refreshtoken.FieldClaimsUsername, refreshtoken.FieldClaimsEmail, refreshtoken.FieldClaimsPreferredUsername, refreshtoken.FieldConnectorID, refreshtoken.FieldToken, refreshtoken.FieldObsoleteToken:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase refreshtoken.FieldCreatedAt, refreshtoken.FieldLastUsed:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the RefreshToken fields.\nfunc (_m *RefreshToken) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase refreshtoken.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldClientID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field client_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClientID = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldScopes:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field scopes\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.Scopes); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field scopes: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase refreshtoken.FieldNonce:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field nonce\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Nonce = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldClaimsUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUserID = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldClaimsUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUsername = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldClaimsEmail:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmail = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldClaimsEmailVerified:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email_verified\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmailVerified = value.Bool\n\t\t\t}\n\t\tcase refreshtoken.FieldClaimsGroups:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_groups\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field claims_groups: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase refreshtoken.FieldClaimsPreferredUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_preferred_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsPreferredUsername = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldConnectorID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ConnectorID = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldConnectorData:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_data\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.ConnectorData = value\n\t\t\t}\n\t\tcase refreshtoken.FieldToken:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field token\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.Token = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldObsoleteToken:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field obsolete_token\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ObsoleteToken = value.String\n\t\t\t}\n\t\tcase refreshtoken.FieldCreatedAt:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field created_at\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CreatedAt = value.Time\n\t\t\t}\n\t\tcase refreshtoken.FieldLastUsed:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field last_used\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.LastUsed = value.Time\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the RefreshToken.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *RefreshToken) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this RefreshToken.\n// Note that you need to call RefreshToken.Unwrap() before calling this method if this RefreshToken\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *RefreshToken) Update() *RefreshTokenUpdateOne {\n\treturn NewRefreshTokenClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the RefreshToken entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *RefreshToken) Unwrap() *RefreshToken {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: RefreshToken is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *RefreshToken) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"RefreshToken(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"client_id=\")\n\tbuilder.WriteString(_m.ClientID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"scopes=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Scopes))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"nonce=\")\n\tbuilder.WriteString(_m.Nonce)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_user_id=\")\n\tbuilder.WriteString(_m.ClaimsUserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_username=\")\n\tbuilder.WriteString(_m.ClaimsUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email=\")\n\tbuilder.WriteString(_m.ClaimsEmail)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email_verified=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsEmailVerified))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_groups=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsGroups))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_preferred_username=\")\n\tbuilder.WriteString(_m.ClaimsPreferredUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"connector_id=\")\n\tbuilder.WriteString(_m.ConnectorID)\n\tbuilder.WriteString(\", \")\n\tif v := _m.ConnectorData; v != nil {\n\t\tbuilder.WriteString(\"connector_data=\")\n\t\tbuilder.WriteString(fmt.Sprintf(\"%v\", *v))\n\t}\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"token=\")\n\tbuilder.WriteString(_m.Token)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"obsolete_token=\")\n\tbuilder.WriteString(_m.ObsoleteToken)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"created_at=\")\n\tbuilder.WriteString(_m.CreatedAt.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"last_used=\")\n\tbuilder.WriteString(_m.LastUsed.Format(time.ANSIC))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// RefreshTokens is a parsable slice of RefreshToken.\ntype RefreshTokens []*RefreshToken\n"
  },
  {
    "path": "storage/ent/db/refreshtoken_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n)\n\n// RefreshTokenCreate is the builder for creating a RefreshToken entity.\ntype RefreshTokenCreate struct {\n\tconfig\n\tmutation *RefreshTokenMutation\n\thooks    []Hook\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_c *RefreshTokenCreate) SetClientID(v string) *RefreshTokenCreate {\n\t_c.mutation.SetClientID(v)\n\treturn _c\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_c *RefreshTokenCreate) SetScopes(v []string) *RefreshTokenCreate {\n\t_c.mutation.SetScopes(v)\n\treturn _c\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_c *RefreshTokenCreate) SetNonce(v string) *RefreshTokenCreate {\n\t_c.mutation.SetNonce(v)\n\treturn _c\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_c *RefreshTokenCreate) SetClaimsUserID(v string) *RefreshTokenCreate {\n\t_c.mutation.SetClaimsUserID(v)\n\treturn _c\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_c *RefreshTokenCreate) SetClaimsUsername(v string) *RefreshTokenCreate {\n\t_c.mutation.SetClaimsUsername(v)\n\treturn _c\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_c *RefreshTokenCreate) SetClaimsEmail(v string) *RefreshTokenCreate {\n\t_c.mutation.SetClaimsEmail(v)\n\treturn _c\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_c *RefreshTokenCreate) SetClaimsEmailVerified(v bool) *RefreshTokenCreate {\n\t_c.mutation.SetClaimsEmailVerified(v)\n\treturn _c\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_c *RefreshTokenCreate) SetClaimsGroups(v []string) *RefreshTokenCreate {\n\t_c.mutation.SetClaimsGroups(v)\n\treturn _c\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_c *RefreshTokenCreate) SetClaimsPreferredUsername(v string) *RefreshTokenCreate {\n\t_c.mutation.SetClaimsPreferredUsername(v)\n\treturn _c\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_c *RefreshTokenCreate) SetNillableClaimsPreferredUsername(v *string) *RefreshTokenCreate {\n\tif v != nil {\n\t\t_c.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _c\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_c *RefreshTokenCreate) SetConnectorID(v string) *RefreshTokenCreate {\n\t_c.mutation.SetConnectorID(v)\n\treturn _c\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_c *RefreshTokenCreate) SetConnectorData(v []byte) *RefreshTokenCreate {\n\t_c.mutation.SetConnectorData(v)\n\treturn _c\n}\n\n// SetToken sets the \"token\" field.\nfunc (_c *RefreshTokenCreate) SetToken(v string) *RefreshTokenCreate {\n\t_c.mutation.SetToken(v)\n\treturn _c\n}\n\n// SetNillableToken sets the \"token\" field if the given value is not nil.\nfunc (_c *RefreshTokenCreate) SetNillableToken(v *string) *RefreshTokenCreate {\n\tif v != nil {\n\t\t_c.SetToken(*v)\n\t}\n\treturn _c\n}\n\n// SetObsoleteToken sets the \"obsolete_token\" field.\nfunc (_c *RefreshTokenCreate) SetObsoleteToken(v string) *RefreshTokenCreate {\n\t_c.mutation.SetObsoleteToken(v)\n\treturn _c\n}\n\n// SetNillableObsoleteToken sets the \"obsolete_token\" field if the given value is not nil.\nfunc (_c *RefreshTokenCreate) SetNillableObsoleteToken(v *string) *RefreshTokenCreate {\n\tif v != nil {\n\t\t_c.SetObsoleteToken(*v)\n\t}\n\treturn _c\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_c *RefreshTokenCreate) SetCreatedAt(v time.Time) *RefreshTokenCreate {\n\t_c.mutation.SetCreatedAt(v)\n\treturn _c\n}\n\n// SetNillableCreatedAt sets the \"created_at\" field if the given value is not nil.\nfunc (_c *RefreshTokenCreate) SetNillableCreatedAt(v *time.Time) *RefreshTokenCreate {\n\tif v != nil {\n\t\t_c.SetCreatedAt(*v)\n\t}\n\treturn _c\n}\n\n// SetLastUsed sets the \"last_used\" field.\nfunc (_c *RefreshTokenCreate) SetLastUsed(v time.Time) *RefreshTokenCreate {\n\t_c.mutation.SetLastUsed(v)\n\treturn _c\n}\n\n// SetNillableLastUsed sets the \"last_used\" field if the given value is not nil.\nfunc (_c *RefreshTokenCreate) SetNillableLastUsed(v *time.Time) *RefreshTokenCreate {\n\tif v != nil {\n\t\t_c.SetLastUsed(*v)\n\t}\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *RefreshTokenCreate) SetID(v string) *RefreshTokenCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the RefreshTokenMutation object of the builder.\nfunc (_c *RefreshTokenCreate) Mutation() *RefreshTokenMutation {\n\treturn _c.mutation\n}\n\n// Save creates the RefreshToken in the database.\nfunc (_c *RefreshTokenCreate) Save(ctx context.Context) (*RefreshToken, error) {\n\t_c.defaults()\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *RefreshTokenCreate) SaveX(ctx context.Context) *RefreshToken {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *RefreshTokenCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *RefreshTokenCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// defaults sets the default values of the builder before save.\nfunc (_c *RefreshTokenCreate) defaults() {\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\tv := refreshtoken.DefaultClaimsPreferredUsername\n\t\t_c.mutation.SetClaimsPreferredUsername(v)\n\t}\n\tif _, ok := _c.mutation.Token(); !ok {\n\t\tv := refreshtoken.DefaultToken\n\t\t_c.mutation.SetToken(v)\n\t}\n\tif _, ok := _c.mutation.ObsoleteToken(); !ok {\n\t\tv := refreshtoken.DefaultObsoleteToken\n\t\t_c.mutation.SetObsoleteToken(v)\n\t}\n\tif _, ok := _c.mutation.CreatedAt(); !ok {\n\t\tv := refreshtoken.DefaultCreatedAt()\n\t\t_c.mutation.SetCreatedAt(v)\n\t}\n\tif _, ok := _c.mutation.LastUsed(); !ok {\n\t\tv := refreshtoken.DefaultLastUsed()\n\t\t_c.mutation.SetLastUsed(v)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *RefreshTokenCreate) check() error {\n\tif _, ok := _c.mutation.ClientID(); !ok {\n\t\treturn &ValidationError{Name: \"client_id\", err: errors.New(`db: missing required field \"RefreshToken.client_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ClientID(); ok {\n\t\tif err := refreshtoken.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Nonce(); !ok {\n\t\treturn &ValidationError{Name: \"nonce\", err: errors.New(`db: missing required field \"RefreshToken.nonce\"`)}\n\t}\n\tif v, ok := _c.mutation.Nonce(); ok {\n\t\tif err := refreshtoken.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.nonce\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsUserID(); !ok {\n\t\treturn &ValidationError{Name: \"claims_user_id\", err: errors.New(`db: missing required field \"RefreshToken.claims_user_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ClaimsUserID(); ok {\n\t\tif err := refreshtoken.ClaimsUserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_user_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_user_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_username\", err: errors.New(`db: missing required field \"RefreshToken.claims_username\"`)}\n\t}\n\tif v, ok := _c.mutation.ClaimsUsername(); ok {\n\t\tif err := refreshtoken.ClaimsUsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_username\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_username\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmail(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email\", err: errors.New(`db: missing required field \"RefreshToken.claims_email\"`)}\n\t}\n\tif v, ok := _c.mutation.ClaimsEmail(); ok {\n\t\tif err := refreshtoken.ClaimsEmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_email\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_email\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmailVerified(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email_verified\", err: errors.New(`db: missing required field \"RefreshToken.claims_email_verified\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_preferred_username\", err: errors.New(`db: missing required field \"RefreshToken.claims_preferred_username\"`)}\n\t}\n\tif _, ok := _c.mutation.ConnectorID(); !ok {\n\t\treturn &ValidationError{Name: \"connector_id\", err: errors.New(`db: missing required field \"RefreshToken.connector_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ConnectorID(); ok {\n\t\tif err := refreshtoken.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.connector_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.Token(); !ok {\n\t\treturn &ValidationError{Name: \"token\", err: errors.New(`db: missing required field \"RefreshToken.token\"`)}\n\t}\n\tif _, ok := _c.mutation.ObsoleteToken(); !ok {\n\t\treturn &ValidationError{Name: \"obsolete_token\", err: errors.New(`db: missing required field \"RefreshToken.obsolete_token\"`)}\n\t}\n\tif _, ok := _c.mutation.CreatedAt(); !ok {\n\t\treturn &ValidationError{Name: \"created_at\", err: errors.New(`db: missing required field \"RefreshToken.created_at\"`)}\n\t}\n\tif _, ok := _c.mutation.LastUsed(); !ok {\n\t\treturn &ValidationError{Name: \"last_used\", err: errors.New(`db: missing required field \"RefreshToken.last_used\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := refreshtoken.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *RefreshTokenCreate) sqlSave(ctx context.Context) (*RefreshToken, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected RefreshToken.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *RefreshTokenCreate) createSpec() (*RefreshToken, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &RefreshToken{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(refreshtoken.Table, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.ClientID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClientID, field.TypeString, value)\n\t\t_node.ClientID = value\n\t}\n\tif value, ok := _c.mutation.Scopes(); ok {\n\t\t_spec.SetField(refreshtoken.FieldScopes, field.TypeJSON, value)\n\t\t_node.Scopes = value\n\t}\n\tif value, ok := _c.mutation.Nonce(); ok {\n\t\t_spec.SetField(refreshtoken.FieldNonce, field.TypeString, value)\n\t\t_node.Nonce = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsUserID, field.TypeString, value)\n\t\t_node.ClaimsUserID = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsUsername, field.TypeString, value)\n\t\t_node.ClaimsUsername = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsEmail, field.TypeString, value)\n\t\t_node.ClaimsEmail = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsEmailVerified, field.TypeBool, value)\n\t\t_node.ClaimsEmailVerified = value\n\t}\n\tif value, ok := _c.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsGroups, field.TypeJSON, value)\n\t\t_node.ClaimsGroups = value\n\t}\n\tif value, ok := _c.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsPreferredUsername, field.TypeString, value)\n\t\t_node.ClaimsPreferredUsername = value\n\t}\n\tif value, ok := _c.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldConnectorID, field.TypeString, value)\n\t\t_node.ConnectorID = value\n\t}\n\tif value, ok := _c.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(refreshtoken.FieldConnectorData, field.TypeBytes, value)\n\t\t_node.ConnectorData = &value\n\t}\n\tif value, ok := _c.mutation.Token(); ok {\n\t\t_spec.SetField(refreshtoken.FieldToken, field.TypeString, value)\n\t\t_node.Token = value\n\t}\n\tif value, ok := _c.mutation.ObsoleteToken(); ok {\n\t\t_spec.SetField(refreshtoken.FieldObsoleteToken, field.TypeString, value)\n\t\t_node.ObsoleteToken = value\n\t}\n\tif value, ok := _c.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(refreshtoken.FieldCreatedAt, field.TypeTime, value)\n\t\t_node.CreatedAt = value\n\t}\n\tif value, ok := _c.mutation.LastUsed(); ok {\n\t\t_spec.SetField(refreshtoken.FieldLastUsed, field.TypeTime, value)\n\t\t_node.LastUsed = value\n\t}\n\treturn _node, _spec\n}\n\n// RefreshTokenCreateBulk is the builder for creating many RefreshToken entities in bulk.\ntype RefreshTokenCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*RefreshTokenCreate\n}\n\n// Save creates the RefreshToken entities in the database.\nfunc (_c *RefreshTokenCreateBulk) Save(ctx context.Context) ([]*RefreshToken, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*RefreshToken, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tbuilder.defaults()\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*RefreshTokenMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *RefreshTokenCreateBulk) SaveX(ctx context.Context) []*RefreshToken {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *RefreshTokenCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *RefreshTokenCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/refreshtoken_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n)\n\n// RefreshTokenDelete is the builder for deleting a RefreshToken entity.\ntype RefreshTokenDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *RefreshTokenMutation\n}\n\n// Where appends a list predicates to the RefreshTokenDelete builder.\nfunc (_d *RefreshTokenDelete) Where(ps ...predicate.RefreshToken) *RefreshTokenDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *RefreshTokenDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *RefreshTokenDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *RefreshTokenDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(refreshtoken.Table, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// RefreshTokenDeleteOne is the builder for deleting a single RefreshToken entity.\ntype RefreshTokenDeleteOne struct {\n\t_d *RefreshTokenDelete\n}\n\n// Where appends a list predicates to the RefreshTokenDelete builder.\nfunc (_d *RefreshTokenDeleteOne) Where(ps ...predicate.RefreshToken) *RefreshTokenDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *RefreshTokenDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{refreshtoken.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *RefreshTokenDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/refreshtoken_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n)\n\n// RefreshTokenQuery is the builder for querying RefreshToken entities.\ntype RefreshTokenQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []refreshtoken.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.RefreshToken\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the RefreshTokenQuery builder.\nfunc (_q *RefreshTokenQuery) Where(ps ...predicate.RefreshToken) *RefreshTokenQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *RefreshTokenQuery) Limit(limit int) *RefreshTokenQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *RefreshTokenQuery) Offset(offset int) *RefreshTokenQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *RefreshTokenQuery) Unique(unique bool) *RefreshTokenQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *RefreshTokenQuery) Order(o ...refreshtoken.OrderOption) *RefreshTokenQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first RefreshToken entity from the query.\n// Returns a *NotFoundError when no RefreshToken was found.\nfunc (_q *RefreshTokenQuery) First(ctx context.Context) (*RefreshToken, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{refreshtoken.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) FirstX(ctx context.Context) *RefreshToken {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first RefreshToken ID from the query.\n// Returns a *NotFoundError when no RefreshToken ID was found.\nfunc (_q *RefreshTokenQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{refreshtoken.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single RefreshToken entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one RefreshToken entity is found.\n// Returns a *NotFoundError when no RefreshToken entities are found.\nfunc (_q *RefreshTokenQuery) Only(ctx context.Context) (*RefreshToken, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{refreshtoken.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{refreshtoken.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) OnlyX(ctx context.Context) *RefreshToken {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only RefreshToken ID in the query.\n// Returns a *NotSingularError when more than one RefreshToken ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *RefreshTokenQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{refreshtoken.Label}\n\tdefault:\n\t\terr = &NotSingularError{refreshtoken.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of RefreshTokens.\nfunc (_q *RefreshTokenQuery) All(ctx context.Context) ([]*RefreshToken, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*RefreshToken, *RefreshTokenQuery]()\n\treturn withInterceptors[[]*RefreshToken](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) AllX(ctx context.Context) []*RefreshToken {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of RefreshToken IDs.\nfunc (_q *RefreshTokenQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(refreshtoken.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *RefreshTokenQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*RefreshTokenQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *RefreshTokenQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *RefreshTokenQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the RefreshTokenQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *RefreshTokenQuery) Clone() *RefreshTokenQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &RefreshTokenQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]refreshtoken.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.RefreshToken{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tClientID string `json:\"client_id,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.RefreshToken.Query().\n//\t\tGroupBy(refreshtoken.FieldClientID).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *RefreshTokenQuery) GroupBy(field string, fields ...string) *RefreshTokenGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &RefreshTokenGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = refreshtoken.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tClientID string `json:\"client_id,omitempty\"`\n//\t}\n//\n//\tclient.RefreshToken.Query().\n//\t\tSelect(refreshtoken.FieldClientID).\n//\t\tScan(ctx, &v)\nfunc (_q *RefreshTokenQuery) Select(fields ...string) *RefreshTokenSelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &RefreshTokenSelect{RefreshTokenQuery: _q}\n\tsbuild.label = refreshtoken.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a RefreshTokenSelect configured with the given aggregations.\nfunc (_q *RefreshTokenQuery) Aggregate(fns ...AggregateFunc) *RefreshTokenSelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *RefreshTokenQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !refreshtoken.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *RefreshTokenQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*RefreshToken, error) {\n\tvar (\n\t\tnodes = []*RefreshToken{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*RefreshToken).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &RefreshToken{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *RefreshTokenQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *RefreshTokenQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(refreshtoken.Table, refreshtoken.Columns, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, refreshtoken.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != refreshtoken.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *RefreshTokenQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(refreshtoken.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = refreshtoken.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// RefreshTokenGroupBy is the group-by builder for RefreshToken entities.\ntype RefreshTokenGroupBy struct {\n\tselector\n\tbuild *RefreshTokenQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *RefreshTokenGroupBy) Aggregate(fns ...AggregateFunc) *RefreshTokenGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *RefreshTokenGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*RefreshTokenQuery, *RefreshTokenGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *RefreshTokenGroupBy) sqlScan(ctx context.Context, root *RefreshTokenQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// RefreshTokenSelect is the builder for selecting fields of RefreshToken entities.\ntype RefreshTokenSelect struct {\n\t*RefreshTokenQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *RefreshTokenSelect) Aggregate(fns ...AggregateFunc) *RefreshTokenSelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *RefreshTokenSelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*RefreshTokenQuery, *RefreshTokenSelect](ctx, _s.RefreshTokenQuery, _s, _s.inters, v)\n}\n\nfunc (_s *RefreshTokenSelect) sqlScan(ctx context.Context, root *RefreshTokenQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/refreshtoken_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n)\n\n// RefreshTokenUpdate is the builder for updating RefreshToken entities.\ntype RefreshTokenUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *RefreshTokenMutation\n}\n\n// Where appends a list predicates to the RefreshTokenUpdate builder.\nfunc (_u *RefreshTokenUpdate) Where(ps ...predicate.RefreshToken) *RefreshTokenUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *RefreshTokenUpdate) SetClientID(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableClientID(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *RefreshTokenUpdate) SetScopes(v []string) *RefreshTokenUpdate {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *RefreshTokenUpdate) AppendScopes(v []string) *RefreshTokenUpdate {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *RefreshTokenUpdate) ClearScopes() *RefreshTokenUpdate {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *RefreshTokenUpdate) SetNonce(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableNonce(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *RefreshTokenUpdate) SetClaimsUserID(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableClaimsUserID(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *RefreshTokenUpdate) SetClaimsUsername(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableClaimsUsername(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *RefreshTokenUpdate) SetClaimsEmail(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableClaimsEmail(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *RefreshTokenUpdate) SetClaimsEmailVerified(v bool) *RefreshTokenUpdate {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableClaimsEmailVerified(v *bool) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *RefreshTokenUpdate) SetClaimsGroups(v []string) *RefreshTokenUpdate {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *RefreshTokenUpdate) AppendClaimsGroups(v []string) *RefreshTokenUpdate {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *RefreshTokenUpdate) ClearClaimsGroups() *RefreshTokenUpdate {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *RefreshTokenUpdate) SetClaimsPreferredUsername(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableClaimsPreferredUsername(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *RefreshTokenUpdate) SetConnectorID(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableConnectorID(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *RefreshTokenUpdate) SetConnectorData(v []byte) *RefreshTokenUpdate {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *RefreshTokenUpdate) ClearConnectorData() *RefreshTokenUpdate {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// SetToken sets the \"token\" field.\nfunc (_u *RefreshTokenUpdate) SetToken(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetToken(v)\n\treturn _u\n}\n\n// SetNillableToken sets the \"token\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableToken(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetToken(*v)\n\t}\n\treturn _u\n}\n\n// SetObsoleteToken sets the \"obsolete_token\" field.\nfunc (_u *RefreshTokenUpdate) SetObsoleteToken(v string) *RefreshTokenUpdate {\n\t_u.mutation.SetObsoleteToken(v)\n\treturn _u\n}\n\n// SetNillableObsoleteToken sets the \"obsolete_token\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableObsoleteToken(v *string) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetObsoleteToken(*v)\n\t}\n\treturn _u\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_u *RefreshTokenUpdate) SetCreatedAt(v time.Time) *RefreshTokenUpdate {\n\t_u.mutation.SetCreatedAt(v)\n\treturn _u\n}\n\n// SetNillableCreatedAt sets the \"created_at\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableCreatedAt(v *time.Time) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetCreatedAt(*v)\n\t}\n\treturn _u\n}\n\n// SetLastUsed sets the \"last_used\" field.\nfunc (_u *RefreshTokenUpdate) SetLastUsed(v time.Time) *RefreshTokenUpdate {\n\t_u.mutation.SetLastUsed(v)\n\treturn _u\n}\n\n// SetNillableLastUsed sets the \"last_used\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdate) SetNillableLastUsed(v *time.Time) *RefreshTokenUpdate {\n\tif v != nil {\n\t\t_u.SetLastUsed(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the RefreshTokenMutation object of the builder.\nfunc (_u *RefreshTokenUpdate) Mutation() *RefreshTokenMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *RefreshTokenUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *RefreshTokenUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *RefreshTokenUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *RefreshTokenUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *RefreshTokenUpdate) check() error {\n\tif v, ok := _u.mutation.ClientID(); ok {\n\t\tif err := refreshtoken.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Nonce(); ok {\n\t\tif err := refreshtoken.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.nonce\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUserID(); ok {\n\t\tif err := refreshtoken.ClaimsUserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_user_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUsername(); ok {\n\t\tif err := refreshtoken.ClaimsUsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_username\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_username\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsEmail(); ok {\n\t\tif err := refreshtoken.ClaimsEmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_email\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_email\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := refreshtoken.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.connector_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *RefreshTokenUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(refreshtoken.Table, refreshtoken.Columns, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(refreshtoken.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, refreshtoken.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(refreshtoken.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(refreshtoken.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, refreshtoken.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(refreshtoken.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(refreshtoken.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(refreshtoken.FieldConnectorData, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Token(); ok {\n\t\t_spec.SetField(refreshtoken.FieldToken, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ObsoleteToken(); ok {\n\t\t_spec.SetField(refreshtoken.FieldObsoleteToken, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(refreshtoken.FieldCreatedAt, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastUsed(); ok {\n\t\t_spec.SetField(refreshtoken.FieldLastUsed, field.TypeTime, value)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{refreshtoken.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// RefreshTokenUpdateOne is the builder for updating a single RefreshToken entity.\ntype RefreshTokenUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *RefreshTokenMutation\n}\n\n// SetClientID sets the \"client_id\" field.\nfunc (_u *RefreshTokenUpdateOne) SetClientID(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetClientID(v)\n\treturn _u\n}\n\n// SetNillableClientID sets the \"client_id\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableClientID(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetClientID(*v)\n\t}\n\treturn _u\n}\n\n// SetScopes sets the \"scopes\" field.\nfunc (_u *RefreshTokenUpdateOne) SetScopes(v []string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetScopes(v)\n\treturn _u\n}\n\n// AppendScopes appends value to the \"scopes\" field.\nfunc (_u *RefreshTokenUpdateOne) AppendScopes(v []string) *RefreshTokenUpdateOne {\n\t_u.mutation.AppendScopes(v)\n\treturn _u\n}\n\n// ClearScopes clears the value of the \"scopes\" field.\nfunc (_u *RefreshTokenUpdateOne) ClearScopes() *RefreshTokenUpdateOne {\n\t_u.mutation.ClearScopes()\n\treturn _u\n}\n\n// SetNonce sets the \"nonce\" field.\nfunc (_u *RefreshTokenUpdateOne) SetNonce(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetNonce(v)\n\treturn _u\n}\n\n// SetNillableNonce sets the \"nonce\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableNonce(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetNonce(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *RefreshTokenUpdateOne) SetClaimsUserID(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableClaimsUserID(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *RefreshTokenUpdateOne) SetClaimsUsername(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableClaimsUsername(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *RefreshTokenUpdateOne) SetClaimsEmail(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableClaimsEmail(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *RefreshTokenUpdateOne) SetClaimsEmailVerified(v bool) *RefreshTokenUpdateOne {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableClaimsEmailVerified(v *bool) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *RefreshTokenUpdateOne) SetClaimsGroups(v []string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *RefreshTokenUpdateOne) AppendClaimsGroups(v []string) *RefreshTokenUpdateOne {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *RefreshTokenUpdateOne) ClearClaimsGroups() *RefreshTokenUpdateOne {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *RefreshTokenUpdateOne) SetClaimsPreferredUsername(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableClaimsPreferredUsername(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *RefreshTokenUpdateOne) SetConnectorID(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableConnectorID(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorData sets the \"connector_data\" field.\nfunc (_u *RefreshTokenUpdateOne) SetConnectorData(v []byte) *RefreshTokenUpdateOne {\n\t_u.mutation.SetConnectorData(v)\n\treturn _u\n}\n\n// ClearConnectorData clears the value of the \"connector_data\" field.\nfunc (_u *RefreshTokenUpdateOne) ClearConnectorData() *RefreshTokenUpdateOne {\n\t_u.mutation.ClearConnectorData()\n\treturn _u\n}\n\n// SetToken sets the \"token\" field.\nfunc (_u *RefreshTokenUpdateOne) SetToken(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetToken(v)\n\treturn _u\n}\n\n// SetNillableToken sets the \"token\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableToken(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetToken(*v)\n\t}\n\treturn _u\n}\n\n// SetObsoleteToken sets the \"obsolete_token\" field.\nfunc (_u *RefreshTokenUpdateOne) SetObsoleteToken(v string) *RefreshTokenUpdateOne {\n\t_u.mutation.SetObsoleteToken(v)\n\treturn _u\n}\n\n// SetNillableObsoleteToken sets the \"obsolete_token\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableObsoleteToken(v *string) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetObsoleteToken(*v)\n\t}\n\treturn _u\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_u *RefreshTokenUpdateOne) SetCreatedAt(v time.Time) *RefreshTokenUpdateOne {\n\t_u.mutation.SetCreatedAt(v)\n\treturn _u\n}\n\n// SetNillableCreatedAt sets the \"created_at\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableCreatedAt(v *time.Time) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetCreatedAt(*v)\n\t}\n\treturn _u\n}\n\n// SetLastUsed sets the \"last_used\" field.\nfunc (_u *RefreshTokenUpdateOne) SetLastUsed(v time.Time) *RefreshTokenUpdateOne {\n\t_u.mutation.SetLastUsed(v)\n\treturn _u\n}\n\n// SetNillableLastUsed sets the \"last_used\" field if the given value is not nil.\nfunc (_u *RefreshTokenUpdateOne) SetNillableLastUsed(v *time.Time) *RefreshTokenUpdateOne {\n\tif v != nil {\n\t\t_u.SetLastUsed(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the RefreshTokenMutation object of the builder.\nfunc (_u *RefreshTokenUpdateOne) Mutation() *RefreshTokenMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the RefreshTokenUpdate builder.\nfunc (_u *RefreshTokenUpdateOne) Where(ps ...predicate.RefreshToken) *RefreshTokenUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *RefreshTokenUpdateOne) Select(field string, fields ...string) *RefreshTokenUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated RefreshToken entity.\nfunc (_u *RefreshTokenUpdateOne) Save(ctx context.Context) (*RefreshToken, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *RefreshTokenUpdateOne) SaveX(ctx context.Context) *RefreshToken {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *RefreshTokenUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *RefreshTokenUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *RefreshTokenUpdateOne) check() error {\n\tif v, ok := _u.mutation.ClientID(); ok {\n\t\tif err := refreshtoken.ClientIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"client_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.client_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.Nonce(); ok {\n\t\tif err := refreshtoken.NonceValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"nonce\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.nonce\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUserID(); ok {\n\t\tif err := refreshtoken.ClaimsUserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_user_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsUsername(); ok {\n\t\tif err := refreshtoken.ClaimsUsernameValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_username\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_username\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ClaimsEmail(); ok {\n\t\tif err := refreshtoken.ClaimsEmailValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"claims_email\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.claims_email\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := refreshtoken.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"RefreshToken.connector_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *RefreshTokenUpdateOne) sqlSave(ctx context.Context) (_node *RefreshToken, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(refreshtoken.Table, refreshtoken.Columns, sqlgraph.NewFieldSpec(refreshtoken.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"RefreshToken.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, refreshtoken.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !refreshtoken.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != refreshtoken.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.ClientID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClientID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.Scopes(); ok {\n\t\t_spec.SetField(refreshtoken.FieldScopes, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedScopes(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, refreshtoken.FieldScopes, value)\n\t\t})\n\t}\n\tif _u.mutation.ScopesCleared() {\n\t\t_spec.ClearField(refreshtoken.FieldScopes, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Nonce(); ok {\n\t\t_spec.SetField(refreshtoken.FieldNonce, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, refreshtoken.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(refreshtoken.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(refreshtoken.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(refreshtoken.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorData(); ok {\n\t\t_spec.SetField(refreshtoken.FieldConnectorData, field.TypeBytes, value)\n\t}\n\tif _u.mutation.ConnectorDataCleared() {\n\t\t_spec.ClearField(refreshtoken.FieldConnectorData, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.Token(); ok {\n\t\t_spec.SetField(refreshtoken.FieldToken, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ObsoleteToken(); ok {\n\t\t_spec.SetField(refreshtoken.FieldObsoleteToken, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(refreshtoken.FieldCreatedAt, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastUsed(); ok {\n\t\t_spec.SetField(refreshtoken.FieldLastUsed, field.TypeTime, value)\n\t}\n\t_node = &RefreshToken{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{refreshtoken.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/db/runtime/runtime.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage runtime\n\n// The schema-stitching logic is generated in github.com/dexidp/dex/storage/ent/db/runtime.go\n\nconst (\n\tVersion = \"v0.14.5\"                                         // Version of ent codegen.\n\tSum     = \"h1:Rj2WOYJtCkWyFo6a+5wB3EfBRP0rnx1fMk6gGA0UUe4=\" // Sum of ent codegen.\n)\n"
  },
  {
    "path": "storage/ent/db/runtime.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage/ent/db/authcode\"\n\t\"github.com/dexidp/dex/storage/ent/db/authrequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/authsession\"\n\t\"github.com/dexidp/dex/storage/ent/db/connector\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicerequest\"\n\t\"github.com/dexidp/dex/storage/ent/db/devicetoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/keys\"\n\t\"github.com/dexidp/dex/storage/ent/db/oauth2client\"\n\t\"github.com/dexidp/dex/storage/ent/db/offlinesession\"\n\t\"github.com/dexidp/dex/storage/ent/db/password\"\n\t\"github.com/dexidp/dex/storage/ent/db/refreshtoken\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n\t\"github.com/dexidp/dex/storage/ent/schema\"\n)\n\n// The init function reads all schema descriptors with runtime code\n// (default values, validators, hooks and policies) and stitches it\n// to their package variables.\nfunc init() {\n\tauthcodeFields := schema.AuthCode{}.Fields()\n\t_ = authcodeFields\n\t// authcodeDescClientID is the schema descriptor for client_id field.\n\tauthcodeDescClientID := authcodeFields[1].Descriptor()\n\t// authcode.ClientIDValidator is a validator for the \"client_id\" field. It is called by the builders before save.\n\tauthcode.ClientIDValidator = authcodeDescClientID.Validators[0].(func(string) error)\n\t// authcodeDescNonce is the schema descriptor for nonce field.\n\tauthcodeDescNonce := authcodeFields[3].Descriptor()\n\t// authcode.NonceValidator is a validator for the \"nonce\" field. It is called by the builders before save.\n\tauthcode.NonceValidator = authcodeDescNonce.Validators[0].(func(string) error)\n\t// authcodeDescRedirectURI is the schema descriptor for redirect_uri field.\n\tauthcodeDescRedirectURI := authcodeFields[4].Descriptor()\n\t// authcode.RedirectURIValidator is a validator for the \"redirect_uri\" field. It is called by the builders before save.\n\tauthcode.RedirectURIValidator = authcodeDescRedirectURI.Validators[0].(func(string) error)\n\t// authcodeDescClaimsUserID is the schema descriptor for claims_user_id field.\n\tauthcodeDescClaimsUserID := authcodeFields[5].Descriptor()\n\t// authcode.ClaimsUserIDValidator is a validator for the \"claims_user_id\" field. It is called by the builders before save.\n\tauthcode.ClaimsUserIDValidator = authcodeDescClaimsUserID.Validators[0].(func(string) error)\n\t// authcodeDescClaimsUsername is the schema descriptor for claims_username field.\n\tauthcodeDescClaimsUsername := authcodeFields[6].Descriptor()\n\t// authcode.ClaimsUsernameValidator is a validator for the \"claims_username\" field. It is called by the builders before save.\n\tauthcode.ClaimsUsernameValidator = authcodeDescClaimsUsername.Validators[0].(func(string) error)\n\t// authcodeDescClaimsEmail is the schema descriptor for claims_email field.\n\tauthcodeDescClaimsEmail := authcodeFields[7].Descriptor()\n\t// authcode.ClaimsEmailValidator is a validator for the \"claims_email\" field. It is called by the builders before save.\n\tauthcode.ClaimsEmailValidator = authcodeDescClaimsEmail.Validators[0].(func(string) error)\n\t// authcodeDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field.\n\tauthcodeDescClaimsPreferredUsername := authcodeFields[10].Descriptor()\n\t// authcode.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field.\n\tauthcode.DefaultClaimsPreferredUsername = authcodeDescClaimsPreferredUsername.Default.(string)\n\t// authcodeDescConnectorID is the schema descriptor for connector_id field.\n\tauthcodeDescConnectorID := authcodeFields[11].Descriptor()\n\t// authcode.ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\tauthcode.ConnectorIDValidator = authcodeDescConnectorID.Validators[0].(func(string) error)\n\t// authcodeDescCodeChallenge is the schema descriptor for code_challenge field.\n\tauthcodeDescCodeChallenge := authcodeFields[14].Descriptor()\n\t// authcode.DefaultCodeChallenge holds the default value on creation for the code_challenge field.\n\tauthcode.DefaultCodeChallenge = authcodeDescCodeChallenge.Default.(string)\n\t// authcodeDescCodeChallengeMethod is the schema descriptor for code_challenge_method field.\n\tauthcodeDescCodeChallengeMethod := authcodeFields[15].Descriptor()\n\t// authcode.DefaultCodeChallengeMethod holds the default value on creation for the code_challenge_method field.\n\tauthcode.DefaultCodeChallengeMethod = authcodeDescCodeChallengeMethod.Default.(string)\n\t// authcodeDescID is the schema descriptor for id field.\n\tauthcodeDescID := authcodeFields[0].Descriptor()\n\t// authcode.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tauthcode.IDValidator = authcodeDescID.Validators[0].(func(string) error)\n\tauthrequestFields := schema.AuthRequest{}.Fields()\n\t_ = authrequestFields\n\t// authrequestDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field.\n\tauthrequestDescClaimsPreferredUsername := authrequestFields[14].Descriptor()\n\t// authrequest.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field.\n\tauthrequest.DefaultClaimsPreferredUsername = authrequestDescClaimsPreferredUsername.Default.(string)\n\t// authrequestDescCodeChallenge is the schema descriptor for code_challenge field.\n\tauthrequestDescCodeChallenge := authrequestFields[18].Descriptor()\n\t// authrequest.DefaultCodeChallenge holds the default value on creation for the code_challenge field.\n\tauthrequest.DefaultCodeChallenge = authrequestDescCodeChallenge.Default.(string)\n\t// authrequestDescCodeChallengeMethod is the schema descriptor for code_challenge_method field.\n\tauthrequestDescCodeChallengeMethod := authrequestFields[19].Descriptor()\n\t// authrequest.DefaultCodeChallengeMethod holds the default value on creation for the code_challenge_method field.\n\tauthrequest.DefaultCodeChallengeMethod = authrequestDescCodeChallengeMethod.Default.(string)\n\t// authrequestDescMfaValidated is the schema descriptor for mfa_validated field.\n\tauthrequestDescMfaValidated := authrequestFields[21].Descriptor()\n\t// authrequest.DefaultMfaValidated holds the default value on creation for the mfa_validated field.\n\tauthrequest.DefaultMfaValidated = authrequestDescMfaValidated.Default.(bool)\n\t// authrequestDescPrompt is the schema descriptor for prompt field.\n\tauthrequestDescPrompt := authrequestFields[22].Descriptor()\n\t// authrequest.DefaultPrompt holds the default value on creation for the prompt field.\n\tauthrequest.DefaultPrompt = authrequestDescPrompt.Default.(string)\n\t// authrequestDescMaxAge is the schema descriptor for max_age field.\n\tauthrequestDescMaxAge := authrequestFields[23].Descriptor()\n\t// authrequest.DefaultMaxAge holds the default value on creation for the max_age field.\n\tauthrequest.DefaultMaxAge = authrequestDescMaxAge.Default.(int)\n\t// authrequestDescID is the schema descriptor for id field.\n\tauthrequestDescID := authrequestFields[0].Descriptor()\n\t// authrequest.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tauthrequest.IDValidator = authrequestDescID.Validators[0].(func(string) error)\n\tauthsessionFields := schema.AuthSession{}.Fields()\n\t_ = authsessionFields\n\t// authsessionDescUserID is the schema descriptor for user_id field.\n\tauthsessionDescUserID := authsessionFields[1].Descriptor()\n\t// authsession.UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tauthsession.UserIDValidator = authsessionDescUserID.Validators[0].(func(string) error)\n\t// authsessionDescConnectorID is the schema descriptor for connector_id field.\n\tauthsessionDescConnectorID := authsessionFields[2].Descriptor()\n\t// authsession.ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\tauthsession.ConnectorIDValidator = authsessionDescConnectorID.Validators[0].(func(string) error)\n\t// authsessionDescNonce is the schema descriptor for nonce field.\n\tauthsessionDescNonce := authsessionFields[3].Descriptor()\n\t// authsession.NonceValidator is a validator for the \"nonce\" field. It is called by the builders before save.\n\tauthsession.NonceValidator = authsessionDescNonce.Validators[0].(func(string) error)\n\t// authsessionDescIPAddress is the schema descriptor for ip_address field.\n\tauthsessionDescIPAddress := authsessionFields[7].Descriptor()\n\t// authsession.DefaultIPAddress holds the default value on creation for the ip_address field.\n\tauthsession.DefaultIPAddress = authsessionDescIPAddress.Default.(string)\n\t// authsessionDescUserAgent is the schema descriptor for user_agent field.\n\tauthsessionDescUserAgent := authsessionFields[8].Descriptor()\n\t// authsession.DefaultUserAgent holds the default value on creation for the user_agent field.\n\tauthsession.DefaultUserAgent = authsessionDescUserAgent.Default.(string)\n\t// authsessionDescID is the schema descriptor for id field.\n\tauthsessionDescID := authsessionFields[0].Descriptor()\n\t// authsession.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tauthsession.IDValidator = authsessionDescID.Validators[0].(func(string) error)\n\tconnectorFields := schema.Connector{}.Fields()\n\t_ = connectorFields\n\t// connectorDescType is the schema descriptor for type field.\n\tconnectorDescType := connectorFields[1].Descriptor()\n\t// connector.TypeValidator is a validator for the \"type\" field. It is called by the builders before save.\n\tconnector.TypeValidator = connectorDescType.Validators[0].(func(string) error)\n\t// connectorDescName is the schema descriptor for name field.\n\tconnectorDescName := connectorFields[2].Descriptor()\n\t// connector.NameValidator is a validator for the \"name\" field. It is called by the builders before save.\n\tconnector.NameValidator = connectorDescName.Validators[0].(func(string) error)\n\t// connectorDescID is the schema descriptor for id field.\n\tconnectorDescID := connectorFields[0].Descriptor()\n\t// connector.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tconnector.IDValidator = func() func(string) error {\n\t\tvalidators := connectorDescID.Validators\n\t\tfns := [...]func(string) error{\n\t\t\tvalidators[0].(func(string) error),\n\t\t\tvalidators[1].(func(string) error),\n\t\t}\n\t\treturn func(id string) error {\n\t\t\tfor _, fn := range fns {\n\t\t\t\tif err := fn(id); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}()\n\tdevicerequestFields := schema.DeviceRequest{}.Fields()\n\t_ = devicerequestFields\n\t// devicerequestDescUserCode is the schema descriptor for user_code field.\n\tdevicerequestDescUserCode := devicerequestFields[0].Descriptor()\n\t// devicerequest.UserCodeValidator is a validator for the \"user_code\" field. It is called by the builders before save.\n\tdevicerequest.UserCodeValidator = devicerequestDescUserCode.Validators[0].(func(string) error)\n\t// devicerequestDescDeviceCode is the schema descriptor for device_code field.\n\tdevicerequestDescDeviceCode := devicerequestFields[1].Descriptor()\n\t// devicerequest.DeviceCodeValidator is a validator for the \"device_code\" field. It is called by the builders before save.\n\tdevicerequest.DeviceCodeValidator = devicerequestDescDeviceCode.Validators[0].(func(string) error)\n\t// devicerequestDescClientID is the schema descriptor for client_id field.\n\tdevicerequestDescClientID := devicerequestFields[2].Descriptor()\n\t// devicerequest.ClientIDValidator is a validator for the \"client_id\" field. It is called by the builders before save.\n\tdevicerequest.ClientIDValidator = devicerequestDescClientID.Validators[0].(func(string) error)\n\t// devicerequestDescClientSecret is the schema descriptor for client_secret field.\n\tdevicerequestDescClientSecret := devicerequestFields[3].Descriptor()\n\t// devicerequest.ClientSecretValidator is a validator for the \"client_secret\" field. It is called by the builders before save.\n\tdevicerequest.ClientSecretValidator = devicerequestDescClientSecret.Validators[0].(func(string) error)\n\tdevicetokenFields := schema.DeviceToken{}.Fields()\n\t_ = devicetokenFields\n\t// devicetokenDescDeviceCode is the schema descriptor for device_code field.\n\tdevicetokenDescDeviceCode := devicetokenFields[0].Descriptor()\n\t// devicetoken.DeviceCodeValidator is a validator for the \"device_code\" field. It is called by the builders before save.\n\tdevicetoken.DeviceCodeValidator = devicetokenDescDeviceCode.Validators[0].(func(string) error)\n\t// devicetokenDescStatus is the schema descriptor for status field.\n\tdevicetokenDescStatus := devicetokenFields[1].Descriptor()\n\t// devicetoken.StatusValidator is a validator for the \"status\" field. It is called by the builders before save.\n\tdevicetoken.StatusValidator = devicetokenDescStatus.Validators[0].(func(string) error)\n\t// devicetokenDescCodeChallenge is the schema descriptor for code_challenge field.\n\tdevicetokenDescCodeChallenge := devicetokenFields[6].Descriptor()\n\t// devicetoken.DefaultCodeChallenge holds the default value on creation for the code_challenge field.\n\tdevicetoken.DefaultCodeChallenge = devicetokenDescCodeChallenge.Default.(string)\n\t// devicetokenDescCodeChallengeMethod is the schema descriptor for code_challenge_method field.\n\tdevicetokenDescCodeChallengeMethod := devicetokenFields[7].Descriptor()\n\t// devicetoken.DefaultCodeChallengeMethod holds the default value on creation for the code_challenge_method field.\n\tdevicetoken.DefaultCodeChallengeMethod = devicetokenDescCodeChallengeMethod.Default.(string)\n\tkeysFields := schema.Keys{}.Fields()\n\t_ = keysFields\n\t// keysDescID is the schema descriptor for id field.\n\tkeysDescID := keysFields[0].Descriptor()\n\t// keys.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tkeys.IDValidator = keysDescID.Validators[0].(func(string) error)\n\toauth2clientFields := schema.OAuth2Client{}.Fields()\n\t_ = oauth2clientFields\n\t// oauth2clientDescSecret is the schema descriptor for secret field.\n\toauth2clientDescSecret := oauth2clientFields[1].Descriptor()\n\t// oauth2client.SecretValidator is a validator for the \"secret\" field. It is called by the builders before save.\n\toauth2client.SecretValidator = oauth2clientDescSecret.Validators[0].(func(string) error)\n\t// oauth2clientDescName is the schema descriptor for name field.\n\toauth2clientDescName := oauth2clientFields[5].Descriptor()\n\t// oauth2client.NameValidator is a validator for the \"name\" field. It is called by the builders before save.\n\toauth2client.NameValidator = oauth2clientDescName.Validators[0].(func(string) error)\n\t// oauth2clientDescLogoURL is the schema descriptor for logo_url field.\n\toauth2clientDescLogoURL := oauth2clientFields[6].Descriptor()\n\t// oauth2client.LogoURLValidator is a validator for the \"logo_url\" field. It is called by the builders before save.\n\toauth2client.LogoURLValidator = oauth2clientDescLogoURL.Validators[0].(func(string) error)\n\t// oauth2clientDescID is the schema descriptor for id field.\n\toauth2clientDescID := oauth2clientFields[0].Descriptor()\n\t// oauth2client.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\toauth2client.IDValidator = func() func(string) error {\n\t\tvalidators := oauth2clientDescID.Validators\n\t\tfns := [...]func(string) error{\n\t\t\tvalidators[0].(func(string) error),\n\t\t\tvalidators[1].(func(string) error),\n\t\t}\n\t\treturn func(id string) error {\n\t\t\tfor _, fn := range fns {\n\t\t\t\tif err := fn(id); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t}()\n\tofflinesessionFields := schema.OfflineSession{}.Fields()\n\t_ = offlinesessionFields\n\t// offlinesessionDescUserID is the schema descriptor for user_id field.\n\tofflinesessionDescUserID := offlinesessionFields[1].Descriptor()\n\t// offlinesession.UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tofflinesession.UserIDValidator = offlinesessionDescUserID.Validators[0].(func(string) error)\n\t// offlinesessionDescConnID is the schema descriptor for conn_id field.\n\tofflinesessionDescConnID := offlinesessionFields[2].Descriptor()\n\t// offlinesession.ConnIDValidator is a validator for the \"conn_id\" field. It is called by the builders before save.\n\tofflinesession.ConnIDValidator = offlinesessionDescConnID.Validators[0].(func(string) error)\n\t// offlinesessionDescID is the schema descriptor for id field.\n\tofflinesessionDescID := offlinesessionFields[0].Descriptor()\n\t// offlinesession.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tofflinesession.IDValidator = offlinesessionDescID.Validators[0].(func(string) error)\n\tpasswordFields := schema.Password{}.Fields()\n\t_ = passwordFields\n\t// passwordDescEmail is the schema descriptor for email field.\n\tpasswordDescEmail := passwordFields[0].Descriptor()\n\t// password.EmailValidator is a validator for the \"email\" field. It is called by the builders before save.\n\tpassword.EmailValidator = passwordDescEmail.Validators[0].(func(string) error)\n\t// passwordDescUsername is the schema descriptor for username field.\n\tpasswordDescUsername := passwordFields[2].Descriptor()\n\t// password.UsernameValidator is a validator for the \"username\" field. It is called by the builders before save.\n\tpassword.UsernameValidator = passwordDescUsername.Validators[0].(func(string) error)\n\t// passwordDescName is the schema descriptor for name field.\n\tpasswordDescName := passwordFields[3].Descriptor()\n\t// password.DefaultName holds the default value on creation for the name field.\n\tpassword.DefaultName = passwordDescName.Default.(string)\n\t// passwordDescPreferredUsername is the schema descriptor for preferred_username field.\n\tpasswordDescPreferredUsername := passwordFields[4].Descriptor()\n\t// password.DefaultPreferredUsername holds the default value on creation for the preferred_username field.\n\tpassword.DefaultPreferredUsername = passwordDescPreferredUsername.Default.(string)\n\t// passwordDescUserID is the schema descriptor for user_id field.\n\tpasswordDescUserID := passwordFields[6].Descriptor()\n\t// password.UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tpassword.UserIDValidator = passwordDescUserID.Validators[0].(func(string) error)\n\trefreshtokenFields := schema.RefreshToken{}.Fields()\n\t_ = refreshtokenFields\n\t// refreshtokenDescClientID is the schema descriptor for client_id field.\n\trefreshtokenDescClientID := refreshtokenFields[1].Descriptor()\n\t// refreshtoken.ClientIDValidator is a validator for the \"client_id\" field. It is called by the builders before save.\n\trefreshtoken.ClientIDValidator = refreshtokenDescClientID.Validators[0].(func(string) error)\n\t// refreshtokenDescNonce is the schema descriptor for nonce field.\n\trefreshtokenDescNonce := refreshtokenFields[3].Descriptor()\n\t// refreshtoken.NonceValidator is a validator for the \"nonce\" field. It is called by the builders before save.\n\trefreshtoken.NonceValidator = refreshtokenDescNonce.Validators[0].(func(string) error)\n\t// refreshtokenDescClaimsUserID is the schema descriptor for claims_user_id field.\n\trefreshtokenDescClaimsUserID := refreshtokenFields[4].Descriptor()\n\t// refreshtoken.ClaimsUserIDValidator is a validator for the \"claims_user_id\" field. It is called by the builders before save.\n\trefreshtoken.ClaimsUserIDValidator = refreshtokenDescClaimsUserID.Validators[0].(func(string) error)\n\t// refreshtokenDescClaimsUsername is the schema descriptor for claims_username field.\n\trefreshtokenDescClaimsUsername := refreshtokenFields[5].Descriptor()\n\t// refreshtoken.ClaimsUsernameValidator is a validator for the \"claims_username\" field. It is called by the builders before save.\n\trefreshtoken.ClaimsUsernameValidator = refreshtokenDescClaimsUsername.Validators[0].(func(string) error)\n\t// refreshtokenDescClaimsEmail is the schema descriptor for claims_email field.\n\trefreshtokenDescClaimsEmail := refreshtokenFields[6].Descriptor()\n\t// refreshtoken.ClaimsEmailValidator is a validator for the \"claims_email\" field. It is called by the builders before save.\n\trefreshtoken.ClaimsEmailValidator = refreshtokenDescClaimsEmail.Validators[0].(func(string) error)\n\t// refreshtokenDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field.\n\trefreshtokenDescClaimsPreferredUsername := refreshtokenFields[9].Descriptor()\n\t// refreshtoken.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field.\n\trefreshtoken.DefaultClaimsPreferredUsername = refreshtokenDescClaimsPreferredUsername.Default.(string)\n\t// refreshtokenDescConnectorID is the schema descriptor for connector_id field.\n\trefreshtokenDescConnectorID := refreshtokenFields[10].Descriptor()\n\t// refreshtoken.ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\trefreshtoken.ConnectorIDValidator = refreshtokenDescConnectorID.Validators[0].(func(string) error)\n\t// refreshtokenDescToken is the schema descriptor for token field.\n\trefreshtokenDescToken := refreshtokenFields[12].Descriptor()\n\t// refreshtoken.DefaultToken holds the default value on creation for the token field.\n\trefreshtoken.DefaultToken = refreshtokenDescToken.Default.(string)\n\t// refreshtokenDescObsoleteToken is the schema descriptor for obsolete_token field.\n\trefreshtokenDescObsoleteToken := refreshtokenFields[13].Descriptor()\n\t// refreshtoken.DefaultObsoleteToken holds the default value on creation for the obsolete_token field.\n\trefreshtoken.DefaultObsoleteToken = refreshtokenDescObsoleteToken.Default.(string)\n\t// refreshtokenDescCreatedAt is the schema descriptor for created_at field.\n\trefreshtokenDescCreatedAt := refreshtokenFields[14].Descriptor()\n\t// refreshtoken.DefaultCreatedAt holds the default value on creation for the created_at field.\n\trefreshtoken.DefaultCreatedAt = refreshtokenDescCreatedAt.Default.(func() time.Time)\n\t// refreshtokenDescLastUsed is the schema descriptor for last_used field.\n\trefreshtokenDescLastUsed := refreshtokenFields[15].Descriptor()\n\t// refreshtoken.DefaultLastUsed holds the default value on creation for the last_used field.\n\trefreshtoken.DefaultLastUsed = refreshtokenDescLastUsed.Default.(func() time.Time)\n\t// refreshtokenDescID is the schema descriptor for id field.\n\trefreshtokenDescID := refreshtokenFields[0].Descriptor()\n\t// refreshtoken.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\trefreshtoken.IDValidator = refreshtokenDescID.Validators[0].(func(string) error)\n\tuseridentityFields := schema.UserIdentity{}.Fields()\n\t_ = useridentityFields\n\t// useridentityDescUserID is the schema descriptor for user_id field.\n\tuseridentityDescUserID := useridentityFields[1].Descriptor()\n\t// useridentity.UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tuseridentity.UserIDValidator = useridentityDescUserID.Validators[0].(func(string) error)\n\t// useridentityDescConnectorID is the schema descriptor for connector_id field.\n\tuseridentityDescConnectorID := useridentityFields[2].Descriptor()\n\t// useridentity.ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\tuseridentity.ConnectorIDValidator = useridentityDescConnectorID.Validators[0].(func(string) error)\n\t// useridentityDescClaimsUserID is the schema descriptor for claims_user_id field.\n\tuseridentityDescClaimsUserID := useridentityFields[3].Descriptor()\n\t// useridentity.DefaultClaimsUserID holds the default value on creation for the claims_user_id field.\n\tuseridentity.DefaultClaimsUserID = useridentityDescClaimsUserID.Default.(string)\n\t// useridentityDescClaimsUsername is the schema descriptor for claims_username field.\n\tuseridentityDescClaimsUsername := useridentityFields[4].Descriptor()\n\t// useridentity.DefaultClaimsUsername holds the default value on creation for the claims_username field.\n\tuseridentity.DefaultClaimsUsername = useridentityDescClaimsUsername.Default.(string)\n\t// useridentityDescClaimsPreferredUsername is the schema descriptor for claims_preferred_username field.\n\tuseridentityDescClaimsPreferredUsername := useridentityFields[5].Descriptor()\n\t// useridentity.DefaultClaimsPreferredUsername holds the default value on creation for the claims_preferred_username field.\n\tuseridentity.DefaultClaimsPreferredUsername = useridentityDescClaimsPreferredUsername.Default.(string)\n\t// useridentityDescClaimsEmail is the schema descriptor for claims_email field.\n\tuseridentityDescClaimsEmail := useridentityFields[6].Descriptor()\n\t// useridentity.DefaultClaimsEmail holds the default value on creation for the claims_email field.\n\tuseridentity.DefaultClaimsEmail = useridentityDescClaimsEmail.Default.(string)\n\t// useridentityDescClaimsEmailVerified is the schema descriptor for claims_email_verified field.\n\tuseridentityDescClaimsEmailVerified := useridentityFields[7].Descriptor()\n\t// useridentity.DefaultClaimsEmailVerified holds the default value on creation for the claims_email_verified field.\n\tuseridentity.DefaultClaimsEmailVerified = useridentityDescClaimsEmailVerified.Default.(bool)\n\t// useridentityDescID is the schema descriptor for id field.\n\tuseridentityDescID := useridentityFields[0].Descriptor()\n\t// useridentity.IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tuseridentity.IDValidator = useridentityDescID.Validators[0].(func(string) error)\n}\n"
  },
  {
    "path": "storage/ent/db/tx.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"entgo.io/ent/dialect\"\n)\n\n// Tx is a transactional client that is created by calling Client.Tx().\ntype Tx struct {\n\tconfig\n\t// AuthCode is the client for interacting with the AuthCode builders.\n\tAuthCode *AuthCodeClient\n\t// AuthRequest is the client for interacting with the AuthRequest builders.\n\tAuthRequest *AuthRequestClient\n\t// AuthSession is the client for interacting with the AuthSession builders.\n\tAuthSession *AuthSessionClient\n\t// Connector is the client for interacting with the Connector builders.\n\tConnector *ConnectorClient\n\t// DeviceRequest is the client for interacting with the DeviceRequest builders.\n\tDeviceRequest *DeviceRequestClient\n\t// DeviceToken is the client for interacting with the DeviceToken builders.\n\tDeviceToken *DeviceTokenClient\n\t// Keys is the client for interacting with the Keys builders.\n\tKeys *KeysClient\n\t// OAuth2Client is the client for interacting with the OAuth2Client builders.\n\tOAuth2Client *OAuth2ClientClient\n\t// OfflineSession is the client for interacting with the OfflineSession builders.\n\tOfflineSession *OfflineSessionClient\n\t// Password is the client for interacting with the Password builders.\n\tPassword *PasswordClient\n\t// RefreshToken is the client for interacting with the RefreshToken builders.\n\tRefreshToken *RefreshTokenClient\n\t// UserIdentity is the client for interacting with the UserIdentity builders.\n\tUserIdentity *UserIdentityClient\n\n\t// lazily loaded.\n\tclient     *Client\n\tclientOnce sync.Once\n\t// ctx lives for the life of the transaction. It is\n\t// the same context used by the underlying connection.\n\tctx context.Context\n}\n\ntype (\n\t// Committer is the interface that wraps the Commit method.\n\tCommitter interface {\n\t\tCommit(context.Context, *Tx) error\n\t}\n\n\t// The CommitFunc type is an adapter to allow the use of ordinary\n\t// function as a Committer. If f is a function with the appropriate\n\t// signature, CommitFunc(f) is a Committer that calls f.\n\tCommitFunc func(context.Context, *Tx) error\n\n\t// CommitHook defines the \"commit middleware\". A function that gets a Committer\n\t// and returns a Committer. For example:\n\t//\n\t//\thook := func(next ent.Committer) ent.Committer {\n\t//\t\treturn ent.CommitFunc(func(ctx context.Context, tx *ent.Tx) error {\n\t//\t\t\t// Do some stuff before.\n\t//\t\t\tif err := next.Commit(ctx, tx); err != nil {\n\t//\t\t\t\treturn err\n\t//\t\t\t}\n\t//\t\t\t// Do some stuff after.\n\t//\t\t\treturn nil\n\t//\t\t})\n\t//\t}\n\t//\n\tCommitHook func(Committer) Committer\n)\n\n// Commit calls f(ctx, m).\nfunc (f CommitFunc) Commit(ctx context.Context, tx *Tx) error {\n\treturn f(ctx, tx)\n}\n\n// Commit commits the transaction.\nfunc (tx *Tx) Commit() error {\n\ttxDriver := tx.config.driver.(*txDriver)\n\tvar fn Committer = CommitFunc(func(context.Context, *Tx) error {\n\t\treturn txDriver.tx.Commit()\n\t})\n\ttxDriver.mu.Lock()\n\thooks := append([]CommitHook(nil), txDriver.onCommit...)\n\ttxDriver.mu.Unlock()\n\tfor i := len(hooks) - 1; i >= 0; i-- {\n\t\tfn = hooks[i](fn)\n\t}\n\treturn fn.Commit(tx.ctx, tx)\n}\n\n// OnCommit adds a hook to call on commit.\nfunc (tx *Tx) OnCommit(f CommitHook) {\n\ttxDriver := tx.config.driver.(*txDriver)\n\ttxDriver.mu.Lock()\n\ttxDriver.onCommit = append(txDriver.onCommit, f)\n\ttxDriver.mu.Unlock()\n}\n\ntype (\n\t// Rollbacker is the interface that wraps the Rollback method.\n\tRollbacker interface {\n\t\tRollback(context.Context, *Tx) error\n\t}\n\n\t// The RollbackFunc type is an adapter to allow the use of ordinary\n\t// function as a Rollbacker. If f is a function with the appropriate\n\t// signature, RollbackFunc(f) is a Rollbacker that calls f.\n\tRollbackFunc func(context.Context, *Tx) error\n\n\t// RollbackHook defines the \"rollback middleware\". A function that gets a Rollbacker\n\t// and returns a Rollbacker. For example:\n\t//\n\t//\thook := func(next ent.Rollbacker) ent.Rollbacker {\n\t//\t\treturn ent.RollbackFunc(func(ctx context.Context, tx *ent.Tx) error {\n\t//\t\t\t// Do some stuff before.\n\t//\t\t\tif err := next.Rollback(ctx, tx); err != nil {\n\t//\t\t\t\treturn err\n\t//\t\t\t}\n\t//\t\t\t// Do some stuff after.\n\t//\t\t\treturn nil\n\t//\t\t})\n\t//\t}\n\t//\n\tRollbackHook func(Rollbacker) Rollbacker\n)\n\n// Rollback calls f(ctx, m).\nfunc (f RollbackFunc) Rollback(ctx context.Context, tx *Tx) error {\n\treturn f(ctx, tx)\n}\n\n// Rollback rollbacks the transaction.\nfunc (tx *Tx) Rollback() error {\n\ttxDriver := tx.config.driver.(*txDriver)\n\tvar fn Rollbacker = RollbackFunc(func(context.Context, *Tx) error {\n\t\treturn txDriver.tx.Rollback()\n\t})\n\ttxDriver.mu.Lock()\n\thooks := append([]RollbackHook(nil), txDriver.onRollback...)\n\ttxDriver.mu.Unlock()\n\tfor i := len(hooks) - 1; i >= 0; i-- {\n\t\tfn = hooks[i](fn)\n\t}\n\treturn fn.Rollback(tx.ctx, tx)\n}\n\n// OnRollback adds a hook to call on rollback.\nfunc (tx *Tx) OnRollback(f RollbackHook) {\n\ttxDriver := tx.config.driver.(*txDriver)\n\ttxDriver.mu.Lock()\n\ttxDriver.onRollback = append(txDriver.onRollback, f)\n\ttxDriver.mu.Unlock()\n}\n\n// Client returns a Client that binds to current transaction.\nfunc (tx *Tx) Client() *Client {\n\ttx.clientOnce.Do(func() {\n\t\ttx.client = &Client{config: tx.config}\n\t\ttx.client.init()\n\t})\n\treturn tx.client\n}\n\nfunc (tx *Tx) init() {\n\ttx.AuthCode = NewAuthCodeClient(tx.config)\n\ttx.AuthRequest = NewAuthRequestClient(tx.config)\n\ttx.AuthSession = NewAuthSessionClient(tx.config)\n\ttx.Connector = NewConnectorClient(tx.config)\n\ttx.DeviceRequest = NewDeviceRequestClient(tx.config)\n\ttx.DeviceToken = NewDeviceTokenClient(tx.config)\n\ttx.Keys = NewKeysClient(tx.config)\n\ttx.OAuth2Client = NewOAuth2ClientClient(tx.config)\n\ttx.OfflineSession = NewOfflineSessionClient(tx.config)\n\ttx.Password = NewPasswordClient(tx.config)\n\ttx.RefreshToken = NewRefreshTokenClient(tx.config)\n\ttx.UserIdentity = NewUserIdentityClient(tx.config)\n}\n\n// txDriver wraps the given dialect.Tx with a nop dialect.Driver implementation.\n// The idea is to support transactions without adding any extra code to the builders.\n// When a builder calls to driver.Tx(), it gets the same dialect.Tx instance.\n// Commit and Rollback are nop for the internal builders and the user must call one\n// of them in order to commit or rollback the transaction.\n//\n// If a closed transaction is embedded in one of the generated entities, and the entity\n// applies a query, for example: AuthCode.QueryXXX(), the query will be executed\n// through the driver which created this transaction.\n//\n// Note that txDriver is not goroutine safe.\ntype txDriver struct {\n\t// the driver we started the transaction from.\n\tdrv dialect.Driver\n\t// tx is the underlying transaction.\n\ttx dialect.Tx\n\t// completion hooks.\n\tmu         sync.Mutex\n\tonCommit   []CommitHook\n\tonRollback []RollbackHook\n}\n\n// newTx creates a new transactional driver.\nfunc newTx(ctx context.Context, drv dialect.Driver) (*txDriver, error) {\n\ttx, err := drv.Tx(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &txDriver{tx: tx, drv: drv}, nil\n}\n\n// Tx returns the transaction wrapper (txDriver) to avoid Commit or Rollback calls\n// from the internal builders. Should be called only by the internal builders.\nfunc (tx *txDriver) Tx(context.Context) (dialect.Tx, error) { return tx, nil }\n\n// Dialect returns the dialect of the driver we started the transaction from.\nfunc (tx *txDriver) Dialect() string { return tx.drv.Dialect() }\n\n// Close is a nop close.\nfunc (*txDriver) Close() error { return nil }\n\n// Commit is a nop commit for the internal builders.\n// User must call `Tx.Commit` in order to commit the transaction.\nfunc (*txDriver) Commit() error { return nil }\n\n// Rollback is a nop rollback for the internal builders.\n// User must call `Tx.Rollback` in order to rollback the transaction.\nfunc (*txDriver) Rollback() error { return nil }\n\n// Exec calls tx.Exec.\nfunc (tx *txDriver) Exec(ctx context.Context, query string, args, v any) error {\n\treturn tx.tx.Exec(ctx, query, args, v)\n}\n\n// Query calls tx.Query.\nfunc (tx *txDriver) Query(ctx context.Context, query string, args, v any) error {\n\treturn tx.tx.Query(ctx, query, args, v)\n}\n\nvar _ dialect.Driver = (*txDriver)(nil)\n"
  },
  {
    "path": "storage/ent/db/useridentity/useridentity.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage useridentity\n\nimport (\n\t\"entgo.io/ent/dialect/sql\"\n)\n\nconst (\n\t// Label holds the string label denoting the useridentity type in the database.\n\tLabel = \"user_identity\"\n\t// FieldID holds the string denoting the id field in the database.\n\tFieldID = \"id\"\n\t// FieldUserID holds the string denoting the user_id field in the database.\n\tFieldUserID = \"user_id\"\n\t// FieldConnectorID holds the string denoting the connector_id field in the database.\n\tFieldConnectorID = \"connector_id\"\n\t// FieldClaimsUserID holds the string denoting the claims_user_id field in the database.\n\tFieldClaimsUserID = \"claims_user_id\"\n\t// FieldClaimsUsername holds the string denoting the claims_username field in the database.\n\tFieldClaimsUsername = \"claims_username\"\n\t// FieldClaimsPreferredUsername holds the string denoting the claims_preferred_username field in the database.\n\tFieldClaimsPreferredUsername = \"claims_preferred_username\"\n\t// FieldClaimsEmail holds the string denoting the claims_email field in the database.\n\tFieldClaimsEmail = \"claims_email\"\n\t// FieldClaimsEmailVerified holds the string denoting the claims_email_verified field in the database.\n\tFieldClaimsEmailVerified = \"claims_email_verified\"\n\t// FieldClaimsGroups holds the string denoting the claims_groups field in the database.\n\tFieldClaimsGroups = \"claims_groups\"\n\t// FieldConsents holds the string denoting the consents field in the database.\n\tFieldConsents = \"consents\"\n\t// FieldMfaSecrets holds the string denoting the mfa_secrets field in the database.\n\tFieldMfaSecrets = \"mfa_secrets\"\n\t// FieldCreatedAt holds the string denoting the created_at field in the database.\n\tFieldCreatedAt = \"created_at\"\n\t// FieldLastLogin holds the string denoting the last_login field in the database.\n\tFieldLastLogin = \"last_login\"\n\t// FieldBlockedUntil holds the string denoting the blocked_until field in the database.\n\tFieldBlockedUntil = \"blocked_until\"\n\t// Table holds the table name of the useridentity in the database.\n\tTable = \"user_identities\"\n)\n\n// Columns holds all SQL columns for useridentity fields.\nvar Columns = []string{\n\tFieldID,\n\tFieldUserID,\n\tFieldConnectorID,\n\tFieldClaimsUserID,\n\tFieldClaimsUsername,\n\tFieldClaimsPreferredUsername,\n\tFieldClaimsEmail,\n\tFieldClaimsEmailVerified,\n\tFieldClaimsGroups,\n\tFieldConsents,\n\tFieldMfaSecrets,\n\tFieldCreatedAt,\n\tFieldLastLogin,\n\tFieldBlockedUntil,\n}\n\n// ValidColumn reports if the column name is valid (part of the table columns).\nfunc ValidColumn(column string) bool {\n\tfor i := range Columns {\n\t\tif column == Columns[i] {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nvar (\n\t// UserIDValidator is a validator for the \"user_id\" field. It is called by the builders before save.\n\tUserIDValidator func(string) error\n\t// ConnectorIDValidator is a validator for the \"connector_id\" field. It is called by the builders before save.\n\tConnectorIDValidator func(string) error\n\t// DefaultClaimsUserID holds the default value on creation for the \"claims_user_id\" field.\n\tDefaultClaimsUserID string\n\t// DefaultClaimsUsername holds the default value on creation for the \"claims_username\" field.\n\tDefaultClaimsUsername string\n\t// DefaultClaimsPreferredUsername holds the default value on creation for the \"claims_preferred_username\" field.\n\tDefaultClaimsPreferredUsername string\n\t// DefaultClaimsEmail holds the default value on creation for the \"claims_email\" field.\n\tDefaultClaimsEmail string\n\t// DefaultClaimsEmailVerified holds the default value on creation for the \"claims_email_verified\" field.\n\tDefaultClaimsEmailVerified bool\n\t// IDValidator is a validator for the \"id\" field. It is called by the builders before save.\n\tIDValidator func(string) error\n)\n\n// OrderOption defines the ordering options for the UserIdentity queries.\ntype OrderOption func(*sql.Selector)\n\n// ByID orders the results by the id field.\nfunc ByID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldID, opts...).ToFunc()\n}\n\n// ByUserID orders the results by the user_id field.\nfunc ByUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldUserID, opts...).ToFunc()\n}\n\n// ByConnectorID orders the results by the connector_id field.\nfunc ByConnectorID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldConnectorID, opts...).ToFunc()\n}\n\n// ByClaimsUserID orders the results by the claims_user_id field.\nfunc ByClaimsUserID(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUserID, opts...).ToFunc()\n}\n\n// ByClaimsUsername orders the results by the claims_username field.\nfunc ByClaimsUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsUsername, opts...).ToFunc()\n}\n\n// ByClaimsPreferredUsername orders the results by the claims_preferred_username field.\nfunc ByClaimsPreferredUsername(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsPreferredUsername, opts...).ToFunc()\n}\n\n// ByClaimsEmail orders the results by the claims_email field.\nfunc ByClaimsEmail(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmail, opts...).ToFunc()\n}\n\n// ByClaimsEmailVerified orders the results by the claims_email_verified field.\nfunc ByClaimsEmailVerified(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldClaimsEmailVerified, opts...).ToFunc()\n}\n\n// ByCreatedAt orders the results by the created_at field.\nfunc ByCreatedAt(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldCreatedAt, opts...).ToFunc()\n}\n\n// ByLastLogin orders the results by the last_login field.\nfunc ByLastLogin(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldLastLogin, opts...).ToFunc()\n}\n\n// ByBlockedUntil orders the results by the blocked_until field.\nfunc ByBlockedUntil(opts ...sql.OrderTermOption) OrderOption {\n\treturn sql.OrderByField(FieldBlockedUntil, opts...).ToFunc()\n}\n"
  },
  {
    "path": "storage/ent/db/useridentity/where.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage useridentity\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n)\n\n// ID filters vertices based on their ID field.\nfunc ID(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldID, id))\n}\n\n// IDEQ applies the EQ predicate on the ID field.\nfunc IDEQ(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldID, id))\n}\n\n// IDNEQ applies the NEQ predicate on the ID field.\nfunc IDNEQ(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldID, id))\n}\n\n// IDIn applies the In predicate on the ID field.\nfunc IDIn(ids ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldID, ids...))\n}\n\n// IDNotIn applies the NotIn predicate on the ID field.\nfunc IDNotIn(ids ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldID, ids...))\n}\n\n// IDGT applies the GT predicate on the ID field.\nfunc IDGT(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldID, id))\n}\n\n// IDGTE applies the GTE predicate on the ID field.\nfunc IDGTE(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldID, id))\n}\n\n// IDLT applies the LT predicate on the ID field.\nfunc IDLT(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldID, id))\n}\n\n// IDLTE applies the LTE predicate on the ID field.\nfunc IDLTE(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldID, id))\n}\n\n// IDEqualFold applies the EqualFold predicate on the ID field.\nfunc IDEqualFold(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEqualFold(FieldID, id))\n}\n\n// IDContainsFold applies the ContainsFold predicate on the ID field.\nfunc IDContainsFold(id string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContainsFold(FieldID, id))\n}\n\n// UserID applies equality check predicate on the \"user_id\" field. It's identical to UserIDEQ.\nfunc UserID(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldUserID, v))\n}\n\n// ConnectorID applies equality check predicate on the \"connector_id\" field. It's identical to ConnectorIDEQ.\nfunc ConnectorID(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ClaimsUserID applies equality check predicate on the \"claims_user_id\" field. It's identical to ClaimsUserIDEQ.\nfunc ClaimsUserID(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUsername applies equality check predicate on the \"claims_username\" field. It's identical to ClaimsUsernameEQ.\nfunc ClaimsUsername(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsPreferredUsername applies equality check predicate on the \"claims_preferred_username\" field. It's identical to ClaimsPreferredUsernameEQ.\nfunc ClaimsPreferredUsername(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsEmail applies equality check predicate on the \"claims_email\" field. It's identical to ClaimsEmailEQ.\nfunc ClaimsEmail(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerified applies equality check predicate on the \"claims_email_verified\" field. It's identical to ClaimsEmailVerifiedEQ.\nfunc ClaimsEmailVerified(v bool) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// Consents applies equality check predicate on the \"consents\" field. It's identical to ConsentsEQ.\nfunc Consents(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldConsents, v))\n}\n\n// MfaSecrets applies equality check predicate on the \"mfa_secrets\" field. It's identical to MfaSecretsEQ.\nfunc MfaSecrets(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldMfaSecrets, v))\n}\n\n// CreatedAt applies equality check predicate on the \"created_at\" field. It's identical to CreatedAtEQ.\nfunc CreatedAt(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldCreatedAt, v))\n}\n\n// LastLogin applies equality check predicate on the \"last_login\" field. It's identical to LastLoginEQ.\nfunc LastLogin(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldLastLogin, v))\n}\n\n// BlockedUntil applies equality check predicate on the \"blocked_until\" field. It's identical to BlockedUntilEQ.\nfunc BlockedUntil(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldBlockedUntil, v))\n}\n\n// UserIDEQ applies the EQ predicate on the \"user_id\" field.\nfunc UserIDEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldUserID, v))\n}\n\n// UserIDNEQ applies the NEQ predicate on the \"user_id\" field.\nfunc UserIDNEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldUserID, v))\n}\n\n// UserIDIn applies the In predicate on the \"user_id\" field.\nfunc UserIDIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldUserID, vs...))\n}\n\n// UserIDNotIn applies the NotIn predicate on the \"user_id\" field.\nfunc UserIDNotIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldUserID, vs...))\n}\n\n// UserIDGT applies the GT predicate on the \"user_id\" field.\nfunc UserIDGT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldUserID, v))\n}\n\n// UserIDGTE applies the GTE predicate on the \"user_id\" field.\nfunc UserIDGTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldUserID, v))\n}\n\n// UserIDLT applies the LT predicate on the \"user_id\" field.\nfunc UserIDLT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldUserID, v))\n}\n\n// UserIDLTE applies the LTE predicate on the \"user_id\" field.\nfunc UserIDLTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldUserID, v))\n}\n\n// UserIDContains applies the Contains predicate on the \"user_id\" field.\nfunc UserIDContains(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContains(FieldUserID, v))\n}\n\n// UserIDHasPrefix applies the HasPrefix predicate on the \"user_id\" field.\nfunc UserIDHasPrefix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasPrefix(FieldUserID, v))\n}\n\n// UserIDHasSuffix applies the HasSuffix predicate on the \"user_id\" field.\nfunc UserIDHasSuffix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasSuffix(FieldUserID, v))\n}\n\n// UserIDEqualFold applies the EqualFold predicate on the \"user_id\" field.\nfunc UserIDEqualFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEqualFold(FieldUserID, v))\n}\n\n// UserIDContainsFold applies the ContainsFold predicate on the \"user_id\" field.\nfunc UserIDContainsFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContainsFold(FieldUserID, v))\n}\n\n// ConnectorIDEQ applies the EQ predicate on the \"connector_id\" field.\nfunc ConnectorIDEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDNEQ applies the NEQ predicate on the \"connector_id\" field.\nfunc ConnectorIDNEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldConnectorID, v))\n}\n\n// ConnectorIDIn applies the In predicate on the \"connector_id\" field.\nfunc ConnectorIDIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDNotIn applies the NotIn predicate on the \"connector_id\" field.\nfunc ConnectorIDNotIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldConnectorID, vs...))\n}\n\n// ConnectorIDGT applies the GT predicate on the \"connector_id\" field.\nfunc ConnectorIDGT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldConnectorID, v))\n}\n\n// ConnectorIDGTE applies the GTE predicate on the \"connector_id\" field.\nfunc ConnectorIDGTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldConnectorID, v))\n}\n\n// ConnectorIDLT applies the LT predicate on the \"connector_id\" field.\nfunc ConnectorIDLT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldConnectorID, v))\n}\n\n// ConnectorIDLTE applies the LTE predicate on the \"connector_id\" field.\nfunc ConnectorIDLTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldConnectorID, v))\n}\n\n// ConnectorIDContains applies the Contains predicate on the \"connector_id\" field.\nfunc ConnectorIDContains(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContains(FieldConnectorID, v))\n}\n\n// ConnectorIDHasPrefix applies the HasPrefix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasPrefix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasPrefix(FieldConnectorID, v))\n}\n\n// ConnectorIDHasSuffix applies the HasSuffix predicate on the \"connector_id\" field.\nfunc ConnectorIDHasSuffix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasSuffix(FieldConnectorID, v))\n}\n\n// ConnectorIDEqualFold applies the EqualFold predicate on the \"connector_id\" field.\nfunc ConnectorIDEqualFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEqualFold(FieldConnectorID, v))\n}\n\n// ConnectorIDContainsFold applies the ContainsFold predicate on the \"connector_id\" field.\nfunc ConnectorIDContainsFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContainsFold(FieldConnectorID, v))\n}\n\n// ClaimsUserIDEQ applies the EQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDNEQ applies the NEQ predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDIn applies the In predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDNotIn applies the NotIn predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDNotIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldClaimsUserID, vs...))\n}\n\n// ClaimsUserIDGT applies the GT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDGTE applies the GTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDGTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLT applies the LT predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDLTE applies the LTE predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDLTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContains applies the Contains predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContains(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContains(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasPrefix applies the HasPrefix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasPrefix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDHasSuffix applies the HasSuffix predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDHasSuffix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDEqualFold applies the EqualFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDEqualFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUserIDContainsFold applies the ContainsFold predicate on the \"claims_user_id\" field.\nfunc ClaimsUserIDContainsFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsUserID, v))\n}\n\n// ClaimsUsernameEQ applies the EQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameNEQ applies the NEQ predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameIn applies the In predicate on the \"claims_username\" field.\nfunc ClaimsUsernameIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameNotIn applies the NotIn predicate on the \"claims_username\" field.\nfunc ClaimsUsernameNotIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldClaimsUsername, vs...))\n}\n\n// ClaimsUsernameGT applies the GT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameGTE applies the GTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameGTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLT applies the LT predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameLTE applies the LTE predicate on the \"claims_username\" field.\nfunc ClaimsUsernameLTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContains applies the Contains predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContains(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContains(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasPrefix applies the HasPrefix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasPrefix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameHasSuffix applies the HasSuffix predicate on the \"claims_username\" field.\nfunc ClaimsUsernameHasSuffix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameEqualFold applies the EqualFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameEqualFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsUsername, v))\n}\n\n// ClaimsUsernameContainsFold applies the ContainsFold predicate on the \"claims_username\" field.\nfunc ClaimsUsernameContainsFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsUsername, v))\n}\n\n// ClaimsPreferredUsernameEQ applies the EQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameNEQ applies the NEQ predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameIn applies the In predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameNotIn applies the NotIn predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameNotIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldClaimsPreferredUsername, vs...))\n}\n\n// ClaimsPreferredUsernameGT applies the GT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameGTE applies the GTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameGTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLT applies the LT predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameLTE applies the LTE predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameLTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContains applies the Contains predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContains(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContains(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasPrefix applies the HasPrefix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasPrefix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameHasSuffix applies the HasSuffix predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameHasSuffix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameEqualFold applies the EqualFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameEqualFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsPreferredUsernameContainsFold applies the ContainsFold predicate on the \"claims_preferred_username\" field.\nfunc ClaimsPreferredUsernameContainsFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsPreferredUsername, v))\n}\n\n// ClaimsEmailEQ applies the EQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailNEQ applies the NEQ predicate on the \"claims_email\" field.\nfunc ClaimsEmailNEQ(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailIn applies the In predicate on the \"claims_email\" field.\nfunc ClaimsEmailIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailNotIn applies the NotIn predicate on the \"claims_email\" field.\nfunc ClaimsEmailNotIn(vs ...string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldClaimsEmail, vs...))\n}\n\n// ClaimsEmailGT applies the GT predicate on the \"claims_email\" field.\nfunc ClaimsEmailGT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailGTE applies the GTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailGTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLT applies the LT predicate on the \"claims_email\" field.\nfunc ClaimsEmailLT(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailLTE applies the LTE predicate on the \"claims_email\" field.\nfunc ClaimsEmailLTE(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContains applies the Contains predicate on the \"claims_email\" field.\nfunc ClaimsEmailContains(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContains(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasPrefix applies the HasPrefix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasPrefix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasPrefix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailHasSuffix applies the HasSuffix predicate on the \"claims_email\" field.\nfunc ClaimsEmailHasSuffix(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldHasSuffix(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailEqualFold applies the EqualFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailEqualFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEqualFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailContainsFold applies the ContainsFold predicate on the \"claims_email\" field.\nfunc ClaimsEmailContainsFold(v string) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldContainsFold(FieldClaimsEmail, v))\n}\n\n// ClaimsEmailVerifiedEQ applies the EQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedEQ(v bool) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsEmailVerifiedNEQ applies the NEQ predicate on the \"claims_email_verified\" field.\nfunc ClaimsEmailVerifiedNEQ(v bool) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldClaimsEmailVerified, v))\n}\n\n// ClaimsGroupsIsNil applies the IsNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsIsNil() predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIsNull(FieldClaimsGroups))\n}\n\n// ClaimsGroupsNotNil applies the NotNil predicate on the \"claims_groups\" field.\nfunc ClaimsGroupsNotNil() predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotNull(FieldClaimsGroups))\n}\n\n// ConsentsEQ applies the EQ predicate on the \"consents\" field.\nfunc ConsentsEQ(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldConsents, v))\n}\n\n// ConsentsNEQ applies the NEQ predicate on the \"consents\" field.\nfunc ConsentsNEQ(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldConsents, v))\n}\n\n// ConsentsIn applies the In predicate on the \"consents\" field.\nfunc ConsentsIn(vs ...[]byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldConsents, vs...))\n}\n\n// ConsentsNotIn applies the NotIn predicate on the \"consents\" field.\nfunc ConsentsNotIn(vs ...[]byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldConsents, vs...))\n}\n\n// ConsentsGT applies the GT predicate on the \"consents\" field.\nfunc ConsentsGT(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldConsents, v))\n}\n\n// ConsentsGTE applies the GTE predicate on the \"consents\" field.\nfunc ConsentsGTE(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldConsents, v))\n}\n\n// ConsentsLT applies the LT predicate on the \"consents\" field.\nfunc ConsentsLT(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldConsents, v))\n}\n\n// ConsentsLTE applies the LTE predicate on the \"consents\" field.\nfunc ConsentsLTE(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldConsents, v))\n}\n\n// MfaSecretsEQ applies the EQ predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsEQ(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldMfaSecrets, v))\n}\n\n// MfaSecretsNEQ applies the NEQ predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsNEQ(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldMfaSecrets, v))\n}\n\n// MfaSecretsIn applies the In predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsIn(vs ...[]byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldMfaSecrets, vs...))\n}\n\n// MfaSecretsNotIn applies the NotIn predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsNotIn(vs ...[]byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldMfaSecrets, vs...))\n}\n\n// MfaSecretsGT applies the GT predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsGT(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldMfaSecrets, v))\n}\n\n// MfaSecretsGTE applies the GTE predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsGTE(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldMfaSecrets, v))\n}\n\n// MfaSecretsLT applies the LT predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsLT(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldMfaSecrets, v))\n}\n\n// MfaSecretsLTE applies the LTE predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsLTE(v []byte) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldMfaSecrets, v))\n}\n\n// MfaSecretsIsNil applies the IsNil predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsIsNil() predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIsNull(FieldMfaSecrets))\n}\n\n// MfaSecretsNotNil applies the NotNil predicate on the \"mfa_secrets\" field.\nfunc MfaSecretsNotNil() predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotNull(FieldMfaSecrets))\n}\n\n// CreatedAtEQ applies the EQ predicate on the \"created_at\" field.\nfunc CreatedAtEQ(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldCreatedAt, v))\n}\n\n// CreatedAtNEQ applies the NEQ predicate on the \"created_at\" field.\nfunc CreatedAtNEQ(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldCreatedAt, v))\n}\n\n// CreatedAtIn applies the In predicate on the \"created_at\" field.\nfunc CreatedAtIn(vs ...time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldCreatedAt, vs...))\n}\n\n// CreatedAtNotIn applies the NotIn predicate on the \"created_at\" field.\nfunc CreatedAtNotIn(vs ...time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldCreatedAt, vs...))\n}\n\n// CreatedAtGT applies the GT predicate on the \"created_at\" field.\nfunc CreatedAtGT(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldCreatedAt, v))\n}\n\n// CreatedAtGTE applies the GTE predicate on the \"created_at\" field.\nfunc CreatedAtGTE(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldCreatedAt, v))\n}\n\n// CreatedAtLT applies the LT predicate on the \"created_at\" field.\nfunc CreatedAtLT(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldCreatedAt, v))\n}\n\n// CreatedAtLTE applies the LTE predicate on the \"created_at\" field.\nfunc CreatedAtLTE(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldCreatedAt, v))\n}\n\n// LastLoginEQ applies the EQ predicate on the \"last_login\" field.\nfunc LastLoginEQ(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldLastLogin, v))\n}\n\n// LastLoginNEQ applies the NEQ predicate on the \"last_login\" field.\nfunc LastLoginNEQ(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldLastLogin, v))\n}\n\n// LastLoginIn applies the In predicate on the \"last_login\" field.\nfunc LastLoginIn(vs ...time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldLastLogin, vs...))\n}\n\n// LastLoginNotIn applies the NotIn predicate on the \"last_login\" field.\nfunc LastLoginNotIn(vs ...time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldLastLogin, vs...))\n}\n\n// LastLoginGT applies the GT predicate on the \"last_login\" field.\nfunc LastLoginGT(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldLastLogin, v))\n}\n\n// LastLoginGTE applies the GTE predicate on the \"last_login\" field.\nfunc LastLoginGTE(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldLastLogin, v))\n}\n\n// LastLoginLT applies the LT predicate on the \"last_login\" field.\nfunc LastLoginLT(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldLastLogin, v))\n}\n\n// LastLoginLTE applies the LTE predicate on the \"last_login\" field.\nfunc LastLoginLTE(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldLastLogin, v))\n}\n\n// BlockedUntilEQ applies the EQ predicate on the \"blocked_until\" field.\nfunc BlockedUntilEQ(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldEQ(FieldBlockedUntil, v))\n}\n\n// BlockedUntilNEQ applies the NEQ predicate on the \"blocked_until\" field.\nfunc BlockedUntilNEQ(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNEQ(FieldBlockedUntil, v))\n}\n\n// BlockedUntilIn applies the In predicate on the \"blocked_until\" field.\nfunc BlockedUntilIn(vs ...time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldIn(FieldBlockedUntil, vs...))\n}\n\n// BlockedUntilNotIn applies the NotIn predicate on the \"blocked_until\" field.\nfunc BlockedUntilNotIn(vs ...time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldNotIn(FieldBlockedUntil, vs...))\n}\n\n// BlockedUntilGT applies the GT predicate on the \"blocked_until\" field.\nfunc BlockedUntilGT(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGT(FieldBlockedUntil, v))\n}\n\n// BlockedUntilGTE applies the GTE predicate on the \"blocked_until\" field.\nfunc BlockedUntilGTE(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldGTE(FieldBlockedUntil, v))\n}\n\n// BlockedUntilLT applies the LT predicate on the \"blocked_until\" field.\nfunc BlockedUntilLT(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLT(FieldBlockedUntil, v))\n}\n\n// BlockedUntilLTE applies the LTE predicate on the \"blocked_until\" field.\nfunc BlockedUntilLTE(v time.Time) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.FieldLTE(FieldBlockedUntil, v))\n}\n\n// And groups predicates with the AND operator between them.\nfunc And(predicates ...predicate.UserIdentity) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.AndPredicates(predicates...))\n}\n\n// Or groups predicates with the OR operator between them.\nfunc Or(predicates ...predicate.UserIdentity) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.OrPredicates(predicates...))\n}\n\n// Not applies the not operator on the given predicate.\nfunc Not(p predicate.UserIdentity) predicate.UserIdentity {\n\treturn predicate.UserIdentity(sql.NotPredicates(p))\n}\n"
  },
  {
    "path": "storage/ent/db/useridentity.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n)\n\n// UserIdentity is the model entity for the UserIdentity schema.\ntype UserIdentity struct {\n\tconfig `json:\"-\"`\n\t// ID of the ent.\n\tID string `json:\"id,omitempty\"`\n\t// UserID holds the value of the \"user_id\" field.\n\tUserID string `json:\"user_id,omitempty\"`\n\t// ConnectorID holds the value of the \"connector_id\" field.\n\tConnectorID string `json:\"connector_id,omitempty\"`\n\t// ClaimsUserID holds the value of the \"claims_user_id\" field.\n\tClaimsUserID string `json:\"claims_user_id,omitempty\"`\n\t// ClaimsUsername holds the value of the \"claims_username\" field.\n\tClaimsUsername string `json:\"claims_username,omitempty\"`\n\t// ClaimsPreferredUsername holds the value of the \"claims_preferred_username\" field.\n\tClaimsPreferredUsername string `json:\"claims_preferred_username,omitempty\"`\n\t// ClaimsEmail holds the value of the \"claims_email\" field.\n\tClaimsEmail string `json:\"claims_email,omitempty\"`\n\t// ClaimsEmailVerified holds the value of the \"claims_email_verified\" field.\n\tClaimsEmailVerified bool `json:\"claims_email_verified,omitempty\"`\n\t// ClaimsGroups holds the value of the \"claims_groups\" field.\n\tClaimsGroups []string `json:\"claims_groups,omitempty\"`\n\t// Consents holds the value of the \"consents\" field.\n\tConsents []byte `json:\"consents,omitempty\"`\n\t// MfaSecrets holds the value of the \"mfa_secrets\" field.\n\tMfaSecrets *[]byte `json:\"mfa_secrets,omitempty\"`\n\t// CreatedAt holds the value of the \"created_at\" field.\n\tCreatedAt time.Time `json:\"created_at,omitempty\"`\n\t// LastLogin holds the value of the \"last_login\" field.\n\tLastLogin time.Time `json:\"last_login,omitempty\"`\n\t// BlockedUntil holds the value of the \"blocked_until\" field.\n\tBlockedUntil time.Time `json:\"blocked_until,omitempty\"`\n\tselectValues sql.SelectValues\n}\n\n// scanValues returns the types for scanning values from sql.Rows.\nfunc (*UserIdentity) scanValues(columns []string) ([]any, error) {\n\tvalues := make([]any, len(columns))\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase useridentity.FieldClaimsGroups, useridentity.FieldConsents, useridentity.FieldMfaSecrets:\n\t\t\tvalues[i] = new([]byte)\n\t\tcase useridentity.FieldClaimsEmailVerified:\n\t\t\tvalues[i] = new(sql.NullBool)\n\t\tcase useridentity.FieldID, useridentity.FieldUserID, useridentity.FieldConnectorID, useridentity.FieldClaimsUserID, useridentity.FieldClaimsUsername, useridentity.FieldClaimsPreferredUsername, useridentity.FieldClaimsEmail:\n\t\t\tvalues[i] = new(sql.NullString)\n\t\tcase useridentity.FieldCreatedAt, useridentity.FieldLastLogin, useridentity.FieldBlockedUntil:\n\t\t\tvalues[i] = new(sql.NullTime)\n\t\tdefault:\n\t\t\tvalues[i] = new(sql.UnknownType)\n\t\t}\n\t}\n\treturn values, nil\n}\n\n// assignValues assigns the values that were returned from sql.Rows (after scanning)\n// to the UserIdentity fields.\nfunc (_m *UserIdentity) assignValues(columns []string, values []any) error {\n\tif m, n := len(values), len(columns); m < n {\n\t\treturn fmt.Errorf(\"mismatch number of scan values: %d != %d\", m, n)\n\t}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase useridentity.FieldID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ID = value.String\n\t\t\t}\n\t\tcase useridentity.FieldUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.UserID = value.String\n\t\t\t}\n\t\tcase useridentity.FieldConnectorID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field connector_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ConnectorID = value.String\n\t\t\t}\n\t\tcase useridentity.FieldClaimsUserID:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_user_id\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUserID = value.String\n\t\t\t}\n\t\tcase useridentity.FieldClaimsUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsUsername = value.String\n\t\t\t}\n\t\tcase useridentity.FieldClaimsPreferredUsername:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_preferred_username\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsPreferredUsername = value.String\n\t\t\t}\n\t\tcase useridentity.FieldClaimsEmail:\n\t\t\tif value, ok := values[i].(*sql.NullString); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmail = value.String\n\t\t\t}\n\t\tcase useridentity.FieldClaimsEmailVerified:\n\t\t\tif value, ok := values[i].(*sql.NullBool); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_email_verified\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.ClaimsEmailVerified = value.Bool\n\t\t\t}\n\t\tcase useridentity.FieldClaimsGroups:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field claims_groups\", values[i])\n\t\t\t} else if value != nil && len(*value) > 0 {\n\t\t\t\tif err := json.Unmarshal(*value, &_m.ClaimsGroups); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"unmarshal field claims_groups: %w\", err)\n\t\t\t\t}\n\t\t\t}\n\t\tcase useridentity.FieldConsents:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field consents\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.Consents = *value\n\t\t\t}\n\t\tcase useridentity.FieldMfaSecrets:\n\t\t\tif value, ok := values[i].(*[]byte); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field mfa_secrets\", values[i])\n\t\t\t} else if value != nil {\n\t\t\t\t_m.MfaSecrets = value\n\t\t\t}\n\t\tcase useridentity.FieldCreatedAt:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field created_at\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.CreatedAt = value.Time\n\t\t\t}\n\t\tcase useridentity.FieldLastLogin:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field last_login\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.LastLogin = value.Time\n\t\t\t}\n\t\tcase useridentity.FieldBlockedUntil:\n\t\t\tif value, ok := values[i].(*sql.NullTime); !ok {\n\t\t\t\treturn fmt.Errorf(\"unexpected type %T for field blocked_until\", values[i])\n\t\t\t} else if value.Valid {\n\t\t\t\t_m.BlockedUntil = value.Time\n\t\t\t}\n\t\tdefault:\n\t\t\t_m.selectValues.Set(columns[i], values[i])\n\t\t}\n\t}\n\treturn nil\n}\n\n// Value returns the ent.Value that was dynamically selected and assigned to the UserIdentity.\n// This includes values selected through modifiers, order, etc.\nfunc (_m *UserIdentity) Value(name string) (ent.Value, error) {\n\treturn _m.selectValues.Get(name)\n}\n\n// Update returns a builder for updating this UserIdentity.\n// Note that you need to call UserIdentity.Unwrap() before calling this method if this UserIdentity\n// was returned from a transaction, and the transaction was committed or rolled back.\nfunc (_m *UserIdentity) Update() *UserIdentityUpdateOne {\n\treturn NewUserIdentityClient(_m.config).UpdateOne(_m)\n}\n\n// Unwrap unwraps the UserIdentity entity that was returned from a transaction after it was closed,\n// so that all future queries will be executed through the driver which created the transaction.\nfunc (_m *UserIdentity) Unwrap() *UserIdentity {\n\t_tx, ok := _m.config.driver.(*txDriver)\n\tif !ok {\n\t\tpanic(\"db: UserIdentity is not a transactional entity\")\n\t}\n\t_m.config.driver = _tx.drv\n\treturn _m\n}\n\n// String implements the fmt.Stringer.\nfunc (_m *UserIdentity) String() string {\n\tvar builder strings.Builder\n\tbuilder.WriteString(\"UserIdentity(\")\n\tbuilder.WriteString(fmt.Sprintf(\"id=%v, \", _m.ID))\n\tbuilder.WriteString(\"user_id=\")\n\tbuilder.WriteString(_m.UserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"connector_id=\")\n\tbuilder.WriteString(_m.ConnectorID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_user_id=\")\n\tbuilder.WriteString(_m.ClaimsUserID)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_username=\")\n\tbuilder.WriteString(_m.ClaimsUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_preferred_username=\")\n\tbuilder.WriteString(_m.ClaimsPreferredUsername)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email=\")\n\tbuilder.WriteString(_m.ClaimsEmail)\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_email_verified=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsEmailVerified))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"claims_groups=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.ClaimsGroups))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"consents=\")\n\tbuilder.WriteString(fmt.Sprintf(\"%v\", _m.Consents))\n\tbuilder.WriteString(\", \")\n\tif v := _m.MfaSecrets; v != nil {\n\t\tbuilder.WriteString(\"mfa_secrets=\")\n\t\tbuilder.WriteString(fmt.Sprintf(\"%v\", *v))\n\t}\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"created_at=\")\n\tbuilder.WriteString(_m.CreatedAt.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"last_login=\")\n\tbuilder.WriteString(_m.LastLogin.Format(time.ANSIC))\n\tbuilder.WriteString(\", \")\n\tbuilder.WriteString(\"blocked_until=\")\n\tbuilder.WriteString(_m.BlockedUntil.Format(time.ANSIC))\n\tbuilder.WriteByte(')')\n\treturn builder.String()\n}\n\n// UserIdentities is a parsable slice of UserIdentity.\ntype UserIdentities []*UserIdentity\n"
  },
  {
    "path": "storage/ent/db/useridentity_create.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n)\n\n// UserIdentityCreate is the builder for creating a UserIdentity entity.\ntype UserIdentityCreate struct {\n\tconfig\n\tmutation *UserIdentityMutation\n\thooks    []Hook\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_c *UserIdentityCreate) SetUserID(v string) *UserIdentityCreate {\n\t_c.mutation.SetUserID(v)\n\treturn _c\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_c *UserIdentityCreate) SetConnectorID(v string) *UserIdentityCreate {\n\t_c.mutation.SetConnectorID(v)\n\treturn _c\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_c *UserIdentityCreate) SetClaimsUserID(v string) *UserIdentityCreate {\n\t_c.mutation.SetClaimsUserID(v)\n\treturn _c\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_c *UserIdentityCreate) SetNillableClaimsUserID(v *string) *UserIdentityCreate {\n\tif v != nil {\n\t\t_c.SetClaimsUserID(*v)\n\t}\n\treturn _c\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_c *UserIdentityCreate) SetClaimsUsername(v string) *UserIdentityCreate {\n\t_c.mutation.SetClaimsUsername(v)\n\treturn _c\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_c *UserIdentityCreate) SetNillableClaimsUsername(v *string) *UserIdentityCreate {\n\tif v != nil {\n\t\t_c.SetClaimsUsername(*v)\n\t}\n\treturn _c\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_c *UserIdentityCreate) SetClaimsPreferredUsername(v string) *UserIdentityCreate {\n\t_c.mutation.SetClaimsPreferredUsername(v)\n\treturn _c\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_c *UserIdentityCreate) SetNillableClaimsPreferredUsername(v *string) *UserIdentityCreate {\n\tif v != nil {\n\t\t_c.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _c\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_c *UserIdentityCreate) SetClaimsEmail(v string) *UserIdentityCreate {\n\t_c.mutation.SetClaimsEmail(v)\n\treturn _c\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_c *UserIdentityCreate) SetNillableClaimsEmail(v *string) *UserIdentityCreate {\n\tif v != nil {\n\t\t_c.SetClaimsEmail(*v)\n\t}\n\treturn _c\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_c *UserIdentityCreate) SetClaimsEmailVerified(v bool) *UserIdentityCreate {\n\t_c.mutation.SetClaimsEmailVerified(v)\n\treturn _c\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_c *UserIdentityCreate) SetNillableClaimsEmailVerified(v *bool) *UserIdentityCreate {\n\tif v != nil {\n\t\t_c.SetClaimsEmailVerified(*v)\n\t}\n\treturn _c\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_c *UserIdentityCreate) SetClaimsGroups(v []string) *UserIdentityCreate {\n\t_c.mutation.SetClaimsGroups(v)\n\treturn _c\n}\n\n// SetConsents sets the \"consents\" field.\nfunc (_c *UserIdentityCreate) SetConsents(v []byte) *UserIdentityCreate {\n\t_c.mutation.SetConsents(v)\n\treturn _c\n}\n\n// SetMfaSecrets sets the \"mfa_secrets\" field.\nfunc (_c *UserIdentityCreate) SetMfaSecrets(v []byte) *UserIdentityCreate {\n\t_c.mutation.SetMfaSecrets(v)\n\treturn _c\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_c *UserIdentityCreate) SetCreatedAt(v time.Time) *UserIdentityCreate {\n\t_c.mutation.SetCreatedAt(v)\n\treturn _c\n}\n\n// SetLastLogin sets the \"last_login\" field.\nfunc (_c *UserIdentityCreate) SetLastLogin(v time.Time) *UserIdentityCreate {\n\t_c.mutation.SetLastLogin(v)\n\treturn _c\n}\n\n// SetBlockedUntil sets the \"blocked_until\" field.\nfunc (_c *UserIdentityCreate) SetBlockedUntil(v time.Time) *UserIdentityCreate {\n\t_c.mutation.SetBlockedUntil(v)\n\treturn _c\n}\n\n// SetID sets the \"id\" field.\nfunc (_c *UserIdentityCreate) SetID(v string) *UserIdentityCreate {\n\t_c.mutation.SetID(v)\n\treturn _c\n}\n\n// Mutation returns the UserIdentityMutation object of the builder.\nfunc (_c *UserIdentityCreate) Mutation() *UserIdentityMutation {\n\treturn _c.mutation\n}\n\n// Save creates the UserIdentity in the database.\nfunc (_c *UserIdentityCreate) Save(ctx context.Context) (*UserIdentity, error) {\n\t_c.defaults()\n\treturn withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks)\n}\n\n// SaveX calls Save and panics if Save returns an error.\nfunc (_c *UserIdentityCreate) SaveX(ctx context.Context) *UserIdentity {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *UserIdentityCreate) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *UserIdentityCreate) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// defaults sets the default values of the builder before save.\nfunc (_c *UserIdentityCreate) defaults() {\n\tif _, ok := _c.mutation.ClaimsUserID(); !ok {\n\t\tv := useridentity.DefaultClaimsUserID\n\t\t_c.mutation.SetClaimsUserID(v)\n\t}\n\tif _, ok := _c.mutation.ClaimsUsername(); !ok {\n\t\tv := useridentity.DefaultClaimsUsername\n\t\t_c.mutation.SetClaimsUsername(v)\n\t}\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\tv := useridentity.DefaultClaimsPreferredUsername\n\t\t_c.mutation.SetClaimsPreferredUsername(v)\n\t}\n\tif _, ok := _c.mutation.ClaimsEmail(); !ok {\n\t\tv := useridentity.DefaultClaimsEmail\n\t\t_c.mutation.SetClaimsEmail(v)\n\t}\n\tif _, ok := _c.mutation.ClaimsEmailVerified(); !ok {\n\t\tv := useridentity.DefaultClaimsEmailVerified\n\t\t_c.mutation.SetClaimsEmailVerified(v)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_c *UserIdentityCreate) check() error {\n\tif _, ok := _c.mutation.UserID(); !ok {\n\t\treturn &ValidationError{Name: \"user_id\", err: errors.New(`db: missing required field \"UserIdentity.user_id\"`)}\n\t}\n\tif v, ok := _c.mutation.UserID(); ok {\n\t\tif err := useridentity.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"UserIdentity.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ConnectorID(); !ok {\n\t\treturn &ValidationError{Name: \"connector_id\", err: errors.New(`db: missing required field \"UserIdentity.connector_id\"`)}\n\t}\n\tif v, ok := _c.mutation.ConnectorID(); ok {\n\t\tif err := useridentity.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"UserIdentity.connector_id\": %w`, err)}\n\t\t}\n\t}\n\tif _, ok := _c.mutation.ClaimsUserID(); !ok {\n\t\treturn &ValidationError{Name: \"claims_user_id\", err: errors.New(`db: missing required field \"UserIdentity.claims_user_id\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_username\", err: errors.New(`db: missing required field \"UserIdentity.claims_username\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsPreferredUsername(); !ok {\n\t\treturn &ValidationError{Name: \"claims_preferred_username\", err: errors.New(`db: missing required field \"UserIdentity.claims_preferred_username\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmail(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email\", err: errors.New(`db: missing required field \"UserIdentity.claims_email\"`)}\n\t}\n\tif _, ok := _c.mutation.ClaimsEmailVerified(); !ok {\n\t\treturn &ValidationError{Name: \"claims_email_verified\", err: errors.New(`db: missing required field \"UserIdentity.claims_email_verified\"`)}\n\t}\n\tif _, ok := _c.mutation.Consents(); !ok {\n\t\treturn &ValidationError{Name: \"consents\", err: errors.New(`db: missing required field \"UserIdentity.consents\"`)}\n\t}\n\tif _, ok := _c.mutation.CreatedAt(); !ok {\n\t\treturn &ValidationError{Name: \"created_at\", err: errors.New(`db: missing required field \"UserIdentity.created_at\"`)}\n\t}\n\tif _, ok := _c.mutation.LastLogin(); !ok {\n\t\treturn &ValidationError{Name: \"last_login\", err: errors.New(`db: missing required field \"UserIdentity.last_login\"`)}\n\t}\n\tif _, ok := _c.mutation.BlockedUntil(); !ok {\n\t\treturn &ValidationError{Name: \"blocked_until\", err: errors.New(`db: missing required field \"UserIdentity.blocked_until\"`)}\n\t}\n\tif v, ok := _c.mutation.ID(); ok {\n\t\tif err := useridentity.IDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"id\", err: fmt.Errorf(`db: validator failed for field \"UserIdentity.id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_c *UserIdentityCreate) sqlSave(ctx context.Context) (*UserIdentity, error) {\n\tif err := _c.check(); err != nil {\n\t\treturn nil, err\n\t}\n\t_node, _spec := _c.createSpec()\n\tif err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil {\n\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\tif _spec.ID.Value != nil {\n\t\tif id, ok := _spec.ID.Value.(string); ok {\n\t\t\t_node.ID = id\n\t\t} else {\n\t\t\treturn nil, fmt.Errorf(\"unexpected UserIdentity.ID type: %T\", _spec.ID.Value)\n\t\t}\n\t}\n\t_c.mutation.id = &_node.ID\n\t_c.mutation.done = true\n\treturn _node, nil\n}\n\nfunc (_c *UserIdentityCreate) createSpec() (*UserIdentity, *sqlgraph.CreateSpec) {\n\tvar (\n\t\t_node = &UserIdentity{config: _c.config}\n\t\t_spec = sqlgraph.NewCreateSpec(useridentity.Table, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString))\n\t)\n\tif id, ok := _c.mutation.ID(); ok {\n\t\t_node.ID = id\n\t\t_spec.ID.Value = id\n\t}\n\tif value, ok := _c.mutation.UserID(); ok {\n\t\t_spec.SetField(useridentity.FieldUserID, field.TypeString, value)\n\t\t_node.UserID = value\n\t}\n\tif value, ok := _c.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(useridentity.FieldConnectorID, field.TypeString, value)\n\t\t_node.ConnectorID = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsUserID, field.TypeString, value)\n\t\t_node.ClaimsUserID = value\n\t}\n\tif value, ok := _c.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsUsername, field.TypeString, value)\n\t\t_node.ClaimsUsername = value\n\t}\n\tif value, ok := _c.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsPreferredUsername, field.TypeString, value)\n\t\t_node.ClaimsPreferredUsername = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsEmail, field.TypeString, value)\n\t\t_node.ClaimsEmail = value\n\t}\n\tif value, ok := _c.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsEmailVerified, field.TypeBool, value)\n\t\t_node.ClaimsEmailVerified = value\n\t}\n\tif value, ok := _c.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsGroups, field.TypeJSON, value)\n\t\t_node.ClaimsGroups = value\n\t}\n\tif value, ok := _c.mutation.Consents(); ok {\n\t\t_spec.SetField(useridentity.FieldConsents, field.TypeBytes, value)\n\t\t_node.Consents = value\n\t}\n\tif value, ok := _c.mutation.MfaSecrets(); ok {\n\t\t_spec.SetField(useridentity.FieldMfaSecrets, field.TypeBytes, value)\n\t\t_node.MfaSecrets = &value\n\t}\n\tif value, ok := _c.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(useridentity.FieldCreatedAt, field.TypeTime, value)\n\t\t_node.CreatedAt = value\n\t}\n\tif value, ok := _c.mutation.LastLogin(); ok {\n\t\t_spec.SetField(useridentity.FieldLastLogin, field.TypeTime, value)\n\t\t_node.LastLogin = value\n\t}\n\tif value, ok := _c.mutation.BlockedUntil(); ok {\n\t\t_spec.SetField(useridentity.FieldBlockedUntil, field.TypeTime, value)\n\t\t_node.BlockedUntil = value\n\t}\n\treturn _node, _spec\n}\n\n// UserIdentityCreateBulk is the builder for creating many UserIdentity entities in bulk.\ntype UserIdentityCreateBulk struct {\n\tconfig\n\terr      error\n\tbuilders []*UserIdentityCreate\n}\n\n// Save creates the UserIdentity entities in the database.\nfunc (_c *UserIdentityCreateBulk) Save(ctx context.Context) ([]*UserIdentity, error) {\n\tif _c.err != nil {\n\t\treturn nil, _c.err\n\t}\n\tspecs := make([]*sqlgraph.CreateSpec, len(_c.builders))\n\tnodes := make([]*UserIdentity, len(_c.builders))\n\tmutators := make([]Mutator, len(_c.builders))\n\tfor i := range _c.builders {\n\t\tfunc(i int, root context.Context) {\n\t\t\tbuilder := _c.builders[i]\n\t\t\tbuilder.defaults()\n\t\t\tvar mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) {\n\t\t\t\tmutation, ok := m.(*UserIdentityMutation)\n\t\t\t\tif !ok {\n\t\t\t\t\treturn nil, fmt.Errorf(\"unexpected mutation type %T\", m)\n\t\t\t\t}\n\t\t\t\tif err := builder.check(); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tbuilder.mutation = mutation\n\t\t\t\tvar err error\n\t\t\t\tnodes[i], specs[i] = builder.createSpec()\n\t\t\t\tif i < len(mutators)-1 {\n\t\t\t\t\t_, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation)\n\t\t\t\t} else {\n\t\t\t\t\tspec := &sqlgraph.BatchCreateSpec{Nodes: specs}\n\t\t\t\t\t// Invoke the actual operation on the latest mutation in the chain.\n\t\t\t\t\tif err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil {\n\t\t\t\t\t\tif sqlgraph.IsConstraintError(err) {\n\t\t\t\t\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t\tmutation.id = &nodes[i].ID\n\t\t\t\tmutation.done = true\n\t\t\t\treturn nodes[i], nil\n\t\t\t})\n\t\t\tfor i := len(builder.hooks) - 1; i >= 0; i-- {\n\t\t\t\tmut = builder.hooks[i](mut)\n\t\t\t}\n\t\t\tmutators[i] = mut\n\t\t}(i, ctx)\n\t}\n\tif len(mutators) > 0 {\n\t\tif _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn nodes, nil\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_c *UserIdentityCreateBulk) SaveX(ctx context.Context) []*UserIdentity {\n\tv, err := _c.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn v\n}\n\n// Exec executes the query.\nfunc (_c *UserIdentityCreateBulk) Exec(ctx context.Context) error {\n\t_, err := _c.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_c *UserIdentityCreateBulk) ExecX(ctx context.Context) {\n\tif err := _c.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/useridentity_delete.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n)\n\n// UserIdentityDelete is the builder for deleting a UserIdentity entity.\ntype UserIdentityDelete struct {\n\tconfig\n\thooks    []Hook\n\tmutation *UserIdentityMutation\n}\n\n// Where appends a list predicates to the UserIdentityDelete builder.\nfunc (_d *UserIdentityDelete) Where(ps ...predicate.UserIdentity) *UserIdentityDelete {\n\t_d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query and returns how many vertices were deleted.\nfunc (_d *UserIdentityDelete) Exec(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks)\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *UserIdentityDelete) ExecX(ctx context.Context) int {\n\tn, err := _d.Exec(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn n\n}\n\nfunc (_d *UserIdentityDelete) sqlExec(ctx context.Context) (int, error) {\n\t_spec := sqlgraph.NewDeleteSpec(useridentity.Table, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString))\n\tif ps := _d.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\taffected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec)\n\tif err != nil && sqlgraph.IsConstraintError(err) {\n\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t}\n\t_d.mutation.done = true\n\treturn affected, err\n}\n\n// UserIdentityDeleteOne is the builder for deleting a single UserIdentity entity.\ntype UserIdentityDeleteOne struct {\n\t_d *UserIdentityDelete\n}\n\n// Where appends a list predicates to the UserIdentityDelete builder.\nfunc (_d *UserIdentityDeleteOne) Where(ps ...predicate.UserIdentity) *UserIdentityDeleteOne {\n\t_d._d.mutation.Where(ps...)\n\treturn _d\n}\n\n// Exec executes the deletion query.\nfunc (_d *UserIdentityDeleteOne) Exec(ctx context.Context) error {\n\tn, err := _d._d.Exec(ctx)\n\tswitch {\n\tcase err != nil:\n\t\treturn err\n\tcase n == 0:\n\t\treturn &NotFoundError{useridentity.Label}\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_d *UserIdentityDeleteOne) ExecX(ctx context.Context) {\n\tif err := _d.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "storage/ent/db/useridentity_query.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"math\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n)\n\n// UserIdentityQuery is the builder for querying UserIdentity entities.\ntype UserIdentityQuery struct {\n\tconfig\n\tctx        *QueryContext\n\torder      []useridentity.OrderOption\n\tinters     []Interceptor\n\tpredicates []predicate.UserIdentity\n\t// intermediate query (i.e. traversal path).\n\tsql  *sql.Selector\n\tpath func(context.Context) (*sql.Selector, error)\n}\n\n// Where adds a new predicate for the UserIdentityQuery builder.\nfunc (_q *UserIdentityQuery) Where(ps ...predicate.UserIdentity) *UserIdentityQuery {\n\t_q.predicates = append(_q.predicates, ps...)\n\treturn _q\n}\n\n// Limit the number of records to be returned by this query.\nfunc (_q *UserIdentityQuery) Limit(limit int) *UserIdentityQuery {\n\t_q.ctx.Limit = &limit\n\treturn _q\n}\n\n// Offset to start from.\nfunc (_q *UserIdentityQuery) Offset(offset int) *UserIdentityQuery {\n\t_q.ctx.Offset = &offset\n\treturn _q\n}\n\n// Unique configures the query builder to filter duplicate records on query.\n// By default, unique is set to true, and can be disabled using this method.\nfunc (_q *UserIdentityQuery) Unique(unique bool) *UserIdentityQuery {\n\t_q.ctx.Unique = &unique\n\treturn _q\n}\n\n// Order specifies how the records should be ordered.\nfunc (_q *UserIdentityQuery) Order(o ...useridentity.OrderOption) *UserIdentityQuery {\n\t_q.order = append(_q.order, o...)\n\treturn _q\n}\n\n// First returns the first UserIdentity entity from the query.\n// Returns a *NotFoundError when no UserIdentity was found.\nfunc (_q *UserIdentityQuery) First(ctx context.Context) (*UserIdentity, error) {\n\tnodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nil, &NotFoundError{useridentity.Label}\n\t}\n\treturn nodes[0], nil\n}\n\n// FirstX is like First, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) FirstX(ctx context.Context) *UserIdentity {\n\tnode, err := _q.First(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// FirstID returns the first UserIdentity ID from the query.\n// Returns a *NotFoundError when no UserIdentity ID was found.\nfunc (_q *UserIdentityQuery) FirstID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil {\n\t\treturn\n\t}\n\tif len(ids) == 0 {\n\t\terr = &NotFoundError{useridentity.Label}\n\t\treturn\n\t}\n\treturn ids[0], nil\n}\n\n// FirstIDX is like FirstID, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) FirstIDX(ctx context.Context) string {\n\tid, err := _q.FirstID(ctx)\n\tif err != nil && !IsNotFound(err) {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// Only returns a single UserIdentity entity found by the query, ensuring it only returns one.\n// Returns a *NotSingularError when more than one UserIdentity entity is found.\n// Returns a *NotFoundError when no UserIdentity entities are found.\nfunc (_q *UserIdentityQuery) Only(ctx context.Context) (*UserIdentity, error) {\n\tnodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tswitch len(nodes) {\n\tcase 1:\n\t\treturn nodes[0], nil\n\tcase 0:\n\t\treturn nil, &NotFoundError{useridentity.Label}\n\tdefault:\n\t\treturn nil, &NotSingularError{useridentity.Label}\n\t}\n}\n\n// OnlyX is like Only, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) OnlyX(ctx context.Context) *UserIdentity {\n\tnode, err := _q.Only(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// OnlyID is like Only, but returns the only UserIdentity ID in the query.\n// Returns a *NotSingularError when more than one UserIdentity ID is found.\n// Returns a *NotFoundError when no entities are found.\nfunc (_q *UserIdentityQuery) OnlyID(ctx context.Context) (id string, err error) {\n\tvar ids []string\n\tif ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil {\n\t\treturn\n\t}\n\tswitch len(ids) {\n\tcase 1:\n\t\tid = ids[0]\n\tcase 0:\n\t\terr = &NotFoundError{useridentity.Label}\n\tdefault:\n\t\terr = &NotSingularError{useridentity.Label}\n\t}\n\treturn\n}\n\n// OnlyIDX is like OnlyID, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) OnlyIDX(ctx context.Context) string {\n\tid, err := _q.OnlyID(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn id\n}\n\n// All executes the query and returns a list of UserIdentities.\nfunc (_q *UserIdentityQuery) All(ctx context.Context) ([]*UserIdentity, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn nil, err\n\t}\n\tqr := querierAll[[]*UserIdentity, *UserIdentityQuery]()\n\treturn withInterceptors[[]*UserIdentity](ctx, _q, qr, _q.inters)\n}\n\n// AllX is like All, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) AllX(ctx context.Context) []*UserIdentity {\n\tnodes, err := _q.All(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn nodes\n}\n\n// IDs executes the query and returns a list of UserIdentity IDs.\nfunc (_q *UserIdentityQuery) IDs(ctx context.Context) (ids []string, err error) {\n\tif _q.ctx.Unique == nil && _q.path != nil {\n\t\t_q.Unique(true)\n\t}\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs)\n\tif err = _q.Select(useridentity.FieldID).Scan(ctx, &ids); err != nil {\n\t\treturn nil, err\n\t}\n\treturn ids, nil\n}\n\n// IDsX is like IDs, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) IDsX(ctx context.Context) []string {\n\tids, err := _q.IDs(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn ids\n}\n\n// Count returns the count of the given query.\nfunc (_q *UserIdentityQuery) Count(ctx context.Context) (int, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount)\n\tif err := _q.prepareQuery(ctx); err != nil {\n\t\treturn 0, err\n\t}\n\treturn withInterceptors[int](ctx, _q, querierCount[*UserIdentityQuery](), _q.inters)\n}\n\n// CountX is like Count, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) CountX(ctx context.Context) int {\n\tcount, err := _q.Count(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn count\n}\n\n// Exist returns true if the query has elements in the graph.\nfunc (_q *UserIdentityQuery) Exist(ctx context.Context) (bool, error) {\n\tctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist)\n\tswitch _, err := _q.FirstID(ctx); {\n\tcase IsNotFound(err):\n\t\treturn false, nil\n\tcase err != nil:\n\t\treturn false, fmt.Errorf(\"db: check existence: %w\", err)\n\tdefault:\n\t\treturn true, nil\n\t}\n}\n\n// ExistX is like Exist, but panics if an error occurs.\nfunc (_q *UserIdentityQuery) ExistX(ctx context.Context) bool {\n\texist, err := _q.Exist(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn exist\n}\n\n// Clone returns a duplicate of the UserIdentityQuery builder, including all associated steps. It can be\n// used to prepare common query builders and use them differently after the clone is made.\nfunc (_q *UserIdentityQuery) Clone() *UserIdentityQuery {\n\tif _q == nil {\n\t\treturn nil\n\t}\n\treturn &UserIdentityQuery{\n\t\tconfig:     _q.config,\n\t\tctx:        _q.ctx.Clone(),\n\t\torder:      append([]useridentity.OrderOption{}, _q.order...),\n\t\tinters:     append([]Interceptor{}, _q.inters...),\n\t\tpredicates: append([]predicate.UserIdentity{}, _q.predicates...),\n\t\t// clone intermediate query.\n\t\tsql:  _q.sql.Clone(),\n\t\tpath: _q.path,\n\t}\n}\n\n// GroupBy is used to group vertices by one or more fields/columns.\n// It is often used with aggregate functions, like: count, max, mean, min, sum.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserID string `json:\"user_id,omitempty\"`\n//\t\tCount int `json:\"count,omitempty\"`\n//\t}\n//\n//\tclient.UserIdentity.Query().\n//\t\tGroupBy(useridentity.FieldUserID).\n//\t\tAggregate(db.Count()).\n//\t\tScan(ctx, &v)\nfunc (_q *UserIdentityQuery) GroupBy(field string, fields ...string) *UserIdentityGroupBy {\n\t_q.ctx.Fields = append([]string{field}, fields...)\n\tgrbuild := &UserIdentityGroupBy{build: _q}\n\tgrbuild.flds = &_q.ctx.Fields\n\tgrbuild.label = useridentity.Label\n\tgrbuild.scan = grbuild.Scan\n\treturn grbuild\n}\n\n// Select allows the selection one or more fields/columns for the given query,\n// instead of selecting all fields in the entity.\n//\n// Example:\n//\n//\tvar v []struct {\n//\t\tUserID string `json:\"user_id,omitempty\"`\n//\t}\n//\n//\tclient.UserIdentity.Query().\n//\t\tSelect(useridentity.FieldUserID).\n//\t\tScan(ctx, &v)\nfunc (_q *UserIdentityQuery) Select(fields ...string) *UserIdentitySelect {\n\t_q.ctx.Fields = append(_q.ctx.Fields, fields...)\n\tsbuild := &UserIdentitySelect{UserIdentityQuery: _q}\n\tsbuild.label = useridentity.Label\n\tsbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan\n\treturn sbuild\n}\n\n// Aggregate returns a UserIdentitySelect configured with the given aggregations.\nfunc (_q *UserIdentityQuery) Aggregate(fns ...AggregateFunc) *UserIdentitySelect {\n\treturn _q.Select().Aggregate(fns...)\n}\n\nfunc (_q *UserIdentityQuery) prepareQuery(ctx context.Context) error {\n\tfor _, inter := range _q.inters {\n\t\tif inter == nil {\n\t\t\treturn fmt.Errorf(\"db: uninitialized interceptor (forgotten import db/runtime?)\")\n\t\t}\n\t\tif trv, ok := inter.(Traverser); ok {\n\t\t\tif err := trv.Traverse(ctx, _q); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\tfor _, f := range _q.ctx.Fields {\n\t\tif !useridentity.ValidColumn(f) {\n\t\t\treturn &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t}\n\t}\n\tif _q.path != nil {\n\t\tprev, err := _q.path(ctx)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_q.sql = prev\n\t}\n\treturn nil\n}\n\nfunc (_q *UserIdentityQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*UserIdentity, error) {\n\tvar (\n\t\tnodes = []*UserIdentity{}\n\t\t_spec = _q.querySpec()\n\t)\n\t_spec.ScanValues = func(columns []string) ([]any, error) {\n\t\treturn (*UserIdentity).scanValues(nil, columns)\n\t}\n\t_spec.Assign = func(columns []string, values []any) error {\n\t\tnode := &UserIdentity{config: _q.config}\n\t\tnodes = append(nodes, node)\n\t\treturn node.assignValues(columns, values)\n\t}\n\tfor i := range hooks {\n\t\thooks[i](ctx, _spec)\n\t}\n\tif err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodes) == 0 {\n\t\treturn nodes, nil\n\t}\n\treturn nodes, nil\n}\n\nfunc (_q *UserIdentityQuery) sqlCount(ctx context.Context) (int, error) {\n\t_spec := _q.querySpec()\n\t_spec.Node.Columns = _q.ctx.Fields\n\tif len(_q.ctx.Fields) > 0 {\n\t\t_spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique\n\t}\n\treturn sqlgraph.CountNodes(ctx, _q.driver, _spec)\n}\n\nfunc (_q *UserIdentityQuery) querySpec() *sqlgraph.QuerySpec {\n\t_spec := sqlgraph.NewQuerySpec(useridentity.Table, useridentity.Columns, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString))\n\t_spec.From = _q.sql\n\tif unique := _q.ctx.Unique; unique != nil {\n\t\t_spec.Unique = *unique\n\t} else if _q.path != nil {\n\t\t_spec.Unique = true\n\t}\n\tif fields := _q.ctx.Fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, useridentity.FieldID)\n\t\tfor i := range fields {\n\t\t\tif fields[i] != useridentity.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, fields[i])\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _q.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\t_spec.Limit = *limit\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t_spec.Offset = *offset\n\t}\n\tif ps := _q.order; len(ps) > 0 {\n\t\t_spec.Order = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\treturn _spec\n}\n\nfunc (_q *UserIdentityQuery) sqlQuery(ctx context.Context) *sql.Selector {\n\tbuilder := sql.Dialect(_q.driver.Dialect())\n\tt1 := builder.Table(useridentity.Table)\n\tcolumns := _q.ctx.Fields\n\tif len(columns) == 0 {\n\t\tcolumns = useridentity.Columns\n\t}\n\tselector := builder.Select(t1.Columns(columns...)...).From(t1)\n\tif _q.sql != nil {\n\t\tselector = _q.sql\n\t\tselector.Select(selector.Columns(columns...)...)\n\t}\n\tif _q.ctx.Unique != nil && *_q.ctx.Unique {\n\t\tselector.Distinct()\n\t}\n\tfor _, p := range _q.predicates {\n\t\tp(selector)\n\t}\n\tfor _, p := range _q.order {\n\t\tp(selector)\n\t}\n\tif offset := _q.ctx.Offset; offset != nil {\n\t\t// limit is mandatory for offset clause. We start\n\t\t// with default value, and override it below if needed.\n\t\tselector.Offset(*offset).Limit(math.MaxInt32)\n\t}\n\tif limit := _q.ctx.Limit; limit != nil {\n\t\tselector.Limit(*limit)\n\t}\n\treturn selector\n}\n\n// UserIdentityGroupBy is the group-by builder for UserIdentity entities.\ntype UserIdentityGroupBy struct {\n\tselector\n\tbuild *UserIdentityQuery\n}\n\n// Aggregate adds the given aggregation functions to the group-by query.\nfunc (_g *UserIdentityGroupBy) Aggregate(fns ...AggregateFunc) *UserIdentityGroupBy {\n\t_g.fns = append(_g.fns, fns...)\n\treturn _g\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_g *UserIdentityGroupBy) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy)\n\tif err := _g.build.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*UserIdentityQuery, *UserIdentityGroupBy](ctx, _g.build, _g, _g.build.inters, v)\n}\n\nfunc (_g *UserIdentityGroupBy) sqlScan(ctx context.Context, root *UserIdentityQuery, v any) error {\n\tselector := root.sqlQuery(ctx).Select()\n\taggregation := make([]string, 0, len(_g.fns))\n\tfor _, fn := range _g.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tif len(selector.SelectedColumns()) == 0 {\n\t\tcolumns := make([]string, 0, len(*_g.flds)+len(_g.fns))\n\t\tfor _, f := range *_g.flds {\n\t\t\tcolumns = append(columns, selector.C(f))\n\t\t}\n\t\tcolumns = append(columns, aggregation...)\n\t\tselector.Select(columns...)\n\t}\n\tselector.GroupBy(selector.Columns(*_g.flds...)...)\n\tif err := selector.Err(); err != nil {\n\t\treturn err\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _g.build.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n\n// UserIdentitySelect is the builder for selecting fields of UserIdentity entities.\ntype UserIdentitySelect struct {\n\t*UserIdentityQuery\n\tselector\n}\n\n// Aggregate adds the given aggregation functions to the selector query.\nfunc (_s *UserIdentitySelect) Aggregate(fns ...AggregateFunc) *UserIdentitySelect {\n\t_s.fns = append(_s.fns, fns...)\n\treturn _s\n}\n\n// Scan applies the selector query and scans the result into the given value.\nfunc (_s *UserIdentitySelect) Scan(ctx context.Context, v any) error {\n\tctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect)\n\tif err := _s.prepareQuery(ctx); err != nil {\n\t\treturn err\n\t}\n\treturn scanWithInterceptors[*UserIdentityQuery, *UserIdentitySelect](ctx, _s.UserIdentityQuery, _s, _s.inters, v)\n}\n\nfunc (_s *UserIdentitySelect) sqlScan(ctx context.Context, root *UserIdentityQuery, v any) error {\n\tselector := root.sqlQuery(ctx)\n\taggregation := make([]string, 0, len(_s.fns))\n\tfor _, fn := range _s.fns {\n\t\taggregation = append(aggregation, fn(selector))\n\t}\n\tswitch n := len(*_s.selector.flds); {\n\tcase n == 0 && len(aggregation) > 0:\n\t\tselector.Select(aggregation...)\n\tcase n != 0 && len(aggregation) > 0:\n\t\tselector.AppendSelect(aggregation...)\n\t}\n\trows := &sql.Rows{}\n\tquery, args := selector.Query()\n\tif err := _s.driver.Query(ctx, query, args, rows); err != nil {\n\t\treturn err\n\t}\n\tdefer rows.Close()\n\treturn sql.ScanSlice(rows, v)\n}\n"
  },
  {
    "path": "storage/ent/db/useridentity_update.go",
    "content": "// Code generated by ent, DO NOT EDIT.\n\npackage db\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t\"entgo.io/ent/dialect/sql/sqlgraph\"\n\t\"entgo.io/ent/dialect/sql/sqljson\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/dexidp/dex/storage/ent/db/predicate\"\n\t\"github.com/dexidp/dex/storage/ent/db/useridentity\"\n)\n\n// UserIdentityUpdate is the builder for updating UserIdentity entities.\ntype UserIdentityUpdate struct {\n\tconfig\n\thooks    []Hook\n\tmutation *UserIdentityMutation\n}\n\n// Where appends a list predicates to the UserIdentityUpdate builder.\nfunc (_u *UserIdentityUpdate) Where(ps ...predicate.UserIdentity) *UserIdentityUpdate {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *UserIdentityUpdate) SetUserID(v string) *UserIdentityUpdate {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableUserID(v *string) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *UserIdentityUpdate) SetConnectorID(v string) *UserIdentityUpdate {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableConnectorID(v *string) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *UserIdentityUpdate) SetClaimsUserID(v string) *UserIdentityUpdate {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableClaimsUserID(v *string) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *UserIdentityUpdate) SetClaimsUsername(v string) *UserIdentityUpdate {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableClaimsUsername(v *string) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *UserIdentityUpdate) SetClaimsPreferredUsername(v string) *UserIdentityUpdate {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableClaimsPreferredUsername(v *string) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *UserIdentityUpdate) SetClaimsEmail(v string) *UserIdentityUpdate {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableClaimsEmail(v *string) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *UserIdentityUpdate) SetClaimsEmailVerified(v bool) *UserIdentityUpdate {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableClaimsEmailVerified(v *bool) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *UserIdentityUpdate) SetClaimsGroups(v []string) *UserIdentityUpdate {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *UserIdentityUpdate) AppendClaimsGroups(v []string) *UserIdentityUpdate {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *UserIdentityUpdate) ClearClaimsGroups() *UserIdentityUpdate {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetConsents sets the \"consents\" field.\nfunc (_u *UserIdentityUpdate) SetConsents(v []byte) *UserIdentityUpdate {\n\t_u.mutation.SetConsents(v)\n\treturn _u\n}\n\n// SetMfaSecrets sets the \"mfa_secrets\" field.\nfunc (_u *UserIdentityUpdate) SetMfaSecrets(v []byte) *UserIdentityUpdate {\n\t_u.mutation.SetMfaSecrets(v)\n\treturn _u\n}\n\n// ClearMfaSecrets clears the value of the \"mfa_secrets\" field.\nfunc (_u *UserIdentityUpdate) ClearMfaSecrets() *UserIdentityUpdate {\n\t_u.mutation.ClearMfaSecrets()\n\treturn _u\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_u *UserIdentityUpdate) SetCreatedAt(v time.Time) *UserIdentityUpdate {\n\t_u.mutation.SetCreatedAt(v)\n\treturn _u\n}\n\n// SetNillableCreatedAt sets the \"created_at\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableCreatedAt(v *time.Time) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetCreatedAt(*v)\n\t}\n\treturn _u\n}\n\n// SetLastLogin sets the \"last_login\" field.\nfunc (_u *UserIdentityUpdate) SetLastLogin(v time.Time) *UserIdentityUpdate {\n\t_u.mutation.SetLastLogin(v)\n\treturn _u\n}\n\n// SetNillableLastLogin sets the \"last_login\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableLastLogin(v *time.Time) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetLastLogin(*v)\n\t}\n\treturn _u\n}\n\n// SetBlockedUntil sets the \"blocked_until\" field.\nfunc (_u *UserIdentityUpdate) SetBlockedUntil(v time.Time) *UserIdentityUpdate {\n\t_u.mutation.SetBlockedUntil(v)\n\treturn _u\n}\n\n// SetNillableBlockedUntil sets the \"blocked_until\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdate) SetNillableBlockedUntil(v *time.Time) *UserIdentityUpdate {\n\tif v != nil {\n\t\t_u.SetBlockedUntil(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the UserIdentityMutation object of the builder.\nfunc (_u *UserIdentityUpdate) Mutation() *UserIdentityMutation {\n\treturn _u.mutation\n}\n\n// Save executes the query and returns the number of nodes affected by the update operation.\nfunc (_u *UserIdentityUpdate) Save(ctx context.Context) (int, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *UserIdentityUpdate) SaveX(ctx context.Context) int {\n\taffected, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn affected\n}\n\n// Exec executes the query.\nfunc (_u *UserIdentityUpdate) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *UserIdentityUpdate) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *UserIdentityUpdate) check() error {\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := useridentity.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"UserIdentity.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := useridentity.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"UserIdentity.connector_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *UserIdentityUpdate) sqlSave(ctx context.Context) (_node int, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(useridentity.Table, useridentity.Columns, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString))\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(useridentity.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(useridentity.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, useridentity.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(useridentity.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Consents(); ok {\n\t\t_spec.SetField(useridentity.FieldConsents, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.MfaSecrets(); ok {\n\t\t_spec.SetField(useridentity.FieldMfaSecrets, field.TypeBytes, value)\n\t}\n\tif _u.mutation.MfaSecretsCleared() {\n\t\t_spec.ClearField(useridentity.FieldMfaSecrets, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(useridentity.FieldCreatedAt, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastLogin(); ok {\n\t\t_spec.SetField(useridentity.FieldLastLogin, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.BlockedUntil(); ok {\n\t\t_spec.SetField(useridentity.FieldBlockedUntil, field.TypeTime, value)\n\t}\n\tif _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{useridentity.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn 0, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n\n// UserIdentityUpdateOne is the builder for updating a single UserIdentity entity.\ntype UserIdentityUpdateOne struct {\n\tconfig\n\tfields   []string\n\thooks    []Hook\n\tmutation *UserIdentityMutation\n}\n\n// SetUserID sets the \"user_id\" field.\nfunc (_u *UserIdentityUpdateOne) SetUserID(v string) *UserIdentityUpdateOne {\n\t_u.mutation.SetUserID(v)\n\treturn _u\n}\n\n// SetNillableUserID sets the \"user_id\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableUserID(v *string) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetConnectorID sets the \"connector_id\" field.\nfunc (_u *UserIdentityUpdateOne) SetConnectorID(v string) *UserIdentityUpdateOne {\n\t_u.mutation.SetConnectorID(v)\n\treturn _u\n}\n\n// SetNillableConnectorID sets the \"connector_id\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableConnectorID(v *string) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetConnectorID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUserID sets the \"claims_user_id\" field.\nfunc (_u *UserIdentityUpdateOne) SetClaimsUserID(v string) *UserIdentityUpdateOne {\n\t_u.mutation.SetClaimsUserID(v)\n\treturn _u\n}\n\n// SetNillableClaimsUserID sets the \"claims_user_id\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableClaimsUserID(v *string) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUserID(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsUsername sets the \"claims_username\" field.\nfunc (_u *UserIdentityUpdateOne) SetClaimsUsername(v string) *UserIdentityUpdateOne {\n\t_u.mutation.SetClaimsUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsUsername sets the \"claims_username\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableClaimsUsername(v *string) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsPreferredUsername sets the \"claims_preferred_username\" field.\nfunc (_u *UserIdentityUpdateOne) SetClaimsPreferredUsername(v string) *UserIdentityUpdateOne {\n\t_u.mutation.SetClaimsPreferredUsername(v)\n\treturn _u\n}\n\n// SetNillableClaimsPreferredUsername sets the \"claims_preferred_username\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableClaimsPreferredUsername(v *string) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsPreferredUsername(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmail sets the \"claims_email\" field.\nfunc (_u *UserIdentityUpdateOne) SetClaimsEmail(v string) *UserIdentityUpdateOne {\n\t_u.mutation.SetClaimsEmail(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmail sets the \"claims_email\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableClaimsEmail(v *string) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmail(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsEmailVerified sets the \"claims_email_verified\" field.\nfunc (_u *UserIdentityUpdateOne) SetClaimsEmailVerified(v bool) *UserIdentityUpdateOne {\n\t_u.mutation.SetClaimsEmailVerified(v)\n\treturn _u\n}\n\n// SetNillableClaimsEmailVerified sets the \"claims_email_verified\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableClaimsEmailVerified(v *bool) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetClaimsEmailVerified(*v)\n\t}\n\treturn _u\n}\n\n// SetClaimsGroups sets the \"claims_groups\" field.\nfunc (_u *UserIdentityUpdateOne) SetClaimsGroups(v []string) *UserIdentityUpdateOne {\n\t_u.mutation.SetClaimsGroups(v)\n\treturn _u\n}\n\n// AppendClaimsGroups appends value to the \"claims_groups\" field.\nfunc (_u *UserIdentityUpdateOne) AppendClaimsGroups(v []string) *UserIdentityUpdateOne {\n\t_u.mutation.AppendClaimsGroups(v)\n\treturn _u\n}\n\n// ClearClaimsGroups clears the value of the \"claims_groups\" field.\nfunc (_u *UserIdentityUpdateOne) ClearClaimsGroups() *UserIdentityUpdateOne {\n\t_u.mutation.ClearClaimsGroups()\n\treturn _u\n}\n\n// SetConsents sets the \"consents\" field.\nfunc (_u *UserIdentityUpdateOne) SetConsents(v []byte) *UserIdentityUpdateOne {\n\t_u.mutation.SetConsents(v)\n\treturn _u\n}\n\n// SetMfaSecrets sets the \"mfa_secrets\" field.\nfunc (_u *UserIdentityUpdateOne) SetMfaSecrets(v []byte) *UserIdentityUpdateOne {\n\t_u.mutation.SetMfaSecrets(v)\n\treturn _u\n}\n\n// ClearMfaSecrets clears the value of the \"mfa_secrets\" field.\nfunc (_u *UserIdentityUpdateOne) ClearMfaSecrets() *UserIdentityUpdateOne {\n\t_u.mutation.ClearMfaSecrets()\n\treturn _u\n}\n\n// SetCreatedAt sets the \"created_at\" field.\nfunc (_u *UserIdentityUpdateOne) SetCreatedAt(v time.Time) *UserIdentityUpdateOne {\n\t_u.mutation.SetCreatedAt(v)\n\treturn _u\n}\n\n// SetNillableCreatedAt sets the \"created_at\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableCreatedAt(v *time.Time) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetCreatedAt(*v)\n\t}\n\treturn _u\n}\n\n// SetLastLogin sets the \"last_login\" field.\nfunc (_u *UserIdentityUpdateOne) SetLastLogin(v time.Time) *UserIdentityUpdateOne {\n\t_u.mutation.SetLastLogin(v)\n\treturn _u\n}\n\n// SetNillableLastLogin sets the \"last_login\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableLastLogin(v *time.Time) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetLastLogin(*v)\n\t}\n\treturn _u\n}\n\n// SetBlockedUntil sets the \"blocked_until\" field.\nfunc (_u *UserIdentityUpdateOne) SetBlockedUntil(v time.Time) *UserIdentityUpdateOne {\n\t_u.mutation.SetBlockedUntil(v)\n\treturn _u\n}\n\n// SetNillableBlockedUntil sets the \"blocked_until\" field if the given value is not nil.\nfunc (_u *UserIdentityUpdateOne) SetNillableBlockedUntil(v *time.Time) *UserIdentityUpdateOne {\n\tif v != nil {\n\t\t_u.SetBlockedUntil(*v)\n\t}\n\treturn _u\n}\n\n// Mutation returns the UserIdentityMutation object of the builder.\nfunc (_u *UserIdentityUpdateOne) Mutation() *UserIdentityMutation {\n\treturn _u.mutation\n}\n\n// Where appends a list predicates to the UserIdentityUpdate builder.\nfunc (_u *UserIdentityUpdateOne) Where(ps ...predicate.UserIdentity) *UserIdentityUpdateOne {\n\t_u.mutation.Where(ps...)\n\treturn _u\n}\n\n// Select allows selecting one or more fields (columns) of the returned entity.\n// The default is selecting all fields defined in the entity schema.\nfunc (_u *UserIdentityUpdateOne) Select(field string, fields ...string) *UserIdentityUpdateOne {\n\t_u.fields = append([]string{field}, fields...)\n\treturn _u\n}\n\n// Save executes the query and returns the updated UserIdentity entity.\nfunc (_u *UserIdentityUpdateOne) Save(ctx context.Context) (*UserIdentity, error) {\n\treturn withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks)\n}\n\n// SaveX is like Save, but panics if an error occurs.\nfunc (_u *UserIdentityUpdateOne) SaveX(ctx context.Context) *UserIdentity {\n\tnode, err := _u.Save(ctx)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn node\n}\n\n// Exec executes the query on the entity.\nfunc (_u *UserIdentityUpdateOne) Exec(ctx context.Context) error {\n\t_, err := _u.Save(ctx)\n\treturn err\n}\n\n// ExecX is like Exec, but panics if an error occurs.\nfunc (_u *UserIdentityUpdateOne) ExecX(ctx context.Context) {\n\tif err := _u.Exec(ctx); err != nil {\n\t\tpanic(err)\n\t}\n}\n\n// check runs all checks and user-defined validators on the builder.\nfunc (_u *UserIdentityUpdateOne) check() error {\n\tif v, ok := _u.mutation.UserID(); ok {\n\t\tif err := useridentity.UserIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"user_id\", err: fmt.Errorf(`db: validator failed for field \"UserIdentity.user_id\": %w`, err)}\n\t\t}\n\t}\n\tif v, ok := _u.mutation.ConnectorID(); ok {\n\t\tif err := useridentity.ConnectorIDValidator(v); err != nil {\n\t\t\treturn &ValidationError{Name: \"connector_id\", err: fmt.Errorf(`db: validator failed for field \"UserIdentity.connector_id\": %w`, err)}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (_u *UserIdentityUpdateOne) sqlSave(ctx context.Context) (_node *UserIdentity, err error) {\n\tif err := _u.check(); err != nil {\n\t\treturn _node, err\n\t}\n\t_spec := sqlgraph.NewUpdateSpec(useridentity.Table, useridentity.Columns, sqlgraph.NewFieldSpec(useridentity.FieldID, field.TypeString))\n\tid, ok := _u.mutation.ID()\n\tif !ok {\n\t\treturn nil, &ValidationError{Name: \"id\", err: errors.New(`db: missing \"UserIdentity.id\" for update`)}\n\t}\n\t_spec.Node.ID.Value = id\n\tif fields := _u.fields; len(fields) > 0 {\n\t\t_spec.Node.Columns = make([]string, 0, len(fields))\n\t\t_spec.Node.Columns = append(_spec.Node.Columns, useridentity.FieldID)\n\t\tfor _, f := range fields {\n\t\t\tif !useridentity.ValidColumn(f) {\n\t\t\t\treturn nil, &ValidationError{Name: f, err: fmt.Errorf(\"db: invalid field %q for query\", f)}\n\t\t\t}\n\t\t\tif f != useridentity.FieldID {\n\t\t\t\t_spec.Node.Columns = append(_spec.Node.Columns, f)\n\t\t\t}\n\t\t}\n\t}\n\tif ps := _u.mutation.predicates; len(ps) > 0 {\n\t\t_spec.Predicate = func(selector *sql.Selector) {\n\t\t\tfor i := range ps {\n\t\t\t\tps[i](selector)\n\t\t\t}\n\t\t}\n\t}\n\tif value, ok := _u.mutation.UserID(); ok {\n\t\t_spec.SetField(useridentity.FieldUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ConnectorID(); ok {\n\t\t_spec.SetField(useridentity.FieldConnectorID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUserID(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsUserID, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsUsername(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsPreferredUsername(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsPreferredUsername, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmail(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsEmail, field.TypeString, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsEmailVerified(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsEmailVerified, field.TypeBool, value)\n\t}\n\tif value, ok := _u.mutation.ClaimsGroups(); ok {\n\t\t_spec.SetField(useridentity.FieldClaimsGroups, field.TypeJSON, value)\n\t}\n\tif value, ok := _u.mutation.AppendedClaimsGroups(); ok {\n\t\t_spec.AddModifier(func(u *sql.UpdateBuilder) {\n\t\t\tsqljson.Append(u, useridentity.FieldClaimsGroups, value)\n\t\t})\n\t}\n\tif _u.mutation.ClaimsGroupsCleared() {\n\t\t_spec.ClearField(useridentity.FieldClaimsGroups, field.TypeJSON)\n\t}\n\tif value, ok := _u.mutation.Consents(); ok {\n\t\t_spec.SetField(useridentity.FieldConsents, field.TypeBytes, value)\n\t}\n\tif value, ok := _u.mutation.MfaSecrets(); ok {\n\t\t_spec.SetField(useridentity.FieldMfaSecrets, field.TypeBytes, value)\n\t}\n\tif _u.mutation.MfaSecretsCleared() {\n\t\t_spec.ClearField(useridentity.FieldMfaSecrets, field.TypeBytes)\n\t}\n\tif value, ok := _u.mutation.CreatedAt(); ok {\n\t\t_spec.SetField(useridentity.FieldCreatedAt, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.LastLogin(); ok {\n\t\t_spec.SetField(useridentity.FieldLastLogin, field.TypeTime, value)\n\t}\n\tif value, ok := _u.mutation.BlockedUntil(); ok {\n\t\t_spec.SetField(useridentity.FieldBlockedUntil, field.TypeTime, value)\n\t}\n\t_node = &UserIdentity{config: _u.config}\n\t_spec.Assign = _node.assignValues\n\t_spec.ScanValues = _node.scanValues\n\tif err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil {\n\t\tif _, ok := err.(*sqlgraph.NotFoundError); ok {\n\t\t\terr = &NotFoundError{useridentity.Label}\n\t\t} else if sqlgraph.IsConstraintError(err) {\n\t\t\terr = &ConstraintError{msg: err.Error(), wrap: err}\n\t\t}\n\t\treturn nil, err\n\t}\n\t_u.mutation.done = true\n\treturn _node, nil\n}\n"
  },
  {
    "path": "storage/ent/generate.go",
    "content": "package ent\n\n//go:generate go tool entgo.io/ent/cmd/ent generate ./schema --target ./db\n"
  },
  {
    "path": "storage/ent/mysql.go",
    "content": "package ent\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"os\"\n\t\"strconv\"\n\t\"time\"\n\n\tentSQL \"entgo.io/ent/dialect/sql\"\n\t\"github.com/go-sql-driver/mysql\" // Register mysql driver.\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/client\"\n\t\"github.com/dexidp/dex/storage/ent/db\"\n)\n\nconst (\n\t// MySQL SSL modes\n\tmysqlSSLTrue       = \"true\"\n\tmysqlSSLFalse      = \"false\"\n\tmysqlSSLSkipVerify = \"skip-verify\"\n\tmysqlSSLCustom     = \"custom\"\n)\n\n// MySQL options for creating an SQL db.\ntype MySQL struct {\n\tNetworkDB\n\n\tSSL SSL `json:\"ssl\"`\n\n\tparams map[string]string\n}\n\n// Open always returns a new in sqlite3 storage.\nfunc (m *MySQL) Open(logger *slog.Logger) (storage.Storage, error) {\n\tlogger.Debug(\"experimental ent-based storage driver is enabled\")\n\tdrv, err := m.driver()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdatabaseClient := client.NewDatabase(\n\t\tclient.WithClient(db.NewClient(db.Driver(drv))),\n\t\tclient.WithHasher(sha256.New),\n\t\t// Set tx isolation leve for each transaction as dex does for postgres\n\t\tclient.WithTxIsolationLevel(sql.LevelSerializable),\n\t)\n\n\tif err := databaseClient.Schema().Create(context.TODO()); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn databaseClient, nil\n}\n\nfunc (m *MySQL) driver() (*entSQL.Driver, error) {\n\tvar tlsConfig string\n\n\tswitch {\n\tcase m.SSL.CAFile != \"\" || m.SSL.CertFile != \"\" || m.SSL.KeyFile != \"\":\n\t\tif err := m.makeTLSConfig(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to make TLS config: %v\", err)\n\t\t}\n\t\ttlsConfig = mysqlSSLCustom\n\tcase m.SSL.Mode == \"\":\n\t\ttlsConfig = mysqlSSLTrue\n\tdefault:\n\t\ttlsConfig = m.SSL.Mode\n\t}\n\n\tdrv, err := entSQL.Open(\"mysql\", m.dsn(tlsConfig))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif m.MaxIdleConns == 0 {\n\t\t/* Override default behavior to fix https://github.com/dexidp/dex/issues/1608 */\n\t\tdrv.DB().SetMaxIdleConns(0)\n\t} else {\n\t\tdrv.DB().SetMaxIdleConns(m.MaxIdleConns)\n\t}\n\n\treturn drv, nil\n}\n\nfunc (m *MySQL) dsn(tlsConfig string) string {\n\tcfg := mysql.Config{\n\t\tUser:                 m.User,\n\t\tPasswd:               m.Password,\n\t\tDBName:               m.Database,\n\t\tAllowNativePasswords: true,\n\n\t\tTimeout: time.Second * time.Duration(m.ConnectionTimeout),\n\n\t\tTLSConfig: tlsConfig,\n\n\t\tParseTime: true,\n\t\tParams:    make(map[string]string),\n\t}\n\n\tif m.Host != \"\" {\n\t\tif m.Host[0] != '/' {\n\t\t\tcfg.Net = \"tcp\"\n\t\t\tcfg.Addr = m.Host\n\n\t\t\tif m.Port != 0 {\n\t\t\t\tcfg.Addr = net.JoinHostPort(m.Host, strconv.Itoa(int(m.Port)))\n\t\t\t}\n\t\t} else {\n\t\t\tcfg.Net = \"unix\"\n\t\t\tcfg.Addr = m.Host\n\t\t}\n\t}\n\n\tfor k, v := range m.params {\n\t\tcfg.Params[k] = v\n\t}\n\n\treturn cfg.FormatDSN()\n}\n\nfunc (m *MySQL) makeTLSConfig() error {\n\tcfg := &tls.Config{}\n\n\tif m.SSL.CAFile != \"\" {\n\t\trootCertPool := x509.NewCertPool()\n\n\t\tpem, err := os.ReadFile(m.SSL.CAFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif ok := rootCertPool.AppendCertsFromPEM(pem); !ok {\n\t\t\treturn fmt.Errorf(\"failed to append PEM\")\n\t\t}\n\t\tcfg.RootCAs = rootCertPool\n\t}\n\n\tif m.SSL.CertFile != \"\" && m.SSL.KeyFile != \"\" {\n\t\tclientCert := make([]tls.Certificate, 0, 1)\n\t\tcerts, err := tls.LoadX509KeyPair(m.SSL.CertFile, m.SSL.KeyFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tclientCert = append(clientCert, certs)\n\t\tcfg.Certificates = clientCert\n\t}\n\n\tmysql.RegisterTLSConfig(mysqlSSLCustom, cfg)\n\treturn nil\n}\n"
  },
  {
    "path": "storage/ent/mysql_test.go",
    "content": "package ent\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/conformance\"\n)\n\nconst (\n\tMySQLEntHostEnv     = \"DEX_MYSQL_ENT_HOST\"\n\tMySQLEntPortEnv     = \"DEX_MYSQL_ENT_PORT\"\n\tMySQLEntDatabaseEnv = \"DEX_MYSQL_ENT_DATABASE\"\n\tMySQLEntUserEnv     = \"DEX_MYSQL_ENT_USER\"\n\tMySQLEntPasswordEnv = \"DEX_MYSQL_ENT_PASSWORD\"\n\n\tMySQL8EntHostEnv     = \"DEX_MYSQL8_ENT_HOST\"\n\tMySQL8EntPortEnv     = \"DEX_MYSQL8_ENT_PORT\"\n\tMySQL8EntDatabaseEnv = \"DEX_MYSQL8_ENT_DATABASE\"\n\tMySQL8EntUserEnv     = \"DEX_MYSQL8_ENT_USER\"\n\tMySQL8EntPasswordEnv = \"DEX_MYSQL8_ENT_PASSWORD\"\n)\n\nfunc mysqlTestConfig(host string, port uint64) *MySQL {\n\treturn &MySQL{\n\t\tNetworkDB: NetworkDB{\n\t\t\tDatabase: getenv(MySQLEntDatabaseEnv, \"mysql\"),\n\t\t\tUser:     getenv(MySQLEntUserEnv, \"mysql\"),\n\t\t\tPassword: getenv(MySQLEntPasswordEnv, \"mysql\"),\n\t\t\tHost:     host,\n\t\t\tPort:     uint16(port),\n\t\t},\n\t\tSSL: SSL{\n\t\t\t// This was originally mysqlSSLSkipVerify. It lead to handshake errors.\n\t\t\t// See https://github.com/go-sql-driver/mysql/issues/1635 for more details.\n\t\t\tMode: mysqlSSLFalse,\n\t\t},\n\t\tparams: map[string]string{\n\t\t\t\"innodb_lock_wait_timeout\": \"1\",\n\t\t},\n\t}\n}\n\nfunc mysql8TestConfig(host string, port uint64) *MySQL {\n\treturn &MySQL{\n\t\tNetworkDB: NetworkDB{\n\t\t\tDatabase: getenv(MySQL8EntDatabaseEnv, \"mysql\"),\n\t\t\tUser:     getenv(MySQL8EntUserEnv, \"mysql\"),\n\t\t\tPassword: getenv(MySQL8EntPasswordEnv, \"mysql\"),\n\t\t\tHost:     host,\n\t\t\tPort:     uint16(port),\n\t\t},\n\t\tSSL: SSL{\n\t\t\tMode: mysqlSSLFalse,\n\t\t},\n\t\tparams: map[string]string{\n\t\t\t\"innodb_lock_wait_timeout\": \"1\",\n\t\t},\n\t}\n}\n\nfunc newMySQL8Storage(t *testing.T, host string, port uint64) storage.Storage {\n\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\n\tcfg := mysql8TestConfig(host, port)\n\ts, err := cfg.Open(logger)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s\n}\n\nfunc newMySQLStorage(t *testing.T, host string, port uint64) storage.Storage {\n\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\n\tcfg := mysqlTestConfig(host, port)\n\ts, err := cfg.Open(logger)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s\n}\n\nfunc TestMySQL(t *testing.T) {\n\thost := os.Getenv(MySQLEntHostEnv)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %s not set, skipping\", MySQLEntHostEnv)\n\t}\n\n\tport := uint64(3306)\n\tif rawPort := os.Getenv(MySQLEntPortEnv); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\trequire.NoError(t, err, \"invalid mysql port %q: %s\", rawPort, err)\n\t}\n\n\tnewStorage := func(t *testing.T) storage.Storage {\n\t\treturn newMySQLStorage(t, host, port)\n\t}\n\tconformance.RunTests(t, newStorage)\n\tconformance.RunTransactionTests(t, newStorage)\n\n\t// TODO(nabokihms): ent MySQL does not retry on deadlocks (Error 1213, SQLSTATE 40001:\n\t// Deadlock found when trying to get lock; try restarting transaction).\n\t// Under high contention most updates fail.\n\t// conformance.RunConcurrencyTests(t, newStorage)\n}\n\nfunc TestMySQL8(t *testing.T) {\n\thost := os.Getenv(MySQL8EntHostEnv)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %s not set, skipping\", MySQL8EntHostEnv)\n\t}\n\n\tport := uint64(3306)\n\tif rawPort := os.Getenv(MySQL8EntPortEnv); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\trequire.NoError(t, err, \"invalid mysql port %q: %s\", rawPort, err)\n\t}\n\n\tnewStorage := func(t *testing.T) storage.Storage {\n\t\treturn newMySQL8Storage(t, host, port)\n\t}\n\tconformance.RunTests(t, newStorage)\n\tconformance.RunTransactionTests(t, newStorage)\n\n\t// TODO(nabokihms): ent MySQL 8 does not retry on deadlocks (Error 1213, SQLSTATE 40001:\n\t// Deadlock found when trying to get lock; try restarting transaction).\n\t// Under high contention most updates fail.\n\t// conformance.RunConcurrencyTests(t, newStorage)\n}\n\nfunc TestMySQLDSN(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tcfg        *MySQL\n\t\tdesiredDSN string\n\t}{\n\t\t{\n\t\t\tname: \"Host port\",\n\t\t\tcfg: &MySQL{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"localhost\",\n\t\t\t\t\tPort: uint16(3306),\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"tcp(localhost:3306)/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Host with port\",\n\t\t\tcfg: &MySQL{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"localhost:3306\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"tcp(localhost:3306)/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Host ipv6 with port\",\n\t\t\tcfg: &MySQL{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"[a:b:c:d]:3306\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"tcp([a:b:c:d]:3306)/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0\",\n\t\t},\n\t\t{\n\t\t\tname: \"Credentials and timeout\",\n\t\t\tcfg: &MySQL{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tDatabase:          \"test\",\n\t\t\t\t\tUser:              \"test\",\n\t\t\t\t\tPassword:          \"test\",\n\t\t\t\t\tConnectionTimeout: 5,\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"test:test@/test?checkConnLiveness=false&parseTime=true&timeout=5s&tls=false&maxAllowedPacket=0\",\n\t\t},\n\t\t{\n\t\t\tname: \"SSL\",\n\t\t\tcfg: &MySQL{\n\t\t\t\tSSL: SSL{\n\t\t\t\t\tCAFile:   \"/ca.crt\",\n\t\t\t\t\tKeyFile:  \"/cert.crt\",\n\t\t\t\t\tCertFile: \"/cert.key\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0\",\n\t\t},\n\t\t{\n\t\t\tname: \"With Params\",\n\t\t\tcfg: &MySQL{\n\t\t\t\tparams: map[string]string{\n\t\t\t\t\t\"innodb_lock_wait_timeout\": \"1\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"/?checkConnLiveness=false&parseTime=true&tls=false&maxAllowedPacket=0&innodb_lock_wait_timeout=1\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.desiredDSN, tt.cfg.dsn(mysqlSSLFalse))\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "storage/ent/postgres.go",
    "content": "package ent\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tentSQL \"entgo.io/ent/dialect/sql\"\n\t_ \"github.com/lib/pq\" // Register postgres driver.\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/client\"\n\t\"github.com/dexidp/dex/storage/ent/db\"\n)\n\nconst (\n\t// postgres SSL modes\n\tpgSSLDisable    = \"disable\"\n\tpgSSLRequire    = \"require\"\n\tpgSSLVerifyCA   = \"verify-ca\"\n\tpgSSLVerifyFull = \"verify-full\"\n)\n\n// Postgres options for creating an SQL db.\ntype Postgres struct {\n\tNetworkDB\n\n\tSSL SSL `json:\"ssl\"`\n}\n\n// Open always returns a new in sqlite3 storage.\nfunc (p *Postgres) Open(logger *slog.Logger) (storage.Storage, error) {\n\tlogger.Debug(\"experimental ent-based storage driver is enabled\")\n\tdrv, err := p.driver()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdatabaseClient := client.NewDatabase(\n\t\tclient.WithClient(db.NewClient(db.Driver(drv))),\n\t\tclient.WithHasher(sha256.New),\n\t\t// The default behavior for Postgres transactions is consistent reads, not consistent writes.\n\t\t// For each transaction opened, ensure it has the correct isolation level.\n\t\t//\n\t\t// See: https://www.postgresql.org/docs/9.3/static/sql-set-transaction.html\n\t\tclient.WithTxIsolationLevel(sql.LevelSerializable),\n\t)\n\n\tif err := databaseClient.Schema().Create(context.TODO()); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn databaseClient, nil\n}\n\nfunc (p *Postgres) driver() (*entSQL.Driver, error) {\n\tdrv, err := entSQL.Open(\"postgres\", p.dsn())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// set database/sql tunables if configured\n\tif p.ConnMaxLifetime != 0 {\n\t\tdrv.DB().SetConnMaxLifetime(time.Duration(p.ConnMaxLifetime) * time.Second)\n\t}\n\n\tif p.MaxIdleConns == 0 {\n\t\tdrv.DB().SetMaxIdleConns(5)\n\t} else {\n\t\tdrv.DB().SetMaxIdleConns(p.MaxIdleConns)\n\t}\n\n\tif p.MaxOpenConns == 0 {\n\t\tdrv.DB().SetMaxOpenConns(5)\n\t} else {\n\t\tdrv.DB().SetMaxOpenConns(p.MaxOpenConns)\n\t}\n\n\treturn drv, nil\n}\n\nfunc (p *Postgres) dsn() string {\n\t// detect host:port for backwards-compatibility\n\thost, port, err := net.SplitHostPort(p.Host)\n\tif err != nil {\n\t\t// not host:port, probably unix socket or bare address\n\t\thost = p.Host\n\t\tif p.Port != 0 {\n\t\t\tport = strconv.Itoa(int(p.Port))\n\t\t}\n\t}\n\n\tvar parameters []string\n\taddParam := func(key, val string) {\n\t\tparameters = append(parameters, fmt.Sprintf(\"%s=%s\", key, val))\n\t}\n\n\taddParam(\"connect_timeout\", strconv.Itoa(p.ConnectionTimeout))\n\n\tif host != \"\" {\n\t\taddParam(\"host\", dataSourceStr(host))\n\t}\n\n\tif port != \"\" {\n\t\taddParam(\"port\", port)\n\t}\n\n\tif p.User != \"\" {\n\t\taddParam(\"user\", dataSourceStr(p.User))\n\t}\n\n\tif p.Password != \"\" {\n\t\taddParam(\"password\", dataSourceStr(p.Password))\n\t}\n\n\tif p.Database != \"\" {\n\t\taddParam(\"dbname\", dataSourceStr(p.Database))\n\t}\n\n\tif p.SSL.Mode == \"\" {\n\t\t// Assume the strictest mode if unspecified.\n\t\taddParam(\"sslmode\", dataSourceStr(pgSSLVerifyFull))\n\t} else {\n\t\taddParam(\"sslmode\", dataSourceStr(p.SSL.Mode))\n\t}\n\n\tif p.SSL.CAFile != \"\" {\n\t\taddParam(\"sslrootcert\", dataSourceStr(p.SSL.CAFile))\n\t}\n\n\tif p.SSL.CertFile != \"\" {\n\t\taddParam(\"sslcert\", dataSourceStr(p.SSL.CertFile))\n\t}\n\n\tif p.SSL.KeyFile != \"\" {\n\t\taddParam(\"sslkey\", dataSourceStr(p.SSL.KeyFile))\n\t}\n\n\treturn strings.Join(parameters, \" \")\n}\n\nvar strEsc = regexp.MustCompile(`([\\\\'])`)\n\nfunc dataSourceStr(str string) string {\n\treturn \"'\" + strEsc.ReplaceAllString(str, `\\$1`) + \"'\"\n}\n"
  },
  {
    "path": "storage/ent/postgres_test.go",
    "content": "package ent\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/conformance\"\n)\n\nconst (\n\tPostgresEntHostEnv     = \"DEX_POSTGRES_ENT_HOST\"\n\tPostgresEntPortEnv     = \"DEX_POSTGRES_ENT_PORT\"\n\tPostgresEntDatabaseEnv = \"DEX_POSTGRES_ENT_DATABASE\"\n\tPostgresEntUserEnv     = \"DEX_POSTGRES_ENT_USER\"\n\tPostgresEntPasswordEnv = \"DEX_POSTGRES_ENT_PASSWORD\"\n)\n\nfunc postgresTestConfig(host string, port uint64) *Postgres {\n\treturn &Postgres{\n\t\tNetworkDB: NetworkDB{\n\t\t\tDatabase: getenv(PostgresEntDatabaseEnv, \"postgres\"),\n\t\t\tUser:     getenv(PostgresEntUserEnv, \"postgres\"),\n\t\t\tPassword: getenv(PostgresEntPasswordEnv, \"postgres\"),\n\t\t\tHost:     host,\n\t\t\tPort:     uint16(port),\n\t\t},\n\t\tSSL: SSL{\n\t\t\tMode: pgSSLDisable, // Postgres container doesn't support SSL.\n\t\t},\n\t}\n}\n\nfunc newPostgresStorage(t *testing.T, host string, port uint64) storage.Storage {\n\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\n\tcfg := postgresTestConfig(host, port)\n\ts, err := cfg.Open(logger)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s\n}\n\nfunc TestPostgres(t *testing.T) {\n\thost := os.Getenv(PostgresEntHostEnv)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %s not set, skipping\", PostgresEntHostEnv)\n\t}\n\n\tport := uint64(5432)\n\tif rawPort := os.Getenv(PostgresEntPortEnv); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\trequire.NoError(t, err, \"invalid postgres port %q: %s\", rawPort, err)\n\t}\n\n\tnewStorage := func(t *testing.T) storage.Storage {\n\t\treturn newPostgresStorage(t, host, port)\n\t}\n\tconformance.RunTests(t, newStorage)\n\tconformance.RunTransactionTests(t, newStorage)\n\n\t// TODO(nabokihms): ent Postgres uses SERIALIZABLE transaction isolation for UpdateRefreshToken,\n\t// but does not retry on serialization failures (pq: could not serialize access due to\n\t// concurrent update, SQLSTATE 40001). Under high contention most updates fail immediately.\n\t// conformance.RunConcurrencyTests(t, newStorage)\n}\n\nfunc TestPostgresDSN(t *testing.T) {\n\ttests := []struct {\n\t\tname       string\n\t\tcfg        *Postgres\n\t\tdesiredDSN string\n\t}{\n\t\t{\n\t\t\tname: \"Host port\",\n\t\t\tcfg: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"localhost\",\n\t\t\t\t\tPort: uint16(5432),\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"connect_timeout=0 host='localhost' port=5432 sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tname: \"Host with port\",\n\t\t\tcfg: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"localhost:5432\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"connect_timeout=0 host='localhost' port=5432 sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tname: \"Host ipv6 with port\",\n\t\t\tcfg: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"[a:b:c:d]:5432\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"connect_timeout=0 host='a:b:c:d' port=5432 sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tname: \"Credentials and timeout\",\n\t\t\tcfg: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tDatabase:          \"test\",\n\t\t\t\t\tUser:              \"test\",\n\t\t\t\t\tPassword:          \"test\",\n\t\t\t\t\tConnectionTimeout: 5,\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"connect_timeout=5 user='test' password='test' dbname='test' sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tname: \"SSL\",\n\t\t\tcfg: &Postgres{\n\t\t\t\tSSL: SSL{\n\t\t\t\t\tMode:     pgSSLRequire,\n\t\t\t\t\tCAFile:   \"/ca.crt\",\n\t\t\t\t\tKeyFile:  \"/cert.crt\",\n\t\t\t\t\tCertFile: \"/cert.key\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tdesiredDSN: \"connect_timeout=0 sslmode='require' sslrootcert='/ca.crt' sslcert='/cert.key' sslkey='/cert.crt'\",\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.desiredDSN, tt.cfg.dsn())\n\t\t})\n\t}\n}\n\nfunc TestPostgresDriver(t *testing.T) {\n\thost := os.Getenv(PostgresEntHostEnv)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %s not set, skipping\", PostgresEntHostEnv)\n\t}\n\n\tport := uint64(5432)\n\tif rawPort := os.Getenv(PostgresEntPortEnv); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\trequire.NoError(t, err, \"invalid postgres port %q: %s\", rawPort, err)\n\t}\n\n\ttests := []struct {\n\t\tname         string\n\t\tcfg          func() *Postgres\n\t\tdesiredConns int\n\t}{\n\t\t{\n\t\t\tname:         \"Defaults\",\n\t\t\tcfg:          func() *Postgres { return postgresTestConfig(host, port) },\n\t\t\tdesiredConns: 5,\n\t\t},\n\t\t{\n\t\t\tname: \"Tune\",\n\t\t\tcfg: func() *Postgres {\n\t\t\t\tcfg := postgresTestConfig(host, port)\n\t\t\t\tcfg.MaxOpenConns = 101\n\t\t\t\treturn cfg\n\t\t\t},\n\t\t\tdesiredConns: 101,\n\t\t},\n\t}\n\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tdrv, err := tt.cfg().driver()\n\t\t\trequire.NoError(t, err)\n\n\t\t\trequire.Equal(t, tt.desiredConns, drv.DB().Stats().MaxOpenConnections)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "storage/ent/schema/authcode.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table auth_code\n(\n    id                        text      not null  primary key,\n    client_id                 text      not null,\n    scopes                    blob      not null,\n    nonce                     text      not null,\n    redirect_uri              text      not null,\n    claims_user_id            text      not null,\n    claims_username           text      not null,\n    claims_email              text      not null,\n    claims_email_verified     integer   not null,\n    claims_groups             blob      not null,\n    connector_id              text      not null,\n    connector_data            blob,\n    expiry                    timestamp not null,\n    claims_preferred_username text default '' not null,\n    code_challenge            text default '' not null,\n    code_challenge_method     text default '' not null\n);\n*/\n\n// AuthCode holds the schema definition for the AuthCode entity.\ntype AuthCode struct {\n\tent.Schema\n}\n\n// Fields of the AuthCode.\nfunc (AuthCode) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"client_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.JSON(\"scopes\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Text(\"nonce\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"redirect_uri\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\n\t\tfield.Text(\"claims_user_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"claims_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"claims_email\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Bool(\"claims_email_verified\"),\n\t\tfield.JSON(\"claims_groups\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Text(\"claims_preferred_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\n\t\tfield.Text(\"connector_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Bytes(\"connector_data\").\n\t\t\tNillable().\n\t\t\tOptional(),\n\t\tfield.Time(\"expiry\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Text(\"code_challenge\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"code_challenge_method\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Time(\"auth_time\").\n\t\t\tSchemaType(timeSchema).\n\t\t\tOptional(),\n\t}\n}\n\n// Edges of the AuthCode.\nfunc (AuthCode) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/authrequest.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table auth_request\n(\n    id                        text      not null  primary key,\n    client_id                 text      not null,\n    response_types            blob      not null,\n    scopes                    blob      not null,\n    redirect_uri              text      not null,\n    nonce                     text      not null,\n    state                     text      not null,\n    force_approval_prompt     integer   not null,\n    logged_in                 integer   not null,\n    claims_user_id            text      not null,\n    claims_username           text      not null,\n    claims_email              text      not null,\n    claims_email_verified     integer   not null,\n    claims_groups             blob      not null,\n    connector_id              text      not null,\n    connector_data            blob,\n    expiry                    timestamp not null,\n    claims_preferred_username text default '' not null,\n    code_challenge            text default '' not null,\n    code_challenge_method     text default '' not null,\n    hmac_key                  blob\n);\n*/\n\n// AuthRequest holds the schema definition for the AuthRequest entity.\ntype AuthRequest struct {\n\tent.Schema\n}\n\n// Fields of the AuthRequest.\nfunc (AuthRequest) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"client_id\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.JSON(\"scopes\", []string{}).\n\t\t\tOptional(),\n\t\tfield.JSON(\"response_types\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Text(\"redirect_uri\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.Text(\"nonce\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.Text(\"state\").\n\t\t\tSchemaType(textSchema),\n\n\t\tfield.Bool(\"force_approval_prompt\"),\n\t\tfield.Bool(\"logged_in\"),\n\n\t\tfield.Text(\"claims_user_id\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.Text(\"claims_username\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.Text(\"claims_email\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.Bool(\"claims_email_verified\"),\n\t\tfield.JSON(\"claims_groups\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Text(\"claims_preferred_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\n\t\tfield.Text(\"connector_id\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.Bytes(\"connector_data\").\n\t\t\tNillable().\n\t\t\tOptional(),\n\t\tfield.Time(\"expiry\").\n\t\t\tSchemaType(timeSchema),\n\n\t\tfield.Text(\"code_challenge\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"code_challenge_method\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Bytes(\"hmac_key\"),\n\t\tfield.Bool(\"mfa_validated\").\n\t\t\tDefault(false),\n\t\tfield.Text(\"prompt\").SchemaType(textSchema).Default(\"\"),\n\t\tfield.Int(\"max_age\").Default(-1),\n\t\tfield.Time(\"auth_time\").SchemaType(timeSchema).Optional(),\n\t}\n}\n\n// Edges of the AuthRequest.\nfunc (AuthRequest) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/authsession.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n// AuthSession holds the schema definition for the AuthSession entity.\ntype AuthSession struct {\n\tent.Schema\n}\n\n// Fields of the AuthSession.\nfunc (AuthSession) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"user_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"connector_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"nonce\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Bytes(\"client_states\"),\n\t\tfield.Time(\"created_at\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Time(\"last_activity\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Text(\"ip_address\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"user_agent\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Time(\"absolute_expiry\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Time(\"idle_expiry\").\n\t\t\tSchemaType(timeSchema),\n\t}\n}\n\n// Edges of the AuthSession.\nfunc (AuthSession) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/client.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table client\n(\n    id            text    not null  primary key,\n    secret        text    not null,\n    redirect_uris blob    not null,\n    trusted_peers blob    not null,\n    public        integer not null,\n    name          text    not null,\n    logo_url      text    not null\n);\n*/\n\n// OAuth2Client holds the schema definition for the Client entity.\ntype OAuth2Client struct {\n\tent.Schema\n}\n\n// Fields of the OAuth2Client.\nfunc (OAuth2Client) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tMaxLen(100).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"secret\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.JSON(\"redirect_uris\", []string{}).\n\t\t\tOptional(),\n\t\tfield.JSON(\"trusted_peers\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Bool(\"public\"),\n\t\tfield.Text(\"name\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"logo_url\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.JSON(\"allowed_connectors\", []string{}).\n\t\t\tOptional(),\n\t\tfield.JSON(\"mfa_chain\", []string{}).\n\t\t\tOptional(),\n\t}\n}\n\n// Edges of the OAuth2Client.\nfunc (OAuth2Client) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/connector.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table connector\n(\n    id               text not null  primary key,\n    type             text not null,\n    name             text not null,\n    resource_version text not null,\n    config           blob\n);\n*/\n\n// Connector holds the schema definition for the Client entity.\ntype Connector struct {\n\tent.Schema\n}\n\n// Fields of the Connector.\nfunc (Connector) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tMaxLen(100).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"type\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"name\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"resource_version\").\n\t\t\tSchemaType(textSchema),\n\t\tfield.Bytes(\"config\"),\n\t\tfield.JSON(\"grant_types\", []string{}).\n\t\t\tOptional(),\n\t}\n}\n\n// Edges of the Connector.\nfunc (Connector) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/devicerequest.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table device_request\n(\n    user_code     text      not null  primary key,\n    device_code   text      not null,\n    client_id     text      not null,\n    client_secret text,\n    scopes        blob      not null,\n    expiry        timestamp not null\n);\n*/\n\n// DeviceRequest holds the schema definition for the DeviceRequest entity.\ntype DeviceRequest struct {\n\tent.Schema\n}\n\n// Fields of the DeviceRequest.\nfunc (DeviceRequest) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"user_code\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"device_code\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"client_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"client_secret\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.JSON(\"scopes\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Time(\"expiry\").\n\t\t\tSchemaType(timeSchema),\n\t}\n}\n\n// Edges of the DeviceRequest.\nfunc (DeviceRequest) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/devicetoken.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table device_token\n(\n    device_code           text            not null primary key,\n    status                text            not null,\n    token                 blob,\n    expiry                timestamp       not null,\n    last_request          timestamp       not null,\n    poll_interval         integer         not null,\n    code_challenge        text default '' not null,\n    code_challenge_method text default '' not null\n);\n*/\n\n// DeviceToken holds the schema definition for the DeviceToken entity.\ntype DeviceToken struct {\n\tent.Schema\n}\n\n// Fields of the DeviceToken.\nfunc (DeviceToken) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"device_code\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"status\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Bytes(\"token\").Nillable().Optional(),\n\t\tfield.Time(\"expiry\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Time(\"last_request\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Int(\"poll_interval\"),\n\t\tfield.Text(\"code_challenge\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"code_challenge_method\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t}\n}\n\n// Edges of the DeviceToken.\nfunc (DeviceToken) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/dialects.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent/dialect\"\n)\n\nvar textSchema = map[string]string{\n\tdialect.Postgres: \"text\",\n\tdialect.SQLite:   \"text\",\n\t// MySQL doesn't support indices on text fields w/o\n\t// specifying key length. Use varchar instead (767 byte\n\t// is the max key length for InnoDB with 4k pages).\n\t// For compound indexes (with two keys) even less.\n\tdialect.MySQL: \"varchar(384)\",\n}\n\nvar timeSchema = map[string]string{\n\tdialect.Postgres: \"timestamptz\",\n\tdialect.SQLite:   \"timestamp\",\n\tdialect.MySQL:    \"datetime(3)\",\n}\n"
  },
  {
    "path": "storage/ent/schema/keys.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n\t\"github.com/go-jose/go-jose/v4\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n/* Original SQL table:\ncreate table keys\n(\n    id                text      not null  primary key,\n    verification_keys blob      not null,\n    signing_key       blob      not null,\n    signing_key_pub   blob      not null,\n    next_rotation     timestamp not null\n);\n*/\n\n// Keys holds the schema definition for the Keys entity.\ntype Keys struct {\n\tent.Schema\n}\n\n// Fields of the Keys.\nfunc (Keys) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.JSON(\"verification_keys\", []storage.VerificationKey{}),\n\t\tfield.JSON(\"signing_key\", jose.JSONWebKey{}),\n\t\tfield.JSON(\"signing_key_pub\", jose.JSONWebKey{}),\n\t\tfield.Time(\"next_rotation\").\n\t\t\tSchemaType(timeSchema),\n\t}\n}\n\n// Edges of the Keys.\nfunc (Keys) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/offlinesession.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table offline_session\n(\n    user_id        text not null,\n    conn_id        text not null,\n    refresh        blob not null,\n    connector_data blob,\n    primary key (user_id, conn_id)\n);\n*/\n\n// OfflineSession holds the schema definition for the OfflineSession entity.\ntype OfflineSession struct {\n\tent.Schema\n}\n\n// Fields of the OfflineSession.\nfunc (OfflineSession) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\t// Using id field here because it's impossible to create multi-key primary yet\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"user_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"conn_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Bytes(\"refresh\"),\n\t\tfield.Bytes(\"connector_data\").Nillable().Optional(),\n\t}\n}\n\n// Edges of the OfflineSession.\nfunc (OfflineSession) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/password.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table password\n(\n    email    text not null  primary key,\n    hash     blob not null,\n    username text not null,\n    user_id  text not null\n);\n*/\n\n// Password holds the schema definition for the Password entity.\ntype Password struct {\n\tent.Schema\n}\n\n// Fields of the Password.\nfunc (Password) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"email\").\n\t\t\tSchemaType(textSchema).\n\t\t\tStorageKey(\"email\"). // use email as ID field to make querying easier\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Bytes(\"hash\"),\n\t\tfield.Text(\"username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"name\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"preferred_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Bool(\"email_verified\").\n\t\t\tOptional().\n\t\t\tNillable(),\n\t\tfield.Text(\"user_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.JSON(\"groups\", []string{}).\n\t\t\tOptional(),\n\t}\n}\n\n// Edges of the Password.\nfunc (Password) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/refreshtoken.go",
    "content": "package schema\n\nimport (\n\t\"time\"\n\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n/* Original SQL table:\ncreate table refresh_token\n(\n    id                        text    not null  primary key,\n    client_id                 text    not null,\n    scopes                    blob    not null,\n    nonce                     text    not null,\n    claims_user_id            text    not null,\n    claims_username           text    not null,\n    claims_email              text    not null,\n    claims_email_verified     integer not null,\n    claims_groups             blob    not null,\n    connector_id              text    not null,\n    connector_data            blob,\n    token                     text      default '' not null,\n    created_at                timestamp default '0001-01-01 00:00:00 UTC' not null,\n    last_used                 timestamp default '0001-01-01 00:00:00 UTC' not null,\n    claims_preferred_username text      default '' not null,\n    obsolete_token            text      default ''\n);\n*/\n\n// RefreshToken holds the schema definition for the RefreshToken entity.\ntype RefreshToken struct {\n\tent.Schema\n}\n\n// Fields of the RefreshToken.\nfunc (RefreshToken) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"client_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.JSON(\"scopes\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Text(\"nonce\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\n\t\tfield.Text(\"claims_user_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"claims_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"claims_email\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Bool(\"claims_email_verified\"),\n\t\tfield.JSON(\"claims_groups\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Text(\"claims_preferred_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\n\t\tfield.Text(\"connector_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Bytes(\"connector_data\").\n\t\t\tNillable().\n\t\t\tOptional(),\n\n\t\tfield.Text(\"token\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"obsolete_token\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\n\t\tfield.Time(\"created_at\").\n\t\t\tSchemaType(timeSchema).\n\t\t\tDefault(time.Now),\n\t\tfield.Time(\"last_used\").\n\t\t\tSchemaType(timeSchema).\n\t\t\tDefault(time.Now),\n\t}\n}\n\n// Edges of the RefreshToken.\nfunc (RefreshToken) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/schema/useridentity.go",
    "content": "package schema\n\nimport (\n\t\"entgo.io/ent\"\n\t\"entgo.io/ent/schema/field\"\n)\n\n// UserIdentity holds the schema definition for the UserIdentity entity.\ntype UserIdentity struct {\n\tent.Schema\n}\n\n// Fields of the UserIdentity.\nfunc (UserIdentity) Fields() []ent.Field {\n\treturn []ent.Field{\n\t\t// Using id field here because it's impossible to create multi-key primary yet\n\t\tfield.Text(\"id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty().\n\t\t\tUnique(),\n\t\tfield.Text(\"user_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"connector_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tNotEmpty(),\n\t\tfield.Text(\"claims_user_id\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"claims_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"claims_preferred_username\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Text(\"claims_email\").\n\t\t\tSchemaType(textSchema).\n\t\t\tDefault(\"\"),\n\t\tfield.Bool(\"claims_email_verified\").\n\t\t\tDefault(false),\n\t\tfield.JSON(\"claims_groups\", []string{}).\n\t\t\tOptional(),\n\t\tfield.Bytes(\"consents\"),\n\t\tfield.Bytes(\"mfa_secrets\").\n\t\t\tNillable().\n\t\t\tOptional(),\n\t\tfield.Time(\"created_at\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Time(\"last_login\").\n\t\t\tSchemaType(timeSchema),\n\t\tfield.Time(\"blocked_until\").\n\t\t\tSchemaType(timeSchema),\n\t}\n}\n\n// Edges of the UserIdentity.\nfunc (UserIdentity) Edges() []ent.Edge {\n\treturn []ent.Edge{}\n}\n"
  },
  {
    "path": "storage/ent/sqlite.go",
    "content": "package ent\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"log/slog\"\n\t\"strings\"\n\n\t\"entgo.io/ent/dialect/sql\"\n\t_ \"github.com/mattn/go-sqlite3\" // Register sqlite driver.\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/ent/client\"\n\t\"github.com/dexidp/dex/storage/ent/db\"\n)\n\n// SQLite3 options for creating an SQL db.\ntype SQLite3 struct {\n\tFile string `json:\"file\"`\n}\n\n// Open always returns a new in sqlite3 storage.\nfunc (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) {\n\tlogger.Debug(\"experimental ent-based storage driver is enabled\")\n\n\t// Implicitly set foreign_keys pragma to \"on\" because it is required by ent\n\ts.File = addFK(s.File)\n\n\tdrv, err := sql.Open(\"sqlite3\", s.File)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// always allow only one connection to sqlite3, any other thread/go-routine\n\t// attempting concurrent access will have to wait\n\tpool := drv.DB()\n\tpool.SetMaxOpenConns(1)\n\n\tdatabaseClient := client.NewDatabase(\n\t\tclient.WithClient(db.NewClient(db.Driver(drv))),\n\t\tclient.WithHasher(sha256.New),\n\t)\n\n\tif err := databaseClient.Schema().Create(context.TODO()); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn databaseClient, nil\n}\n\nfunc addFK(dsn string) string {\n\tif strings.Contains(dsn, \"_fk\") {\n\t\treturn dsn\n\t}\n\n\tdelim := \"?\"\n\tif strings.Contains(dsn, \"?\") {\n\t\tdelim = \"&\"\n\t}\n\treturn dsn + delim + \"_fk=1\"\n}\n"
  },
  {
    "path": "storage/ent/sqlite_test.go",
    "content": "package ent\n\nimport (\n\t\"log/slog\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/conformance\"\n)\n\nfunc newSQLiteStorage(t *testing.T) storage.Storage {\n\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\n\tcfg := SQLite3{File: \":memory:\"}\n\ts, err := cfg.Open(logger)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn s\n}\n\nfunc TestSQLite3(t *testing.T) {\n\tconformance.RunTests(t, newSQLiteStorage)\n\tconformance.RunConcurrencyTests(t, newSQLiteStorage)\n}\n"
  },
  {
    "path": "storage/ent/types.go",
    "content": "package ent\n\n// NetworkDB contains options common to SQL databases accessed over network.\ntype NetworkDB struct {\n\tDatabase string\n\tUser     string\n\tPassword string\n\tHost     string\n\tPort     uint16\n\n\tConnectionTimeout int // Seconds\n\n\tMaxOpenConns    int // default: 5\n\tMaxIdleConns    int // default: 5\n\tConnMaxLifetime int // Seconds, default: not set\n}\n\n// SSL represents SSL options for network databases.\ntype SSL struct {\n\tMode   string\n\tCAFile string\n\t// Files for client auth.\n\tKeyFile  string\n\tCertFile string\n}\n"
  },
  {
    "path": "storage/ent/utils.go",
    "content": "package ent\n\nimport \"os\"\n\nfunc getenv(key, defaultVal string) string {\n\tif val := os.Getenv(key); val != \"\" {\n\t\treturn val\n\t}\n\treturn defaultVal\n}\n"
  },
  {
    "path": "storage/etcd/config.go",
    "content": "package etcd\n\nimport (\n\t\"log/slog\"\n\t\"time\"\n\n\t\"go.etcd.io/etcd/client/pkg/v3/transport\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.etcd.io/etcd/client/v3/namespace\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\nvar defaultDialTimeout = 2 * time.Second\n\n// SSL represents SSL options for etcd databases.\ntype SSL struct {\n\tServerName string `json:\"serverName\"`\n\tCAFile     string `json:\"caFile\"`\n\tKeyFile    string `json:\"keyFile\"`\n\tCertFile   string `json:\"certFile\"`\n}\n\n// Etcd options for connecting to etcd databases.\n// If you are using a shared etcd cluster for storage, it might be useful to\n// configure an etcd namespace either via Namespace field or using `etcd grpc-proxy\n// --namespace=<prefix>`\ntype Etcd struct {\n\tEndpoints []string `json:\"endpoints\"`\n\tNamespace string   `json:\"namespace\"`\n\tUsername  string   `json:\"username\"`\n\tPassword  string   `json:\"password\"`\n\tSSL       SSL      `json:\"ssl\"`\n}\n\n// Open creates a new storage implementation backed by Etcd\nfunc (p *Etcd) Open(logger *slog.Logger) (storage.Storage, error) {\n\treturn p.open(logger)\n}\n\nfunc (p *Etcd) open(logger *slog.Logger) (*conn, error) {\n\tcfg := clientv3.Config{\n\t\tEndpoints:   p.Endpoints,\n\t\tDialTimeout: defaultDialTimeout,\n\t\tUsername:    p.Username,\n\t\tPassword:    p.Password,\n\t}\n\n\tvar cfgtls *transport.TLSInfo\n\ttlsinfo := transport.TLSInfo{}\n\tif p.SSL.CertFile != \"\" {\n\t\ttlsinfo.CertFile = p.SSL.CertFile\n\t\tcfgtls = &tlsinfo\n\t}\n\n\tif p.SSL.KeyFile != \"\" {\n\t\ttlsinfo.KeyFile = p.SSL.KeyFile\n\t\tcfgtls = &tlsinfo\n\t}\n\n\tif p.SSL.CAFile != \"\" {\n\t\ttlsinfo.TrustedCAFile = p.SSL.CAFile\n\t\tcfgtls = &tlsinfo\n\t}\n\n\tif p.SSL.ServerName != \"\" {\n\t\ttlsinfo.ServerName = p.SSL.ServerName\n\t\tcfgtls = &tlsinfo\n\t}\n\n\tif cfgtls != nil {\n\t\tclientTLS, err := cfgtls.ClientConfig()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tcfg.TLS = clientTLS\n\t}\n\n\tdb, err := clientv3.New(cfg)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(p.Namespace) > 0 {\n\t\tdb.KV = namespace.NewKV(db.KV, p.Namespace)\n\t}\n\tc := &conn{\n\t\tdb:     db,\n\t\tlogger: logger,\n\t}\n\treturn c, nil\n}\n"
  },
  {
    "path": "storage/etcd/etcd.go",
    "content": "package etcd\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"time\"\n\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\nconst (\n\tclientPrefix         = \"client/\"\n\tauthCodePrefix       = \"auth_code/\"\n\trefreshTokenPrefix   = \"refresh_token/\"\n\tauthRequestPrefix    = \"auth_req/\"\n\tpasswordPrefix       = \"password/\"\n\tofflineSessionPrefix = \"offline_session/\"\n\tconnectorPrefix      = \"connector/\"\n\tkeysName             = \"openid-connect-keys\"\n\tdeviceRequestPrefix  = \"device_req/\"\n\tdeviceTokenPrefix    = \"device_token/\"\n\tuserIdentityPrefix   = \"user_identity/\"\n\tauthSessionPrefix    = \"auth_session/\"\n\n\t// defaultStorageTimeout will be applied to all storage's operations.\n\tdefaultStorageTimeout = 5 * time.Second\n)\n\nvar _ storage.Storage = (*conn)(nil)\n\ntype conn struct {\n\tdb     *clientv3.Client\n\tlogger *slog.Logger\n}\n\nfunc (c *conn) Close() error {\n\treturn c.db.Close()\n}\n\nfunc (c *conn) GarbageCollect(ctx context.Context, now time.Time) (result storage.GCResult, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tauthRequests, err := c.listAuthRequests(ctx)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\tvar delErr error\n\tfor _, authRequest := range authRequests {\n\t\tif now.After(authRequest.Expiry) {\n\t\t\tif err := c.deleteKey(ctx, keyID(authRequestPrefix, authRequest.ID)); err != nil {\n\t\t\t\tc.logger.Error(\"failed to delete auth request\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete auth request: %v\", err)\n\t\t\t}\n\t\t\tresult.AuthRequests++\n\t\t}\n\t}\n\tif delErr != nil {\n\t\treturn result, delErr\n\t}\n\n\tauthCodes, err := c.listAuthCodes(ctx)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\tfor _, authCode := range authCodes {\n\t\tif now.After(authCode.Expiry) {\n\t\t\tif err := c.deleteKey(ctx, keyID(authCodePrefix, authCode.ID)); err != nil {\n\t\t\t\tc.logger.Error(\"failed to delete auth code\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete auth code: %v\", err)\n\t\t\t}\n\t\t\tresult.AuthCodes++\n\t\t}\n\t}\n\n\tdeviceRequests, err := c.listDeviceRequests(ctx)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\tfor _, deviceRequest := range deviceRequests {\n\t\tif now.After(deviceRequest.Expiry) {\n\t\t\tif err := c.deleteKey(ctx, keyID(deviceRequestPrefix, deviceRequest.UserCode)); err != nil {\n\t\t\t\tc.logger.Error(\"failed to delete device request\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete device request: %v\", err)\n\t\t\t}\n\t\t\tresult.DeviceRequests++\n\t\t}\n\t}\n\n\tdeviceTokens, err := c.listDeviceTokens(ctx)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\tfor _, deviceToken := range deviceTokens {\n\t\tif now.After(deviceToken.Expiry) {\n\t\t\tif err := c.deleteKey(ctx, keyID(deviceTokenPrefix, deviceToken.DeviceCode)); err != nil {\n\t\t\t\tc.logger.Error(\"failed to delete device token\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete device token: %v\", err)\n\t\t\t}\n\t\t\tresult.DeviceTokens++\n\t\t}\n\t}\n\n\tauthSessions, err := c.listAuthSessionsInternal(ctx)\n\tif err != nil {\n\t\treturn result, err\n\t}\n\n\tfor _, authSession := range authSessions {\n\t\tif now.After(authSession.AbsoluteExpiry) || now.After(authSession.IdleExpiry) {\n\t\t\tif err := c.deleteKey(ctx, keyAuthSession(authSession.UserID, authSession.ConnectorID)); err != nil {\n\t\t\t\tc.logger.Error(\"failed to delete auth session\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete auth session: %v\", err)\n\t\t\t} else {\n\t\t\t\tresult.AuthSessions++\n\t\t\t}\n\t\t}\n\t}\n\n\treturn result, delErr\n}\n\nfunc (c *conn) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) error {\n\treturn c.txnCreate(ctx, keyID(authRequestPrefix, a.ID), fromStorageAuthRequest(a))\n}\n\nfunc (c *conn) GetAuthRequest(ctx context.Context, id string) (a storage.AuthRequest, err error) {\n\t// TODO: Add this to other funcs??\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar req AuthRequest\n\tif err = c.getKey(ctx, keyID(authRequestPrefix, id), &req); err != nil {\n\t\treturn\n\t}\n\treturn toStorageAuthRequest(req), nil\n}\n\nfunc (c *conn) UpdateAuthRequest(ctx context.Context, id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyID(authRequestPrefix, id), func(currentValue []byte) ([]byte, error) {\n\t\tvar current AuthRequest\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(toStorageAuthRequest(current))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(fromStorageAuthRequest(updated))\n\t})\n}\n\nfunc (c *conn) DeleteAuthRequest(ctx context.Context, id string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyID(authRequestPrefix, id))\n}\n\nfunc (c *conn) CreateAuthCode(ctx context.Context, a storage.AuthCode) error {\n\treturn c.txnCreate(ctx, keyID(authCodePrefix, a.ID), fromStorageAuthCode(a))\n}\n\nfunc (c *conn) GetAuthCode(ctx context.Context, id string) (a storage.AuthCode, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar ac AuthCode\n\terr = c.getKey(ctx, keyID(authCodePrefix, id), &ac)\n\tif err == nil {\n\t\ta = toStorageAuthCode(ac)\n\t}\n\treturn a, err\n}\n\nfunc (c *conn) DeleteAuthCode(ctx context.Context, id string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyID(authCodePrefix, id))\n}\n\nfunc (c *conn) CreateRefresh(ctx context.Context, r storage.RefreshToken) error {\n\treturn c.txnCreate(ctx, keyID(refreshTokenPrefix, r.ID), fromStorageRefreshToken(r))\n}\n\nfunc (c *conn) GetRefresh(ctx context.Context, id string) (r storage.RefreshToken, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar token RefreshToken\n\tif err = c.getKey(ctx, keyID(refreshTokenPrefix, id), &token); err != nil {\n\t\treturn\n\t}\n\treturn toStorageRefreshToken(token), nil\n}\n\nfunc (c *conn) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyID(refreshTokenPrefix, id), func(currentValue []byte) ([]byte, error) {\n\t\tvar current RefreshToken\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(toStorageRefreshToken(current))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(fromStorageRefreshToken(updated))\n\t})\n}\n\nfunc (c *conn) DeleteRefresh(ctx context.Context, id string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyID(refreshTokenPrefix, id))\n}\n\nfunc (c *conn) ListRefreshTokens(ctx context.Context) (tokens []storage.RefreshToken, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tres, err := c.db.Get(ctx, refreshTokenPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn tokens, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar token RefreshToken\n\t\tif err = json.Unmarshal(v.Value, &token); err != nil {\n\t\t\treturn tokens, err\n\t\t}\n\t\ttokens = append(tokens, toStorageRefreshToken(token))\n\t}\n\treturn tokens, nil\n}\n\nfunc (c *conn) CreateClient(ctx context.Context, cli storage.Client) error {\n\treturn c.txnCreate(ctx, keyID(clientPrefix, cli.ID), cli)\n}\n\nfunc (c *conn) GetClient(ctx context.Context, id string) (cli storage.Client, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\terr = c.getKey(ctx, keyID(clientPrefix, id), &cli)\n\treturn cli, err\n}\n\nfunc (c *conn) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyID(clientPrefix, id), func(currentValue []byte) ([]byte, error) {\n\t\tvar current storage.Client\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(current)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(updated)\n\t})\n}\n\nfunc (c *conn) DeleteClient(ctx context.Context, id string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyID(clientPrefix, id))\n}\n\nfunc (c *conn) ListClients(ctx context.Context) (clients []storage.Client, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tres, err := c.db.Get(ctx, clientPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn clients, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar cli storage.Client\n\t\tif err = json.Unmarshal(v.Value, &cli); err != nil {\n\t\t\treturn clients, err\n\t\t}\n\t\tclients = append(clients, cli)\n\t}\n\treturn clients, nil\n}\n\nfunc (c *conn) CreatePassword(ctx context.Context, p storage.Password) error {\n\treturn c.txnCreate(ctx, passwordPrefix+strings.ToLower(p.Email), p)\n}\n\nfunc (c *conn) GetPassword(ctx context.Context, email string) (p storage.Password, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\terr = c.getKey(ctx, keyEmail(passwordPrefix, email), &p)\n\treturn p, err\n}\n\nfunc (c *conn) UpdatePassword(ctx context.Context, email string, updater func(p storage.Password) (storage.Password, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyEmail(passwordPrefix, email), func(currentValue []byte) ([]byte, error) {\n\t\tvar current storage.Password\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(current)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(updated)\n\t})\n}\n\nfunc (c *conn) DeletePassword(ctx context.Context, email string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyEmail(passwordPrefix, email))\n}\n\nfunc (c *conn) ListPasswords(ctx context.Context) (passwords []storage.Password, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tres, err := c.db.Get(ctx, passwordPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn passwords, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar p storage.Password\n\t\tif err = json.Unmarshal(v.Value, &p); err != nil {\n\t\t\treturn passwords, err\n\t\t}\n\t\tpasswords = append(passwords, p)\n\t}\n\treturn passwords, nil\n}\n\nfunc (c *conn) CreateOfflineSessions(ctx context.Context, s storage.OfflineSessions) error {\n\treturn c.txnCreate(ctx, keySession(s.UserID, s.ConnID), fromStorageOfflineSessions(s))\n}\n\nfunc (c *conn) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s storage.OfflineSessions) (storage.OfflineSessions, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keySession(userID, connID), func(currentValue []byte) ([]byte, error) {\n\t\tvar current OfflineSessions\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(toStorageOfflineSessions(current))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(fromStorageOfflineSessions(updated))\n\t})\n}\n\nfunc (c *conn) GetOfflineSessions(ctx context.Context, userID string, connID string) (s storage.OfflineSessions, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar os OfflineSessions\n\tif err = c.getKey(ctx, keySession(userID, connID), &os); err != nil {\n\t\treturn\n\t}\n\treturn toStorageOfflineSessions(os), nil\n}\n\nfunc (c *conn) DeleteOfflineSessions(ctx context.Context, userID string, connID string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keySession(userID, connID))\n}\n\nfunc (c *conn) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) error {\n\treturn c.txnCreate(ctx, keyUserIdentity(u.UserID, u.ConnectorID), fromStorageUserIdentity(u))\n}\n\nfunc (c *conn) GetUserIdentity(ctx context.Context, userID, connectorID string) (u storage.UserIdentity, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar ui UserIdentity\n\tif err = c.getKey(ctx, keyUserIdentity(userID, connectorID), &ui); err != nil {\n\t\treturn\n\t}\n\treturn toStorageUserIdentity(ui), nil\n}\n\nfunc (c *conn) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyUserIdentity(userID, connectorID), func(currentValue []byte) ([]byte, error) {\n\t\tvar current UserIdentity\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(toStorageUserIdentity(current))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(fromStorageUserIdentity(updated))\n\t})\n}\n\nfunc (c *conn) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyUserIdentity(userID, connectorID))\n}\n\nfunc (c *conn) ListUserIdentities(ctx context.Context) (identities []storage.UserIdentity, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tres, err := c.db.Get(ctx, userIdentityPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn identities, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar ui UserIdentity\n\t\tif err = json.Unmarshal(v.Value, &ui); err != nil {\n\t\t\treturn identities, err\n\t\t}\n\t\tidentities = append(identities, toStorageUserIdentity(ui))\n\t}\n\treturn identities, nil\n}\n\nfunc (c *conn) CreateAuthSession(ctx context.Context, s storage.AuthSession) error {\n\treturn c.txnCreate(ctx, keyAuthSession(s.UserID, s.ConnectorID), fromStorageAuthSession(s))\n}\n\nfunc (c *conn) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar s AuthSession\n\tif err := c.getKey(ctx, keyAuthSession(userID, connectorID), &s); err != nil {\n\t\treturn storage.AuthSession{}, err\n\t}\n\treturn toStorageAuthSession(s), nil\n}\n\nfunc (c *conn) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyAuthSession(userID, connectorID), func(currentValue []byte) ([]byte, error) {\n\t\tvar current AuthSession\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(toStorageAuthSession(current))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(fromStorageAuthSession(updated))\n\t})\n}\n\nfunc (c *conn) ListAuthSessions(ctx context.Context) (sessions []storage.AuthSession, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\n\tres, err := c.listAuthSessionsInternal(ctx)\n\tif err != nil {\n\t\treturn sessions, err\n\t}\n\tfor _, v := range res {\n\t\tsessions = append(sessions, toStorageAuthSession(v))\n\t}\n\treturn sessions, nil\n}\n\nfunc (c *conn) DeleteAuthSession(ctx context.Context, userID, connectorID string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyAuthSession(userID, connectorID))\n}\n\nfunc (c *conn) CreateConnector(ctx context.Context, connector storage.Connector) error {\n\treturn c.txnCreate(ctx, keyID(connectorPrefix, connector.ID), connector)\n}\n\nfunc (c *conn) GetConnector(ctx context.Context, id string) (conn storage.Connector, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\terr = c.getKey(ctx, keyID(connectorPrefix, id), &conn)\n\treturn conn, err\n}\n\nfunc (c *conn) UpdateConnector(ctx context.Context, id string, updater func(s storage.Connector) (storage.Connector, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyID(connectorPrefix, id), func(currentValue []byte) ([]byte, error) {\n\t\tvar current storage.Connector\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(current)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(updated)\n\t})\n}\n\nfunc (c *conn) DeleteConnector(ctx context.Context, id string) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.deleteKey(ctx, keyID(connectorPrefix, id))\n}\n\nfunc (c *conn) ListConnectors(ctx context.Context) (connectors []storage.Connector, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tres, err := c.db.Get(ctx, connectorPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar c storage.Connector\n\t\tif err = json.Unmarshal(v.Value, &c); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconnectors = append(connectors, c)\n\t}\n\treturn connectors, nil\n}\n\nfunc (c *conn) GetKeys(ctx context.Context) (keys storage.Keys, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tres, err := c.db.Get(ctx, keysName)\n\tif err != nil {\n\t\treturn keys, err\n\t}\n\tif res.Count > 0 && len(res.Kvs) > 0 {\n\t\terr = json.Unmarshal(res.Kvs[0].Value, &keys)\n\t}\n\treturn keys, err\n}\n\nfunc (c *conn) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keysName, func(currentValue []byte) ([]byte, error) {\n\t\tvar current storage.Keys\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(current)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(updated)\n\t})\n}\n\nfunc (c *conn) deleteKey(ctx context.Context, key string) error {\n\tres, err := c.db.Delete(ctx, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif res.Deleted == 0 {\n\t\treturn storage.ErrNotFound\n\t}\n\treturn nil\n}\n\nfunc (c *conn) getKey(ctx context.Context, key string, value interface{}) error {\n\tr, err := c.db.Get(ctx, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif r.Count == 0 {\n\t\treturn storage.ErrNotFound\n\t}\n\treturn json.Unmarshal(r.Kvs[0].Value, value)\n}\n\nfunc (c *conn) listAuthRequests(ctx context.Context) (reqs []AuthRequest, err error) {\n\tres, err := c.db.Get(ctx, authRequestPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn reqs, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar r AuthRequest\n\t\tif err = json.Unmarshal(v.Value, &r); err != nil {\n\t\t\treturn reqs, err\n\t\t}\n\t\treqs = append(reqs, r)\n\t}\n\treturn reqs, nil\n}\n\nfunc (c *conn) listAuthCodes(ctx context.Context) (codes []AuthCode, err error) {\n\tres, err := c.db.Get(ctx, authCodePrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn codes, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar c AuthCode\n\t\tif err = json.Unmarshal(v.Value, &c); err != nil {\n\t\t\treturn codes, err\n\t\t}\n\t\tcodes = append(codes, c)\n\t}\n\treturn codes, nil\n}\n\nfunc (c *conn) listAuthSessionsInternal(ctx context.Context) (sessions []AuthSession, err error) {\n\tres, err := c.db.Get(ctx, authSessionPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn sessions, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar s AuthSession\n\t\tif err = json.Unmarshal(v.Value, &s); err != nil {\n\t\t\treturn sessions, err\n\t\t}\n\t\tsessions = append(sessions, s)\n\t}\n\treturn sessions, nil\n}\n\nfunc (c *conn) txnCreate(ctx context.Context, key string, value interface{}) error {\n\tb, err := json.Marshal(value)\n\tif err != nil {\n\t\treturn err\n\t}\n\ttxn := c.db.Txn(ctx)\n\tres, err := txn.\n\t\tIf(clientv3.Compare(clientv3.CreateRevision(key), \"=\", 0)).\n\t\tThen(clientv3.OpPut(key, string(b))).\n\t\tCommit()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !res.Succeeded {\n\t\treturn storage.ErrAlreadyExists\n\t}\n\treturn nil\n}\n\nfunc (c *conn) txnUpdate(ctx context.Context, key string, update func(current []byte) ([]byte, error)) error {\n\tgetResp, err := c.db.Get(ctx, key)\n\tif err != nil {\n\t\treturn err\n\t}\n\tvar currentValue []byte\n\tvar modRev int64\n\tif len(getResp.Kvs) > 0 {\n\t\tcurrentValue = getResp.Kvs[0].Value\n\t\tmodRev = getResp.Kvs[0].ModRevision\n\t}\n\n\tupdatedValue, err := update(currentValue)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttxn := c.db.Txn(ctx)\n\tupdateResp, err := txn.\n\t\tIf(clientv3.Compare(clientv3.ModRevision(key), \"=\", modRev)).\n\t\tThen(clientv3.OpPut(key, string(updatedValue))).\n\t\tCommit()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !updateResp.Succeeded {\n\t\treturn fmt.Errorf(\"failed to update key=%q: concurrent conflicting update happened\", key)\n\t}\n\treturn nil\n}\n\nfunc keyID(prefix, id string) string       { return prefix + id }\nfunc keyEmail(prefix, email string) string { return prefix + strings.ToLower(email) }\nfunc keySession(userID, connID string) string {\n\treturn offlineSessionPrefix + strings.ToLower(userID+\"|\"+connID)\n}\n\nfunc keyUserIdentity(userID, connectorID string) string {\n\treturn userIdentityPrefix + strings.ToLower(userID+\"|\"+connectorID)\n}\n\nfunc keyAuthSession(userID, connectorID string) string {\n\treturn authSessionPrefix + strings.ToLower(userID+\"|\"+connectorID)\n}\n\nfunc (c *conn) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) error {\n\treturn c.txnCreate(ctx, keyID(deviceRequestPrefix, d.UserCode), fromStorageDeviceRequest(d))\n}\n\nfunc (c *conn) GetDeviceRequest(ctx context.Context, userCode string) (r storage.DeviceRequest, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar dr DeviceRequest\n\tif err = c.getKey(ctx, keyID(deviceRequestPrefix, userCode), &dr); err == nil {\n\t\tr = toStorageDeviceRequest(dr)\n\t}\n\treturn\n}\n\nfunc (c *conn) listDeviceRequests(ctx context.Context) (requests []DeviceRequest, err error) {\n\tres, err := c.db.Get(ctx, deviceRequestPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn requests, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar r DeviceRequest\n\t\tif err = json.Unmarshal(v.Value, &r); err != nil {\n\t\t\treturn requests, err\n\t\t}\n\t\trequests = append(requests, r)\n\t}\n\treturn requests, nil\n}\n\nfunc (c *conn) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) error {\n\treturn c.txnCreate(ctx, keyID(deviceTokenPrefix, t.DeviceCode), fromStorageDeviceToken(t))\n}\n\nfunc (c *conn) GetDeviceToken(ctx context.Context, deviceCode string) (t storage.DeviceToken, err error) {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\tvar dt DeviceToken\n\tif err = c.getKey(ctx, keyID(deviceTokenPrefix, deviceCode), &dt); err == nil {\n\t\tt = toStorageDeviceToken(dt)\n\t}\n\treturn\n}\n\nfunc (c *conn) listDeviceTokens(ctx context.Context) (deviceTokens []DeviceToken, err error) {\n\tres, err := c.db.Get(ctx, deviceTokenPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn deviceTokens, err\n\t}\n\tfor _, v := range res.Kvs {\n\t\tvar dt DeviceToken\n\t\tif err = json.Unmarshal(v.Value, &dt); err != nil {\n\t\t\treturn deviceTokens, err\n\t\t}\n\t\tdeviceTokens = append(deviceTokens, dt)\n\t}\n\treturn deviceTokens, nil\n}\n\nfunc (c *conn) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error {\n\tctx, cancel := context.WithTimeout(ctx, defaultStorageTimeout)\n\tdefer cancel()\n\treturn c.txnUpdate(ctx, keyID(deviceTokenPrefix, deviceCode), func(currentValue []byte) ([]byte, error) {\n\t\tvar current DeviceToken\n\t\tif len(currentValue) > 0 {\n\t\t\tif err := json.Unmarshal(currentValue, &current); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t\tupdated, err := updater(toStorageDeviceToken(current))\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn json.Marshal(fromStorageDeviceToken(updated))\n\t})\n}\n"
  },
  {
    "path": "storage/etcd/etcd_test.go",
    "content": "package etcd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"runtime\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/conformance\"\n)\n\nfunc withTimeout(t time.Duration, f func()) {\n\tc := make(chan struct{})\n\tdefer close(c)\n\n\tgo func() {\n\t\tselect {\n\t\tcase <-c:\n\t\tcase <-time.After(t):\n\t\t\t// Dump a stack trace of the program. Useful for debugging deadlocks.\n\t\t\tbuf := make([]byte, 2<<20)\n\t\t\tfmt.Fprintf(os.Stderr, \"%s\\n\", buf[:runtime.Stack(buf, true)])\n\t\t\tpanic(\"test took too long\")\n\t\t}\n\t}()\n\n\tf()\n}\n\nfunc cleanDB(c *conn) error {\n\tctx := context.TODO()\n\tfor _, prefix := range []string{\n\t\tclientPrefix,\n\t\tauthCodePrefix,\n\t\trefreshTokenPrefix,\n\t\tauthRequestPrefix,\n\t\tpasswordPrefix,\n\t\tofflineSessionPrefix,\n\t\tconnectorPrefix,\n\t\tdeviceRequestPrefix,\n\t\tdeviceTokenPrefix,\n\t} {\n\t\t_, err := c.db.Delete(ctx, prefix, clientv3.WithPrefix())\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc TestEtcd(t *testing.T) {\n\ttestEtcdEnv := \"DEX_ETCD_ENDPOINTS\"\n\tendpointsStr := os.Getenv(testEtcdEnv)\n\tif endpointsStr == \"\" {\n\t\tt.Skipf(\"test environment variable %q not set, skipping\", testEtcdEnv)\n\t\treturn\n\t}\n\tendpoints := strings.Split(endpointsStr, \",\")\n\n\tnewStorage := func(t *testing.T) storage.Storage {\n\t\ts := &Etcd{\n\t\t\tEndpoints: endpoints,\n\t\t}\n\t\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\t\tconn, err := s.open(logger)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(os.Stdout, err)\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif err := cleanDB(conn); err != nil {\n\t\t\tfmt.Fprintln(os.Stdout, err)\n\t\t\tt.Fatal(err)\n\t\t}\n\t\treturn conn\n\t}\n\n\twithTimeout(time.Second*10, func() {\n\t\tconformance.RunTests(t, newStorage)\n\t})\n\n\twithTimeout(time.Minute*1, func() {\n\t\tconformance.RunTransactionTests(t, newStorage)\n\t})\n\n\t// TODO(nabokihms): etcd uses compare-and-swap (txnUpdate) for UpdateRefreshToken,\n\t// but does not retry on CAS conflicts (\"concurrent conflicting update happened\").\n\t// Under high contention virtually all updates fail — only the first writer succeeds.\n\t// withTimeout(time.Minute*1, func() {\n\t// \t  conformance.RunConcurrencyTests(t, newStorage)\n\t// })\n}\n"
  },
  {
    "path": "storage/etcd/types.go",
    "content": "package etcd\n\nimport (\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// AuthCode is a mirrored struct from storage with JSON struct tags\ntype AuthCode struct {\n\tID          string   `json:\"ID\"`\n\tClientID    string   `json:\"clientID\"`\n\tRedirectURI string   `json:\"redirectURI\"`\n\tNonce       string   `json:\"nonce,omitempty\"`\n\tScopes      []string `json:\"scopes,omitempty\"`\n\n\tConnectorID   string `json:\"connectorID,omitempty\"`\n\tConnectorData []byte `json:\"connectorData,omitempty\"`\n\tClaims        Claims `json:\"claims,omitempty\"`\n\n\tExpiry time.Time `json:\"expiry\"`\n\n\tCodeChallenge       string `json:\"code_challenge,omitempty\"`\n\tCodeChallengeMethod string `json:\"code_challenge_method,omitempty\"`\n\n\tAuthTime time.Time `json:\"auth_time\"`\n}\n\nfunc toStorageAuthCode(a AuthCode) storage.AuthCode {\n\treturn storage.AuthCode{\n\t\tID:            a.ID,\n\t\tClientID:      a.ClientID,\n\t\tRedirectURI:   a.RedirectURI,\n\t\tConnectorID:   a.ConnectorID,\n\t\tConnectorData: a.ConnectorData,\n\t\tNonce:         a.Nonce,\n\t\tScopes:        a.Scopes,\n\t\tClaims:        toStorageClaims(a.Claims),\n\t\tExpiry:        a.Expiry,\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       a.CodeChallenge,\n\t\t\tCodeChallengeMethod: a.CodeChallengeMethod,\n\t\t},\n\t\tAuthTime: a.AuthTime,\n\t}\n}\n\nfunc fromStorageAuthCode(a storage.AuthCode) AuthCode {\n\treturn AuthCode{\n\t\tID:                  a.ID,\n\t\tClientID:            a.ClientID,\n\t\tRedirectURI:         a.RedirectURI,\n\t\tConnectorID:         a.ConnectorID,\n\t\tConnectorData:       a.ConnectorData,\n\t\tNonce:               a.Nonce,\n\t\tScopes:              a.Scopes,\n\t\tClaims:              fromStorageClaims(a.Claims),\n\t\tExpiry:              a.Expiry,\n\t\tCodeChallenge:       a.PKCE.CodeChallenge,\n\t\tCodeChallengeMethod: a.PKCE.CodeChallengeMethod,\n\t\tAuthTime:            a.AuthTime,\n\t}\n}\n\n// AuthRequest is a mirrored struct from storage with JSON struct tags\ntype AuthRequest struct {\n\tID       string `json:\"id\"`\n\tClientID string `json:\"client_id\"`\n\n\tResponseTypes []string `json:\"response_types\"`\n\tScopes        []string `json:\"scopes\"`\n\tRedirectURI   string   `json:\"redirect_uri\"`\n\tNonce         string   `json:\"nonce\"`\n\tState         string   `json:\"state\"`\n\n\tForceApprovalPrompt bool `json:\"force_approval_prompt\"`\n\n\tExpiry time.Time `json:\"expiry\"`\n\n\tLoggedIn bool `json:\"logged_in\"`\n\n\tClaims Claims `json:\"claims\"`\n\n\tConnectorID   string `json:\"connector_id\"`\n\tConnectorData []byte `json:\"connector_data\"`\n\n\tCodeChallenge       string `json:\"code_challenge,omitempty\"`\n\tCodeChallengeMethod string `json:\"code_challenge_method,omitempty\"`\n\n\tHMACKey []byte `json:\"hmac_key\"`\n\n\tMFAValidated bool `json:\"mfa_validated\"`\n\n\tPrompt   string    `json:\"prompt,omitempty\"`\n\tMaxAge   int       `json:\"max_age\"`\n\tAuthTime time.Time `json:\"auth_time\"`\n}\n\nfunc fromStorageAuthRequest(a storage.AuthRequest) AuthRequest {\n\treturn AuthRequest{\n\t\tID:                  a.ID,\n\t\tClientID:            a.ClientID,\n\t\tResponseTypes:       a.ResponseTypes,\n\t\tScopes:              a.Scopes,\n\t\tRedirectURI:         a.RedirectURI,\n\t\tNonce:               a.Nonce,\n\t\tState:               a.State,\n\t\tForceApprovalPrompt: a.ForceApprovalPrompt,\n\t\tExpiry:              a.Expiry,\n\t\tLoggedIn:            a.LoggedIn,\n\t\tClaims:              fromStorageClaims(a.Claims),\n\t\tConnectorID:         a.ConnectorID,\n\t\tConnectorData:       a.ConnectorData,\n\t\tCodeChallenge:       a.PKCE.CodeChallenge,\n\t\tCodeChallengeMethod: a.PKCE.CodeChallengeMethod,\n\t\tHMACKey:             a.HMACKey,\n\t\tMFAValidated:        a.MFAValidated,\n\t\tPrompt:              a.Prompt,\n\t\tMaxAge:              a.MaxAge,\n\t\tAuthTime:            a.AuthTime,\n\t}\n}\n\nfunc toStorageAuthRequest(a AuthRequest) storage.AuthRequest {\n\treturn storage.AuthRequest{\n\t\tID:                  a.ID,\n\t\tClientID:            a.ClientID,\n\t\tResponseTypes:       a.ResponseTypes,\n\t\tScopes:              a.Scopes,\n\t\tRedirectURI:         a.RedirectURI,\n\t\tNonce:               a.Nonce,\n\t\tState:               a.State,\n\t\tForceApprovalPrompt: a.ForceApprovalPrompt,\n\t\tLoggedIn:            a.LoggedIn,\n\t\tConnectorID:         a.ConnectorID,\n\t\tConnectorData:       a.ConnectorData,\n\t\tExpiry:              a.Expiry,\n\t\tClaims:              toStorageClaims(a.Claims),\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       a.CodeChallenge,\n\t\t\tCodeChallengeMethod: a.CodeChallengeMethod,\n\t\t},\n\t\tHMACKey:      a.HMACKey,\n\t\tMFAValidated: a.MFAValidated,\n\t\tPrompt:       a.Prompt,\n\t\tMaxAge:       a.MaxAge,\n\t\tAuthTime:     a.AuthTime,\n\t}\n}\n\n// RefreshToken is a mirrored struct from storage with JSON struct tags\ntype RefreshToken struct {\n\tID string `json:\"id\"`\n\n\tToken         string `json:\"token\"`\n\tObsoleteToken string `json:\"obsolete_token\"`\n\n\tCreatedAt time.Time `json:\"created_at\"`\n\tLastUsed  time.Time `json:\"last_used\"`\n\n\tClientID string `json:\"client_id\"`\n\n\tConnectorID   string `json:\"connector_id\"`\n\tConnectorData []byte `json:\"connector_data\"`\n\tClaims        Claims `json:\"claims\"`\n\n\tScopes []string `json:\"scopes\"`\n\n\tNonce string `json:\"nonce\"`\n}\n\nfunc toStorageRefreshToken(r RefreshToken) storage.RefreshToken {\n\treturn storage.RefreshToken{\n\t\tID:            r.ID,\n\t\tToken:         r.Token,\n\t\tObsoleteToken: r.ObsoleteToken,\n\t\tCreatedAt:     r.CreatedAt,\n\t\tLastUsed:      r.LastUsed,\n\t\tClientID:      r.ClientID,\n\t\tConnectorID:   r.ConnectorID,\n\t\tConnectorData: r.ConnectorData,\n\t\tScopes:        r.Scopes,\n\t\tNonce:         r.Nonce,\n\t\tClaims:        toStorageClaims(r.Claims),\n\t}\n}\n\nfunc fromStorageRefreshToken(r storage.RefreshToken) RefreshToken {\n\treturn RefreshToken{\n\t\tID:            r.ID,\n\t\tToken:         r.Token,\n\t\tObsoleteToken: r.ObsoleteToken,\n\t\tCreatedAt:     r.CreatedAt,\n\t\tLastUsed:      r.LastUsed,\n\t\tClientID:      r.ClientID,\n\t\tConnectorID:   r.ConnectorID,\n\t\tConnectorData: r.ConnectorData,\n\t\tScopes:        r.Scopes,\n\t\tNonce:         r.Nonce,\n\t\tClaims:        fromStorageClaims(r.Claims),\n\t}\n}\n\n// Claims is a mirrored struct from storage with JSON struct tags.\ntype Claims struct {\n\tUserID            string   `json:\"userID\"`\n\tUsername          string   `json:\"username\"`\n\tPreferredUsername string   `json:\"preferredUsername\"`\n\tEmail             string   `json:\"email\"`\n\tEmailVerified     bool     `json:\"emailVerified\"`\n\tGroups            []string `json:\"groups,omitempty\"`\n}\n\nfunc fromStorageClaims(i storage.Claims) Claims {\n\treturn Claims{\n\t\tUserID:            i.UserID,\n\t\tUsername:          i.Username,\n\t\tPreferredUsername: i.PreferredUsername,\n\t\tEmail:             i.Email,\n\t\tEmailVerified:     i.EmailVerified,\n\t\tGroups:            i.Groups,\n\t}\n}\n\nfunc toStorageClaims(i Claims) storage.Claims {\n\treturn storage.Claims{\n\t\tUserID:            i.UserID,\n\t\tUsername:          i.Username,\n\t\tPreferredUsername: i.PreferredUsername,\n\t\tEmail:             i.Email,\n\t\tEmailVerified:     i.EmailVerified,\n\t\tGroups:            i.Groups,\n\t}\n}\n\n// Keys is a mirrored struct from storage with JSON struct tags\ntype Keys struct {\n\tSigningKey       *jose.JSONWebKey          `json:\"signing_key,omitempty\"`\n\tSigningKeyPub    *jose.JSONWebKey          `json:\"signing_key_pub,omitempty\"`\n\tVerificationKeys []storage.VerificationKey `json:\"verification_keys\"`\n\tNextRotation     time.Time                 `json:\"next_rotation\"`\n}\n\n// OfflineSessions is a mirrored struct from storage with JSON struct tags\ntype OfflineSessions struct {\n\tUserID        string                              `json:\"user_id,omitempty\"`\n\tConnID        string                              `json:\"conn_id,omitempty\"`\n\tRefresh       map[string]*storage.RefreshTokenRef `json:\"refresh,omitempty\"`\n\tConnectorData []byte                              `json:\"connectorData,omitempty\"`\n}\n\nfunc fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions {\n\treturn OfflineSessions{\n\t\tUserID:        o.UserID,\n\t\tConnID:        o.ConnID,\n\t\tRefresh:       o.Refresh,\n\t\tConnectorData: o.ConnectorData,\n\t}\n}\n\nfunc toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions {\n\ts := storage.OfflineSessions{\n\t\tUserID:        o.UserID,\n\t\tConnID:        o.ConnID,\n\t\tRefresh:       o.Refresh,\n\t\tConnectorData: o.ConnectorData,\n\t}\n\tif s.Refresh == nil {\n\t\t// Server code assumes this will be non-nil.\n\t\ts.Refresh = make(map[string]*storage.RefreshTokenRef)\n\t}\n\treturn s\n}\n\n// UserIdentity is a mirrored struct from storage with JSON struct tags\ntype UserIdentity struct {\n\tUserID       string                        `json:\"user_id,omitempty\"`\n\tConnectorID  string                        `json:\"connector_id,omitempty\"`\n\tClaims       Claims                        `json:\"claims,omitempty\"`\n\tConsents     map[string][]string           `json:\"consents,omitempty\"`\n\tMFASecrets   map[string]*storage.MFASecret `json:\"mfa_secrets,omitempty\"`\n\tCreatedAt    time.Time                     `json:\"created_at\"`\n\tLastLogin    time.Time                     `json:\"last_login\"`\n\tBlockedUntil time.Time                     `json:\"blocked_until\"`\n}\n\nfunc fromStorageUserIdentity(u storage.UserIdentity) UserIdentity {\n\treturn UserIdentity{\n\t\tUserID:       u.UserID,\n\t\tConnectorID:  u.ConnectorID,\n\t\tClaims:       fromStorageClaims(u.Claims),\n\t\tConsents:     u.Consents,\n\t\tMFASecrets:   u.MFASecrets,\n\t\tCreatedAt:    u.CreatedAt,\n\t\tLastLogin:    u.LastLogin,\n\t\tBlockedUntil: u.BlockedUntil,\n\t}\n}\n\nfunc toStorageUserIdentity(u UserIdentity) storage.UserIdentity {\n\ts := storage.UserIdentity{\n\t\tUserID:       u.UserID,\n\t\tConnectorID:  u.ConnectorID,\n\t\tClaims:       toStorageClaims(u.Claims),\n\t\tConsents:     u.Consents,\n\t\tMFASecrets:   u.MFASecrets,\n\t\tCreatedAt:    u.CreatedAt,\n\t\tLastLogin:    u.LastLogin,\n\t\tBlockedUntil: u.BlockedUntil,\n\t}\n\tif s.Consents == nil {\n\t\t// Server code assumes this will be non-nil.\n\t\ts.Consents = make(map[string][]string)\n\t}\n\treturn s\n}\n\n// AuthSession is a mirrored struct from storage with JSON struct tags.\ntype AuthSession struct {\n\tUserID         string                              `json:\"user_id,omitempty\"`\n\tConnectorID    string                              `json:\"connector_id,omitempty\"`\n\tNonce          string                              `json:\"nonce,omitempty\"`\n\tClientStates   map[string]*storage.ClientAuthState `json:\"client_states,omitempty\"`\n\tCreatedAt      time.Time                           `json:\"created_at\"`\n\tLastActivity   time.Time                           `json:\"last_activity\"`\n\tIPAddress      string                              `json:\"ip_address,omitempty\"`\n\tUserAgent      string                              `json:\"user_agent,omitempty\"`\n\tAbsoluteExpiry time.Time                           `json:\"absolute_expiry\"`\n\tIdleExpiry     time.Time                           `json:\"idle_expiry\"`\n}\n\nfunc fromStorageAuthSession(s storage.AuthSession) AuthSession {\n\treturn AuthSession{\n\t\tUserID:         s.UserID,\n\t\tConnectorID:    s.ConnectorID,\n\t\tNonce:          s.Nonce,\n\t\tClientStates:   s.ClientStates,\n\t\tCreatedAt:      s.CreatedAt,\n\t\tLastActivity:   s.LastActivity,\n\t\tIPAddress:      s.IPAddress,\n\t\tUserAgent:      s.UserAgent,\n\t\tAbsoluteExpiry: s.AbsoluteExpiry,\n\t\tIdleExpiry:     s.IdleExpiry,\n\t}\n}\n\nfunc toStorageAuthSession(s AuthSession) storage.AuthSession {\n\tresult := storage.AuthSession{\n\t\tUserID:         s.UserID,\n\t\tConnectorID:    s.ConnectorID,\n\t\tNonce:          s.Nonce,\n\t\tClientStates:   s.ClientStates,\n\t\tCreatedAt:      s.CreatedAt,\n\t\tLastActivity:   s.LastActivity,\n\t\tIPAddress:      s.IPAddress,\n\t\tUserAgent:      s.UserAgent,\n\t\tAbsoluteExpiry: s.AbsoluteExpiry,\n\t\tIdleExpiry:     s.IdleExpiry,\n\t}\n\tif result.ClientStates == nil {\n\t\tresult.ClientStates = make(map[string]*storage.ClientAuthState)\n\t}\n\treturn result\n}\n\n// DeviceRequest is a mirrored struct from storage with JSON struct tags\ntype DeviceRequest struct {\n\tUserCode     string    `json:\"user_code\"`\n\tDeviceCode   string    `json:\"device_code\"`\n\tClientID     string    `json:\"client_id\"`\n\tClientSecret string    `json:\"client_secret\"`\n\tScopes       []string  `json:\"scopes\"`\n\tExpiry       time.Time `json:\"expiry\"`\n}\n\nfunc fromStorageDeviceRequest(d storage.DeviceRequest) DeviceRequest {\n\treturn DeviceRequest{\n\t\tUserCode:     d.UserCode,\n\t\tDeviceCode:   d.DeviceCode,\n\t\tClientID:     d.ClientID,\n\t\tClientSecret: d.ClientSecret,\n\t\tScopes:       d.Scopes,\n\t\tExpiry:       d.Expiry,\n\t}\n}\n\nfunc toStorageDeviceRequest(d DeviceRequest) storage.DeviceRequest {\n\treturn storage.DeviceRequest{\n\t\tUserCode:     d.UserCode,\n\t\tDeviceCode:   d.DeviceCode,\n\t\tClientID:     d.ClientID,\n\t\tClientSecret: d.ClientSecret,\n\t\tScopes:       d.Scopes,\n\t\tExpiry:       d.Expiry,\n\t}\n}\n\n// DeviceToken is a mirrored struct from storage with JSON struct tags\ntype DeviceToken struct {\n\tDeviceCode          string    `json:\"device_code\"`\n\tStatus              string    `json:\"status\"`\n\tToken               string    `json:\"token\"`\n\tExpiry              time.Time `json:\"expiry\"`\n\tLastRequestTime     time.Time `json:\"last_request\"`\n\tPollIntervalSeconds int       `json:\"poll_interval\"`\n\tCodeChallenge       string    `json:\"code_challenge,omitempty\"`\n\tCodeChallengeMethod string    `json:\"code_challenge_method,omitempty\"`\n}\n\nfunc fromStorageDeviceToken(t storage.DeviceToken) DeviceToken {\n\treturn DeviceToken{\n\t\tDeviceCode:          t.DeviceCode,\n\t\tStatus:              t.Status,\n\t\tToken:               t.Token,\n\t\tExpiry:              t.Expiry,\n\t\tLastRequestTime:     t.LastRequestTime,\n\t\tPollIntervalSeconds: t.PollIntervalSeconds,\n\t\tCodeChallenge:       t.PKCE.CodeChallenge,\n\t\tCodeChallengeMethod: t.PKCE.CodeChallengeMethod,\n\t}\n}\n\nfunc toStorageDeviceToken(t DeviceToken) storage.DeviceToken {\n\treturn storage.DeviceToken{\n\t\tDeviceCode:          t.DeviceCode,\n\t\tStatus:              t.Status,\n\t\tToken:               t.Token,\n\t\tExpiry:              t.Expiry,\n\t\tLastRequestTime:     t.LastRequestTime,\n\t\tPollIntervalSeconds: t.PollIntervalSeconds,\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       t.CodeChallenge,\n\t\t\tCodeChallengeMethod: t.CodeChallengeMethod,\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "storage/health.go",
    "content": "package storage\n\nimport (\n\t\"context\"\n\t\"crypto\"\n\t\"fmt\"\n\t\"time\"\n)\n\n// NewCustomHealthCheckFunc returns a new health check function.\nfunc NewCustomHealthCheckFunc(s Storage, now func() time.Time) func(context.Context) (details interface{}, err error) {\n\treturn func(ctx context.Context) (details interface{}, err error) {\n\t\ta := AuthRequest{\n\t\t\tID:       NewID(),\n\t\t\tClientID: NewID(),\n\n\t\t\t// Set a short expiry so if the delete fails this will be cleaned up quickly by garbage collection.\n\t\t\tExpiry:  now().Add(time.Minute),\n\t\t\tHMACKey: NewHMACKey(crypto.SHA256),\n\t\t}\n\n\t\tif err := s.CreateAuthRequest(ctx, a); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"create auth request: %v\", err)\n\t\t}\n\n\t\tif err := s.DeleteAuthRequest(ctx, a.ID); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"delete auth request: %v\", err)\n\t\t}\n\n\t\treturn nil, nil\n\t}\n}\n"
  },
  {
    "path": "storage/kubernetes/client.go",
    "content": "package kubernetes\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/base32\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"hash\"\n\t\"hash/fnv\"\n\t\"io\"\n\t\"log/slog\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Masterminds/semver\"\n\t\"github.com/ghodss/yaml\"\n\t\"golang.org/x/net/http2\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/kubernetes/k8sapi\"\n)\n\nconst (\n\tserviceAccountPath          = \"/var/run/secrets/kubernetes.io/serviceaccount/\"\n\tserviceAccountTokenPath     = serviceAccountPath + \"token\"\n\tserviceAccountCAPath        = serviceAccountPath + \"ca.crt\"\n\tserviceAccountNamespacePath = serviceAccountPath + \"namespace\"\n\n\tkubernetesServiceHostENV  = \"KUBERNETES_SERVICE_HOST\"\n\tkubernetesServicePortENV  = \"KUBERNETES_SERVICE_PORT\"\n\tkubernetesPodNamespaceENV = \"KUBERNETES_POD_NAMESPACE\"\n)\n\ntype client struct {\n\tclient    *http.Client\n\tbaseURL   string\n\tnamespace string\n\tlogger    *slog.Logger\n\n\t// Hash function to map IDs (which could span a large range) to Kubernetes names.\n\t// While this is not currently upgradable, it could be in the future.\n\t//\n\t// The default hash is a non-cryptographic hash, because cryptographic hashes\n\t// always produce sums too long to fit into a Kubernetes name. Because of this,\n\t// gets, updates, and deletes are _always_ checked for collisions.\n\thash func() hash.Hash\n\n\t// API version of the oidc resources. For example \"oidc.coreos.com\". This is\n\t// currently not configurable, but could be in the future.\n\tapiVersion string\n\t// API version of the custom resource definitions.\n\t// Different Kubernetes version requires to create CRD in certain API. It will be discovered automatically on\n\t// storage opening.\n\tcrdAPIVersion string\n\n\t// CRD handling behavior controls how missing Custom Resource Definitions are handled:\n\t// - \"ensure\": Attempt to create all missing CRDs. Fails if any CRD creation fails. (default)\n\t// - \"check\": Fail if any CRDs are missing, with error \"storage is not initialized, CRDs are not created\"\n\tcrdHandling string\n\n\t// This is called once the client's Close method is called to signal goroutines,\n\t// such as the one creating third party resources, to stop.\n\tcancel context.CancelFunc\n}\n\n// idToName maps an arbitrary ID, such as an email or client ID to a Kubernetes object name.\nfunc (cli *client) idToName(s string) string {\n\treturn idToName(s, cli.hash)\n}\n\n// offlineTokenName maps two arbitrary IDs, to a single Kubernetes object name.\n// This is used when more than one field is used to uniquely identify the object.\nfunc (cli *client) offlineTokenName(userID string, connID string) string {\n\treturn offlineTokenName(userID, connID, cli.hash)\n}\n\n// Kubernetes names must match the regexp '[a-z0-9]([-a-z0-9]*[a-z0-9])?'.\nvar encoding = base32.NewEncoding(\"abcdefghijklmnopqrstuvwxyz234567\")\n\nfunc idToName(s string, h func() hash.Hash) string {\n\treturn strings.TrimRight(encoding.EncodeToString(h().Sum([]byte(s))), \"=\")\n}\n\nfunc offlineTokenName(userID string, connID string, h func() hash.Hash) string {\n\thash := h()\n\thash.Write([]byte(userID))\n\thash.Write([]byte(connID))\n\treturn strings.TrimRight(encoding.EncodeToString(hash.Sum(nil)), \"=\")\n}\n\n// https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names\nconst kubeResourceMaxLen = 253\n\nvar kubeResourceNameRegex = regexp.MustCompile(`^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$`)\n\nfunc (cli *client) urlForWithParams(\n\tapiVersion, namespace, resource, name string, params url.Values,\n) (string, error) {\n\tbasePath := \"apis/\"\n\tif apiVersion == \"v1\" {\n\t\tbasePath = \"api/\"\n\t}\n\n\tif name != \"\" && (len(name) > kubeResourceMaxLen || !kubeResourceNameRegex.MatchString(name)) {\n\t\t// The actual name can be found in auth request or auth code objects and equals to the state value\n\t\treturn \"\", fmt.Errorf(\n\t\t\t\"invalid kubernetes resource name: must match the pattern %s and be no longer than %d characters\",\n\t\t\tkubeResourceNameRegex.String(),\n\t\t\tkubeResourceMaxLen)\n\t}\n\n\tvar p string\n\tif namespace != \"\" {\n\t\tp = path.Join(basePath, apiVersion, \"namespaces\", namespace, resource, name)\n\t} else {\n\t\tp = path.Join(basePath, apiVersion, resource, name)\n\t}\n\n\tencodedParams := params.Encode()\n\tparamsSuffix := \"\"\n\tif len(encodedParams) > 0 {\n\t\tparamsSuffix = \"?\" + encodedParams\n\t}\n\n\tif strings.HasSuffix(cli.baseURL, \"/\") {\n\t\treturn cli.baseURL + p + paramsSuffix, nil\n\t}\n\n\treturn cli.baseURL + \"/\" + p + paramsSuffix, nil\n}\n\nfunc (cli *client) urlFor(apiVersion, namespace, resource, name string) (string, error) {\n\treturn cli.urlForWithParams(apiVersion, namespace, resource, name, url.Values{})\n}\n\n// Define an error interface so we can get at the underlying status code if it's\n// absolutely necessary. For instance when we need to see if an error indicates\n// a resource already exists.\ntype httpError interface {\n\tStatusCode() int\n}\n\nvar _ httpError = (*httpErr)(nil)\n\ntype httpErr struct {\n\tmethod string\n\turl    string\n\tstatus int\n\tbody   []byte\n}\n\nfunc (e *httpErr) StatusCode() int {\n\treturn e.status\n}\n\nfunc (e *httpErr) Error() string {\n\treturn fmt.Sprintf(\"%s %s %s: response from server \\\"%s\\\"\", e.method, e.url, http.StatusText(e.status), bytes.TrimSpace(e.body))\n}\n\nfunc checkHTTPErr(r *http.Response, validStatusCodes ...int) error {\n\tfor _, status := range validStatusCodes {\n\t\tif r.StatusCode == status {\n\t\t\treturn nil\n\t\t}\n\t}\n\n\tbody, err := io.ReadAll(io.LimitReader(r.Body, 2<<15)) // 64 KiB\n\tif err != nil {\n\t\treturn fmt.Errorf(\"read response body: %v\", err)\n\t}\n\n\t// Check this case after we read the body so the connection can be reused.\n\tif r.StatusCode == http.StatusNotFound {\n\t\treturn storage.ErrNotFound\n\t}\n\tif r.Request.Method == http.MethodPost && r.StatusCode == http.StatusConflict {\n\t\treturn storage.ErrAlreadyExists\n\t}\n\n\tvar url, method string\n\tif r.Request != nil {\n\t\tmethod = r.Request.Method\n\t\turl = r.Request.URL.String()\n\t}\n\treturn &httpErr{method, url, r.StatusCode, body}\n}\n\n// Close the response body. The initial request is drained so the connection can\n// be reused.\nfunc closeResp(r *http.Response) {\n\tio.Copy(io.Discard, r.Body)\n\tr.Body.Close()\n}\n\nfunc (cli *client) get(resource, name string, v interface{}) error {\n\treturn cli.getResource(cli.apiVersion, cli.namespace, resource, name, v)\n}\n\nfunc (cli *client) getURL(url string, v interface{}) error {\n\tresp, err := cli.client.Get(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer closeResp(resp)\n\tif err := checkHTTPErr(resp, http.StatusOK); err != nil {\n\t\treturn err\n\t}\n\treturn json.NewDecoder(resp.Body).Decode(v)\n}\n\nfunc (cli *client) getResource(apiVersion, namespace, resource, name string, v interface{}) error {\n\tu, err := cli.urlFor(apiVersion, namespace, resource, name)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.getURL(u, v)\n}\n\nfunc (cli *client) listN(resource string, v interface{}, n int) error { //nolint:unparam // In practice, n is the gcResultLimit constant.\n\tparams := url.Values{}\n\tparams.Add(\"limit\", fmt.Sprintf(\"%d\", n))\n\tu, err := cli.urlForWithParams(cli.apiVersion, cli.namespace, resource, \"\", params)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.getURL(u, v)\n}\n\nfunc (cli *client) list(resource string, v interface{}) error {\n\treturn cli.get(resource, \"\", v)\n}\n\nfunc (cli *client) post(resource string, v interface{}) error {\n\treturn cli.postResource(cli.apiVersion, cli.namespace, resource, v)\n}\n\nfunc (cli *client) postResource(apiVersion, namespace, resource string, v interface{}) error {\n\tbody, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal object: %v\", err)\n\t}\n\n\turl, err := cli.urlFor(apiVersion, namespace, resource, \"\")\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp, err := cli.client.Post(url, \"application/json\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer closeResp(resp)\n\treturn checkHTTPErr(resp, http.StatusCreated)\n}\n\nfunc (cli *client) detectKubernetesVersion() error {\n\tvar version struct{ GitVersion string }\n\n\turl := cli.baseURL + \"/version\"\n\tresp, err := cli.client.Get(url)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tdefer closeResp(resp)\n\tif err := checkHTTPErr(resp, http.StatusOK); err != nil {\n\t\treturn err\n\t}\n\n\tif err := json.NewDecoder(resp.Body).Decode(&version); err != nil {\n\t\treturn err\n\t}\n\n\tclusterVersion, err := semver.NewVersion(version.GitVersion)\n\tif err != nil {\n\t\tcli.logger.Warn(\"cannot detect Kubernetes version\", \"version\", clusterVersion, \"err\", err)\n\t\treturn nil\n\t}\n\n\tif clusterVersion.LessThan(semver.MustParse(\"v1.16.0\")) {\n\t\tcli.crdAPIVersion = legacyCRDAPIVersion\n\t}\n\n\treturn nil\n}\n\nfunc (cli *client) delete(resource, name string) error {\n\turl, err := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq, err := http.NewRequest(\"DELETE\", url, nil)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create delete request: %v\", err)\n\t}\n\tresp, err := cli.client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"delete request: %v\", err)\n\t}\n\tdefer closeResp(resp)\n\treturn checkHTTPErr(resp, http.StatusOK)\n}\n\nfunc (cli *client) deleteAll(resource string) error {\n\tvar list struct {\n\t\tk8sapi.TypeMeta `json:\",inline\"`\n\t\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\t\tItems           []struct {\n\t\t\tk8sapi.TypeMeta   `json:\",inline\"`\n\t\t\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\t\t} `json:\"items\"`\n\t}\n\tif err := cli.list(resource, &list); err != nil {\n\t\treturn err\n\t}\n\tfor _, item := range list.Items {\n\t\tif err := cli.delete(resource, item.Name); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (cli *client) put(resource, name string, v interface{}) error {\n\tbody, err := json.Marshal(v)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal object: %v\", err)\n\t}\n\n\turl, err := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treq, err := http.NewRequest(\"PUT\", url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"create patch request: %v\", err)\n\t}\n\n\treq.Header.Set(\"Content-Length\", strconv.Itoa(len(body)))\n\n\tresp, err := cli.client.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"patch request: %v\", err)\n\t}\n\tdefer closeResp(resp)\n\n\treturn checkHTTPErr(resp, http.StatusOK)\n}\n\n// Copied from https://github.com/gtank/cryptopasta\nfunc defaultTLSConfig() *tls.Config {\n\treturn &tls.Config{\n\t\t// Avoids most of the memorably-named TLS attacks\n\t\tMinVersion: tls.VersionTLS12,\n\t\t// Causes servers to use Go's default ciphersuite preferences,\n\t\t// which are tuned to avoid attacks. Does nothing on clients.\n\t\tPreferServerCipherSuites: true,\n\t\t// Only use curves which have constant-time implementations\n\t\tCurvePreferences: []tls.CurveID{\n\t\t\ttls.CurveP256,\n\t\t},\n\t}\n}\n\nfunc newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger *slog.Logger, inCluster bool, crdHandling string) (*client, error) {\n\ttlsConfig := defaultTLSConfig()\n\tdata := func(b string, file string) ([]byte, error) {\n\t\tif b != \"\" {\n\t\t\treturn base64.StdEncoding.DecodeString(b)\n\t\t}\n\t\tif file == \"\" {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn os.ReadFile(file)\n\t}\n\n\tif caData, err := data(cluster.CertificateAuthorityData, cluster.CertificateAuthority); err != nil {\n\t\treturn nil, err\n\t} else if caData != nil {\n\t\ttlsConfig.RootCAs = x509.NewCertPool()\n\t\tif !tlsConfig.RootCAs.AppendCertsFromPEM(caData) {\n\t\t\treturn nil, fmt.Errorf(\"no certificate data found: %v\", err)\n\t\t}\n\t}\n\n\tclientCert, err := data(user.ClientCertificateData, user.ClientCertificate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tclientKey, err := data(user.ClientKeyData, user.ClientKey)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif clientCert != nil && clientKey != nil {\n\t\tcert, err := tls.X509KeyPair(clientCert, clientKey)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to load client cert: %v\", err)\n\t\t}\n\t\ttlsConfig.Certificates = []tls.Certificate{cert}\n\t}\n\n\tvar t http.RoundTripper\n\thttpTransport := &http.Transport{\n\t\tProxy: http.ProxyFromEnvironment,\n\t\tDialContext: (&net.Dialer{\n\t\t\tTimeout:   30 * time.Second,\n\t\t\tKeepAlive: 30 * time.Second,\n\t\t}).DialContext,\n\t\tTLSClientConfig:       tlsConfig,\n\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\tExpectContinueTimeout: 1 * time.Second,\n\t}\n\n\t// Since we set a custom TLS client config we have to explicitly\n\t// enable HTTP/2.\n\t//\n\t// https://github.com/golang/go/blob/go1.7.4/src/net/http/transport.go#L200-L206\n\tif err := http2.ConfigureTransport(httpTransport); err != nil {\n\t\treturn nil, err\n\t}\n\tt = wrapRoundTripper(httpTransport, user, inCluster)\n\n\tapiVersion := \"dex.coreos.com/v1\"\n\n\tlogger.Info(\"kubernetes client\", \"api_version\", apiVersion)\n\treturn &client{\n\t\tclient: &http.Client{\n\t\t\tTransport: t,\n\t\t\tTimeout:   15 * time.Second,\n\t\t},\n\t\tbaseURL:       cluster.Server,\n\t\thash:          func() hash.Hash { return fnv.New64() },\n\t\tnamespace:     namespace,\n\t\tapiVersion:    apiVersion,\n\t\tcrdAPIVersion: crdAPIVersion,\n\t\tcrdHandling:   crdHandling,\n\t\tlogger:        logger,\n\t}, nil\n}\n\nfunc loadKubeConfig(kubeConfigPath string) (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) {\n\tdata, err := os.ReadFile(kubeConfigPath)\n\tif err != nil {\n\t\terr = fmt.Errorf(\"read %s: %v\", kubeConfigPath, err)\n\t\treturn\n\t}\n\n\tvar c k8sapi.Config\n\tif err = yaml.Unmarshal(data, &c); err != nil {\n\t\terr = fmt.Errorf(\"unmarshal %s: %v\", kubeConfigPath, err)\n\t\treturn\n\t}\n\n\tcluster, user, namespace, err = currentContext(&c)\n\tif namespace == \"\" {\n\t\tnamespace = \"default\"\n\t}\n\treturn\n}\n\nfunc namespaceFromServiceAccountJWT(s string) (string, error) {\n\t// The service account token is just a JWT. Parse it as such.\n\tparts := strings.Split(s, \".\")\n\tif len(parts) < 2 {\n\t\t// It's extremely important we don't log the actual service account token.\n\t\treturn \"\", fmt.Errorf(\"malformed service account token: expected 3 parts got %d\", len(parts))\n\t}\n\tpayload, err := base64.RawURLEncoding.DecodeString(parts[1])\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"malformed service account token: %v\", err)\n\t}\n\tvar data struct {\n\t\t// The claim Kubernetes uses to identify which namespace a service account belongs to.\n\t\t//\n\t\t// See: https://github.com/kubernetes/kubernetes/blob/v1.4.3/pkg/serviceaccount/jwt.go#L42\n\t\tNamespace string `json:\"kubernetes.io/serviceaccount/namespace\"`\n\t}\n\tif err := json.Unmarshal(payload, &data); err != nil {\n\t\treturn \"\", fmt.Errorf(\"malformed service account token: %v\", err)\n\t}\n\tif data.Namespace == \"\" {\n\t\treturn \"\", errors.New(`jwt claim \"kubernetes.io/serviceaccount/namespace\" not found`)\n\t}\n\treturn data.Namespace, nil\n}\n\nfunc namespaceFromFile(path string) (string, error) {\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(data), nil\n}\n\nfunc getInClusterConfigNamespace(token, namespaceENV, namespacePath string) (string, error) {\n\tnamespace := os.Getenv(namespaceENV)\n\tif namespace != \"\" {\n\t\treturn namespace, nil\n\t}\n\n\tnamespace, err := namespaceFromServiceAccountJWT(token)\n\tif err == nil {\n\t\treturn namespace, nil\n\t}\n\n\terr = fmt.Errorf(\"inspect service account token: %v\", err)\n\tnamespace, fileErr := namespaceFromFile(namespacePath)\n\tif fileErr == nil {\n\t\treturn namespace, nil\n\t}\n\n\treturn \"\", fmt.Errorf(\"%v: trying to get namespace from file: %v\", err, fileErr)\n}\n\nfunc getInClusterConnectOptions(host, port string) (k8sapi.Cluster, error) {\n\tif len(host) == 0 || len(port) == 0 {\n\t\treturn k8sapi.Cluster{}, fmt.Errorf(\n\t\t\t\"unable to load in-cluster configuration, %s and %s must be defined\",\n\t\t\tkubernetesServiceHostENV,\n\t\t\tkubernetesServicePortENV,\n\t\t)\n\t}\n\n\tcluster := k8sapi.Cluster{\n\t\tServer:               \"https://\" + net.JoinHostPort(host, port),\n\t\tCertificateAuthority: serviceAccountCAPath,\n\t}\n\treturn cluster, nil\n}\n\nfunc inClusterConfig() (k8sapi.Cluster, k8sapi.AuthInfo, string, error) {\n\tcluster, err := getInClusterConnectOptions(os.Getenv(kubernetesServiceHostENV), os.Getenv(kubernetesServicePortENV))\n\tif err != nil {\n\t\treturn cluster, k8sapi.AuthInfo{}, \"\", err\n\t}\n\n\ttoken, err := os.ReadFile(serviceAccountTokenPath)\n\tif err != nil {\n\t\treturn cluster, k8sapi.AuthInfo{}, \"\", err\n\t}\n\n\tuser := k8sapi.AuthInfo{Token: string(token)}\n\n\tnamespace, err := getInClusterConfigNamespace(user.Token, kubernetesPodNamespaceENV, serviceAccountNamespacePath)\n\tif err != nil {\n\t\treturn cluster, user, \"\", err\n\t}\n\n\treturn cluster, user, namespace, nil\n}\n\nfunc currentContext(config *k8sapi.Config) (cluster k8sapi.Cluster, user k8sapi.AuthInfo, ns string, err error) {\n\tif config.CurrentContext == \"\" {\n\t\tif len(config.Contexts) == 1 {\n\t\t\tconfig.CurrentContext = config.Contexts[0].Name\n\t\t} else {\n\t\t\treturn cluster, user, \"\", errors.New(\"kubeconfig has no current context\")\n\t\t}\n\t}\n\tk8sContext, ok := func() (k8sapi.Context, bool) {\n\t\tfor _, namedContext := range config.Contexts {\n\t\t\tif namedContext.Name == config.CurrentContext {\n\t\t\t\treturn namedContext.Context, true\n\t\t\t}\n\t\t}\n\t\treturn k8sapi.Context{}, false\n\t}()\n\tif !ok {\n\t\treturn cluster, user, \"\", fmt.Errorf(\"no context named %q found\", config.CurrentContext)\n\t}\n\n\tcluster, ok = func() (k8sapi.Cluster, bool) {\n\t\tfor _, namedCluster := range config.Clusters {\n\t\t\tif namedCluster.Name == k8sContext.Cluster {\n\t\t\t\treturn namedCluster.Cluster, true\n\t\t\t}\n\t\t}\n\t\treturn k8sapi.Cluster{}, false\n\t}()\n\tif !ok {\n\t\treturn cluster, user, \"\", fmt.Errorf(\"no cluster named %q found\", k8sContext.Cluster)\n\t}\n\n\tuser, ok = func() (k8sapi.AuthInfo, bool) {\n\t\tfor _, namedAuthInfo := range config.AuthInfos {\n\t\t\tif namedAuthInfo.Name == k8sContext.AuthInfo {\n\t\t\t\treturn namedAuthInfo.AuthInfo, true\n\t\t\t}\n\t\t}\n\t\treturn k8sapi.AuthInfo{}, false\n\t}()\n\tif !ok {\n\t\treturn cluster, user, \"\", fmt.Errorf(\"no user named %q found\", k8sContext.AuthInfo)\n\t}\n\treturn cluster, user, k8sContext.Namespace, nil\n}\n"
  },
  {
    "path": "storage/kubernetes/client_test.go",
    "content": "package kubernetes\n\nimport (\n\t\"hash\"\n\t\"hash/fnv\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/dexidp/dex/storage/kubernetes/k8sapi\"\n)\n\n// This test does not have an explicit error condition but is used\n// with the race detector to detect the safety of idToName.\nfunc TestIDToName(t *testing.T) {\n\tn := 100\n\tvar wg sync.WaitGroup\n\twg.Add(n)\n\tc := make(chan struct{})\n\n\th := func() hash.Hash { return fnv.New64() }\n\n\tfor i := 0; i < n; i++ {\n\t\tgo func() {\n\t\t\t<-c\n\t\t\tname := idToName(\"foo\", h)\n\t\t\t_ = name\n\t\t\twg.Done()\n\t\t}()\n\t}\n\tclose(c)\n\twg.Wait()\n}\n\nfunc TestOfflineTokenName(t *testing.T) {\n\th := func() hash.Hash { return fnv.New64() }\n\n\tuserID1 := \"john\"\n\tuserID2 := \"jane\"\n\n\tid1 := offlineTokenName(userID1, \"local\", h)\n\tid2 := offlineTokenName(userID2, \"local\", h)\n\tif id1 == id2 {\n\t\tt.Errorf(\"expected offlineTokenName to produce different hashes\")\n\t}\n}\n\nfunc TestInClusterTransport(t *testing.T) {\n\tlogger := slog.New(slog.DiscardHandler)\n\n\tuser := k8sapi.AuthInfo{Token: \"abc\"}\n\tcli, err := newClient(\n\t\tk8sapi.Cluster{},\n\t\tuser,\n\t\t\"test\",\n\t\tlogger,\n\t\ttrue,\n\t\t\"\",\n\t)\n\trequire.NoError(t, err)\n\n\tfpath := filepath.Join(os.TempDir(), \"test.in_cluster\")\n\tdefer os.RemoveAll(fpath)\n\n\terr = os.WriteFile(fpath, []byte(\"def\"), 0o644)\n\trequire.NoError(t, err)\n\n\ttests := []struct {\n\t\tname     string\n\t\ttime     func() time.Time\n\t\texpected string\n\t}{\n\t\t{\n\t\t\tname: \"Stale token\",\n\t\t\ttime: func() time.Time {\n\t\t\t\treturn time.Now().Add(-24 * time.Hour)\n\t\t\t},\n\t\t\texpected: \"def\",\n\t\t},\n\t\t{\n\t\t\tname: \"Normal token\",\n\t\t\ttime: func() time.Time {\n\t\t\t\treturn time.Time{}\n\t\t\t},\n\t\t\texpected: \"abc\",\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\thelper := newInClusterTransportHelper(user)\n\t\t\thelper.now = tc.time\n\t\t\thelper.tokenLocation = fpath\n\n\t\t\tcli.client.Transport = transport{\n\t\t\t\tupdateReq: func(r *http.Request) {\n\t\t\t\t\thelper.UpdateToken()\n\t\t\t\t\tr.Header.Set(\"Authorization\", \"Bearer \"+helper.GetToken())\n\t\t\t\t},\n\t\t\t\tbase: cli.client.Transport,\n\t\t\t}\n\n\t\t\t_ = cli.isCRDReady(\"test\")\n\t\t\trequire.Equal(t, tc.expected, helper.info.Token)\n\t\t})\n\t}\n}\n\nfunc TestNamespaceFromServiceAccountJWT(t *testing.T) {\n\tnamespace, err := namespaceFromServiceAccountJWT(serviceAccountToken)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\twantNamespace := \"dex-test-namespace\"\n\tif namespace != wantNamespace {\n\t\tt.Errorf(\"expected namespace %q got %q\", wantNamespace, namespace)\n\t}\n}\n\nfunc TestGetClusterConfigNamespace(t *testing.T) {\n\tconst namespaceENVVariableName = \"TEST_GET_CLUSTER_CONFIG_NAMESPACE\"\n\t{\n\t\tos.Setenv(namespaceENVVariableName, \"namespace-from-env\")\n\t\tdefer os.Unsetenv(namespaceENVVariableName)\n\t}\n\n\tvar namespaceFile string\n\t{\n\t\ttmpfile, err := os.CreateTemp(os.TempDir(), \"test-get-cluster-config-namespace\")\n\t\trequire.NoError(t, err)\n\n\t\t_, err = tmpfile.Write([]byte(\"namespace-from-file\"))\n\t\trequire.NoError(t, err)\n\n\t\tnamespaceFile = tmpfile.Name()\n\t\tdefer os.Remove(namespaceFile)\n\t}\n\n\ttests := []struct {\n\t\tname        string\n\t\ttoken       string\n\t\tfileName    string\n\t\tenvVariable string\n\n\t\texpectedError     bool\n\t\texpectedNamespace string\n\t}{\n\t\t{\n\t\t\tname:        \"With env variable\",\n\t\t\tenvVariable: \"TEST_GET_CLUSTER_CONFIG_NAMESPACE\",\n\n\t\t\texpectedNamespace: \"namespace-from-env\",\n\t\t},\n\t\t{\n\t\t\tname:  \"With token\",\n\t\t\ttoken: serviceAccountToken,\n\n\t\t\texpectedNamespace: \"dex-test-namespace\",\n\t\t},\n\t\t{\n\t\t\tname:     \"With namespace file\",\n\t\t\tfileName: namespaceFile,\n\n\t\t\texpectedNamespace: \"namespace-from-file\",\n\t\t},\n\t\t{\n\t\t\tname:     \"With file and token\",\n\t\t\tfileName: namespaceFile,\n\t\t\ttoken:    serviceAccountToken,\n\n\t\t\texpectedNamespace: \"dex-test-namespace\",\n\t\t},\n\t\t{\n\t\t\tname:        \"With file and env\",\n\t\t\tfileName:    namespaceFile,\n\t\t\tenvVariable: \"TEST_GET_CLUSTER_CONFIG_NAMESPACE\",\n\n\t\t\texpectedNamespace: \"namespace-from-env\",\n\t\t},\n\t\t{\n\t\t\tname:        \"With token and env\",\n\t\t\tenvVariable: \"TEST_GET_CLUSTER_CONFIG_NAMESPACE\",\n\t\t\ttoken:       serviceAccountToken,\n\n\t\t\texpectedNamespace: \"namespace-from-env\",\n\t\t},\n\t\t{\n\t\t\tname:        \"With file, token and env\",\n\t\t\tfileName:    namespaceFile,\n\t\t\ttoken:       serviceAccountToken,\n\t\t\tenvVariable: \"TEST_GET_CLUSTER_CONFIG_NAMESPACE\",\n\n\t\t\texpectedNamespace: \"namespace-from-env\",\n\t\t},\n\t\t{\n\t\t\tname:          \"Without anything\",\n\t\t\texpectedError: true,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tnamespace, err := getInClusterConfigNamespace(tc.token, tc.envVariable, tc.fileName)\n\t\t\tif tc.expectedError {\n\t\t\t\trequire.Error(t, err)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t\trequire.Equal(t, namespace, tc.expectedNamespace)\n\t\t})\n\t}\n}\n\nfunc TestGetInClusterConnectOptions(t *testing.T) {\n\ttype testCase struct {\n\t\tname        string\n\t\thost        string\n\t\tport        string\n\t\texpectedURL string\n\t\texpectError bool\n\t}\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tname:        \"valid IPv4\",\n\t\t\thost:        \"10.1.1.1\",\n\t\t\tport:        \"443\",\n\t\t\texpectedURL: \"https://10.1.1.1:443\",\n\t\t},\n\t\t{\n\t\t\tname:        \"valid IPv6\",\n\t\t\thost:        \"fd00::1\",\n\t\t\tport:        \"8443\",\n\t\t\texpectedURL: \"https://[fd00::1]:8443\",\n\t\t},\n\t\t{\n\t\t\tname:        \"valid DNS name\",\n\t\t\thost:        \"kubernetes.default.svc\",\n\t\t\tport:        \"443\",\n\t\t\texpectedURL: \"https://kubernetes.default.svc:443\",\n\t\t},\n\t\t{\n\t\t\tname:        \"empty host\",\n\t\t\thost:        \"\",\n\t\t\tport:        \"443\",\n\t\t\texpectError: true,\n\t\t},\n\t\t{\n\t\t\tname:        \"empty port\",\n\t\t\thost:        \"127.0.0.1\",\n\t\t\tport:        \"\",\n\t\t\texpectError: true,\n\t\t},\n\t}\n\n\tfor _, tc := range testCases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tcluster, err := getInClusterConnectOptions(tc.host, tc.port)\n\n\t\t\tif tc.expectError {\n\t\t\t\tassert.Error(t, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequire.NoError(t, err)\n\t\t\tassert.Equal(t, tc.expectedURL, cluster.Server)\n\t\t\tassert.Equal(t, \"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\", cluster.CertificateAuthority)\n\t\t})\n\t}\n}\n\nconst serviceAccountToken = \"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXgtdGVzdC1uYW1lc3BhY2UiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZG90aGVyb2JvdC1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZG90aGVyb2JvdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQyYjJhOTRmLTk4MjAtMTFlNi1iZDc0LTJlZmQzOGYxMjYxYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZXgtdGVzdC1uYW1lc3BhY2U6ZG90aGVyb2JvdCJ9.KViBpPwCiBwxDvAjYUUXoVvLVwqV011aLlYQpNtX12Bh8M-QAFch-3RWlo_SR00bcdFg_nZo9JKACYlF_jHMEsf__PaYms9r7vEaSg0jPfkqnL2WXZktzQRyLBr0n-bxeUrbwIWsKOAC0DfFB5nM8XoXljRmq8yAx8BAdmQp7MIFb4EOV9nYthhua6pjzYyaFSiDiYTjw7HtXOvoL8oepodJ3-37pUKS8vdBvnvUoqC4M1YAhkO5L36JF6KV_RfmG8GPEdNQfXotHcsR-3jKi1n8S5l7Xd-rhrGOhSGQizH3dORzo9GvBAhYeqbq1O-NLzm2EQUiMQayIUx7o4g3Kw\"\n\n// The following program was used to generate the example token. Since we don't want to\n// import Kubernetes, just leave it as a comment.\n\n/*\npackage main\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"fmt\"\n\t\"log\"\n\n\t\"k8s.io/kubernetes/pkg/api\"\n\t\"k8s.io/kubernetes/pkg/serviceaccount\"\n\t\"k8s.io/kubernetes/pkg/util/uuid\"\n)\n\nfunc main() {\n\tkey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tsa := api.ServiceAccount{\n\t\tObjectMeta: api.ObjectMeta{\n\t\t\tNamespace: \"dex-test-namespace\",\n\t\t\tName:      \"dotherobot\",\n\t\t\tUID:       uuid.NewUUID(),\n\t\t},\n\t}\n\tsecret := api.Secret{\n\t\tObjectMeta: api.ObjectMeta{\n\t\t\tNamespace: \"dex-test-namespace\",\n\t\t\tName:      \"dotherobot-secret\",\n\t\t\tUID:       uuid.NewUUID(),\n\t\t},\n\t}\n\ttoken, err := serviceaccount.JWTTokenGenerator(key).GenerateToken(sa, secret)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Println(token)\n}\n*/\n"
  },
  {
    "path": "storage/kubernetes/doc.go",
    "content": "// Package kubernetes provides a storage implementation using Kubernetes third party APIs.\npackage kubernetes\n"
  },
  {
    "path": "storage/kubernetes/k8sapi/client.go",
    "content": "/*\nCopyright 2014 The Kubernetes Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage k8sapi\n\n// Where possible, json tags match the cli argument names.\n// Top level config objects and all values required for proper functioning are not \"omitempty\".  Any truly optional piece of config is allowed to be omitted.\n\n// Config holds the information needed to build connect to remote kubernetes clusters as a given user.\ntype Config struct {\n\t// Legacy field from pkg/api/types.go TypeMeta.\n\t// TODO(jlowdermilk): remove this after eliminating downstream dependencies.\n\tKind string `json:\"kind,omitempty\"`\n\t// Deprecated: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).\n\t// Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify\n\t// a single value for the cluster version.\n\t// This field isn't really needed anyway, so we are deprecating it without replacement.\n\t// It will be ignored if it is present.\n\tAPIVersion string `json:\"apiVersion,omitempty\"`\n\t// Preferences holds general information to be use for cli interactions\n\tPreferences Preferences `json:\"preferences\"`\n\t// Clusters is a map of referenceable names to cluster configs\n\tClusters []NamedCluster `json:\"clusters\"`\n\t// AuthInfos is a map of referenceable names to user configs\n\tAuthInfos []NamedAuthInfo `json:\"users\"`\n\t// Contexts is a map of referenceable names to context configs\n\tContexts []NamedContext `json:\"contexts\"`\n\t// CurrentContext is the name of the context that you would like to use by default\n\tCurrentContext string `json:\"current-context\"`\n\t// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields\n\tExtensions []NamedExtension `json:\"extensions,omitempty\"`\n}\n\n// Preferences contains information about the users command line experience preferences.\ntype Preferences struct {\n\tColors bool `json:\"colors,omitempty\"`\n\t// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields\n\tExtensions []NamedExtension `json:\"extensions,omitempty\"`\n}\n\n// Cluster contains information about how to communicate with a kubernetes cluster.\ntype Cluster struct {\n\t// Server is the address of the kubernetes cluster (https://hostname:port).\n\tServer string `json:\"server\"`\n\t// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).\n\tAPIVersion string `json:\"api-version,omitempty\"`\n\t// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.\n\tInsecureSkipTLSVerify bool `json:\"insecure-skip-tls-verify,omitempty\"`\n\t// CertificateAuthority is the path to a cert file for the certificate authority.\n\tCertificateAuthority string `json:\"certificate-authority,omitempty\"`\n\t// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority\n\t//\n\t// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.\n\tCertificateAuthorityData string `json:\"certificate-authority-data,omitempty\"`\n\t// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields\n\tExtensions []NamedExtension `json:\"extensions,omitempty\"`\n}\n\n// AuthInfo contains information that describes identity information.  This is use to tell the kubernetes cluster who you are.\ntype AuthInfo struct {\n\t// ClientCertificate is the path to a client cert file for TLS.\n\tClientCertificate string `json:\"client-certificate,omitempty\"`\n\t// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate\n\t//\n\t// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.\n\tClientCertificateData string `json:\"client-certificate-data,omitempty\"`\n\t// ClientKey is the path to a client key file for TLS.\n\tClientKey string `json:\"client-key,omitempty\"`\n\t// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey\n\t//\n\t// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.\n\tClientKeyData string `json:\"client-key-data,omitempty\"`\n\t// Token is the bearer token for authentication to the kubernetes cluster.\n\tToken string `json:\"token,omitempty\"`\n\t// Impersonate is the username to impersonate.  The name matches the flag.\n\tImpersonate string `json:\"as,omitempty\"`\n\t// Username is the username for basic authentication to the kubernetes cluster.\n\tUsername string `json:\"username,omitempty\"`\n\t// Password is the password for basic authentication to the kubernetes cluster.\n\tPassword string `json:\"password,omitempty\"`\n\t// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.\n\tAuthProvider *AuthProviderConfig `json:\"auth-provider,omitempty\"`\n\t// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields\n\tExtensions []NamedExtension `json:\"extensions,omitempty\"`\n}\n\n// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster),\n// a user (how do I identify myself), and a namespace (what subset of resources do I want to work with).\ntype Context struct {\n\t// Cluster is the name of the cluster for this context\n\tCluster string `json:\"cluster\"`\n\t// AuthInfo is the name of the authInfo for this context\n\tAuthInfo string `json:\"user\"`\n\t// Namespace is the default namespace to use on unspecified requests\n\tNamespace string `json:\"namespace,omitempty\"`\n\t// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields\n\tExtensions []NamedExtension `json:\"extensions,omitempty\"`\n}\n\n// NamedCluster relates nicknames to cluster information\ntype NamedCluster struct {\n\t// Name is the nickname for this Cluster\n\tName string `json:\"name\"`\n\t// Cluster holds the cluster information\n\tCluster Cluster `json:\"cluster\"`\n}\n\n// NamedContext relates nicknames to context information\ntype NamedContext struct {\n\t// Name is the nickname for this Context\n\tName string `json:\"name\"`\n\t// Context holds the context information\n\tContext Context `json:\"context\"`\n}\n\n// NamedAuthInfo relates nicknames to auth information\ntype NamedAuthInfo struct {\n\t// Name is the nickname for this AuthInfo\n\tName string `json:\"name\"`\n\t// AuthInfo holds the auth information\n\tAuthInfo AuthInfo `json:\"user\"`\n}\n\n// NamedExtension relates nicknames to extension information\ntype NamedExtension struct {\n\t// Name is the nickname for this Extension\n\tName string `json:\"name\"`\n}\n\n// AuthProviderConfig holds the configuration for a specified auth provider.\ntype AuthProviderConfig struct {\n\tName   string            `json:\"name\"`\n\tConfig map[string]string `json:\"config\"`\n}\n"
  },
  {
    "path": "storage/kubernetes/k8sapi/crd_extensions.go",
    "content": "/*\nCopyright 2017 The Kubernetes Authors.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage k8sapi\n\n// CustomResourceDefinitionSpec describes how a user wants their resource to appear\ntype CustomResourceDefinitionSpec struct {\n\t// Group is the group this resource belongs in\n\tGroup string `json:\"group\" protobuf:\"bytes,1,opt,name=group\"`\n\t// Version is the version this resource belongs in\n\tVersion string `json:\"version\" protobuf:\"bytes,2,opt,name=version\"`\n\t// Names are the names used to describe this custom resource\n\tNames CustomResourceDefinitionNames `json:\"names\" protobuf:\"bytes,3,opt,name=names\"`\n\n\t// Scope indicates whether this resource is cluster or namespace scoped.  Default is namespaced\n\tScope ResourceScope `json:\"scope\" protobuf:\"bytes,4,opt,name=scope,casttype=ResourceScope\"`\n\t// versions is the list of all API versions of the defined custom resource.\n\t// Version names are used to compute the order in which served versions are listed in API discovery.\n\t// If the version string is \"kube-like\", it will sort above non \"kube-like\" version strings, which are ordered\n\t// lexicographically. \"Kube-like\" versions start with a \"v\", then are followed by a number (the major version),\n\t// then optionally the string \"alpha\" or \"beta\" and another number (the minor version). These are sorted first\n\t// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing\n\t// major version, then minor version. An example sorted list of versions:\n\t// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.\n\tVersions []CustomResourceDefinitionVersion `json:\"versions\" protobuf:\"bytes,7,rep,name=versions\"`\n}\n\n// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition\ntype CustomResourceDefinitionNames struct {\n\t// Plural is the plural name of the resource to serve.  It must match the name of the CustomResourceDefinition-registration\n\t// too: plural.group and it must be all lowercase.\n\tPlural string `json:\"plural\" protobuf:\"bytes,1,opt,name=plural\"`\n\t// Singular is the singular name of the resource.  It must be all lowercase  Defaults to lowercased <kind>\n\tSingular string `json:\"singular,omitempty\" protobuf:\"bytes,2,opt,name=singular\"`\n\t// ShortNames are short names for the resource.  It must be all lowercase.\n\tShortNames []string `json:\"shortNames,omitempty\" protobuf:\"bytes,3,opt,name=shortNames\"`\n\t// Kind is the serialized kind of the resource.  It is normally CamelCase and singular.\n\tKind string `json:\"kind\" protobuf:\"bytes,4,opt,name=kind\"`\n\t// ListKind is the serialized kind of the list for this resource.  Defaults to <kind>List.\n\tListKind string `json:\"listKind,omitempty\" protobuf:\"bytes,5,opt,name=listKind\"`\n}\n\n// ResourceScope is an enum defining the different scopes available to a custom resource\ntype ResourceScope string\n\nconst (\n\t// ClusterScoped is the `cluster` scope for a custom resource.\n\tClusterScoped ResourceScope = \"Cluster\"\n\t// NamespaceScoped is the `namespaced` scope for a custom resource.\n\tNamespaceScoped ResourceScope = \"Namespaced\"\n)\n\n// ConditionStatus reflects if a resource\ntype ConditionStatus string\n\n// These are valid condition statuses. \"ConditionTrue\" means a resource is in the condition.\n// \"ConditionFalse\" means a resource is not in the condition. \"ConditionUnknown\" means kubernetes\n// can't decide if a resource is in the condition or not. In the future, we could add other\n// intermediate conditions, e.g. ConditionDegraded.\nconst (\n\tConditionTrue    ConditionStatus = \"True\"\n\tConditionFalse   ConditionStatus = \"False\"\n\tConditionUnknown ConditionStatus = \"Unknown\"\n)\n\n// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type\ntype CustomResourceDefinitionConditionType string\n\nconst (\n\t// Established means that the resource has become active. A resource is established when all names are\n\t// accepted without a conflict for the first time. A resource stays established until deleted, even during\n\t// a later NamesAccepted due to changed names. Note that not all names can be changed.\n\tEstablished CustomResourceDefinitionConditionType = \"Established\"\n\t// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in\n\t// the group and are therefore accepted.\n\tNamesAccepted CustomResourceDefinitionConditionType = \"NamesAccepted\"\n\t// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.\n\tTerminating CustomResourceDefinitionConditionType = \"Terminating\"\n)\n\n// CustomResourceDefinitionCondition contains details for the current condition of this pod.\ntype CustomResourceDefinitionCondition struct {\n\t// Type is the type of the condition.\n\tType CustomResourceDefinitionConditionType `json:\"type\" protobuf:\"bytes,1,opt,name=type,casttype=CustomResourceDefinitionConditionType\"`\n\t// Status is the status of the condition.\n\t// Can be True, False, Unknown.\n\tStatus ConditionStatus `json:\"status\" protobuf:\"bytes,2,opt,name=status,casttype=ConditionStatus\"`\n\t// Last time the condition transitioned from one status to another.\n\t// +optional\n\tLastTransitionTime Time `json:\"lastTransitionTime,omitempty\" protobuf:\"bytes,3,opt,name=lastTransitionTime\"`\n\t// Unique, one-word, CamelCase reason for the condition's last transition.\n\t// +optional\n\tReason string `json:\"reason,omitempty\" protobuf:\"bytes,4,opt,name=reason\"`\n\t// Human-readable message indicating details about last transition.\n\t// +optional\n\tMessage string `json:\"message,omitempty\" protobuf:\"bytes,5,opt,name=message\"`\n}\n\n// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition\ntype CustomResourceDefinitionStatus struct {\n\t// Conditions indicate state for particular aspects of a CustomResourceDefinition\n\tConditions []CustomResourceDefinitionCondition `json:\"conditions\" protobuf:\"bytes,1,opt,name=conditions\"`\n\n\t// AcceptedNames are the names that are actually being used to serve discovery\n\t// They may be different than the names in spec.\n\tAcceptedNames CustomResourceDefinitionNames `json:\"acceptedNames\" protobuf:\"bytes,2,opt,name=acceptedNames\"`\n}\n\n// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of\n// a CustomResourceDefinition\nconst CustomResourceCleanupFinalizer = \"customresourcecleanup.apiextensions.k8s.io\"\n\n// +genclient\n// +genclient:nonNamespaced\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// CustomResourceDefinition represents a resource that should be exposed on the API server.  Its name MUST be in the format\n// <.spec.name>.<.spec.group>.\ntype CustomResourceDefinition struct {\n\tTypeMeta   `json:\",inline\"`\n\tObjectMeta `json:\"metadata,omitempty\" protobuf:\"bytes,1,opt,name=metadata\"`\n\n\t// Spec describes how the user wants the resources to appear\n\tSpec CustomResourceDefinitionSpec `json:\"spec,omitempty\" protobuf:\"bytes,2,opt,name=spec\"`\n\t// Status indicates the actual state of the CustomResourceDefinition\n\tStatus CustomResourceDefinitionStatus `json:\"status,omitempty\" protobuf:\"bytes,3,opt,name=status\"`\n}\n\n// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n\n// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.\ntype CustomResourceDefinitionList struct {\n\tTypeMeta `json:\",inline\"`\n\tListMeta `json:\"metadata,omitempty\" protobuf:\"bytes,1,opt,name=metadata\"`\n\n\t// Items individual CustomResourceDefinitions\n\tItems []CustomResourceDefinition `json:\"items\" protobuf:\"bytes,2,rep,name=items\"`\n}\n\ntype CustomResourceDefinitionVersion struct {\n\t// name is the version name, e.g. “v1”, “v2beta1”, etc.\n\t// The custom resources are served under this version at `/apis/<group>/<version>/...` if `served` is true.\n\tName string `json:\"name\" protobuf:\"bytes,1,opt,name=name\"`\n\t// served is a flag enabling/disabling this version from being served via REST APIs\n\tServed bool `json:\"served\" protobuf:\"varint,2,opt,name=served\"`\n\t// storage indicates this version should be used when persisting custom resources to storage.\n\t// There must be exactly one version with storage=true.\n\tStorage bool `json:\"storage\" protobuf:\"varint,3,opt,name=storage\"`\n\t// schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource.\n\t// +optional\n\tSchema *CustomResourceValidation `json:\"schema,omitempty\" protobuf:\"bytes,4,opt,name=schema\"`\n}\n\n// CustomResourceValidation is a list of validation methods for CustomResources.\ntype CustomResourceValidation struct {\n\t// OpenAPIV3Schema is the OpenAPI v3 schema to be validated against.\n\tOpenAPIV3Schema *JSONSchemaProps `json:\"openAPIV3Schema,omitempty\" protobuf:\"bytes,1,opt,name=openAPIV3Schema\"`\n}\n\n// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).\ntype JSONSchemaProps struct {\n\tType                   string `json:\"type,omitempty\" protobuf:\"bytes,5,opt,name=type\"`\n\tXPreserveUnknownFields *bool  `json:\"x-kubernetes-preserve-unknown-fields,omitempty\" protobuf:\"bytes,38,opt,name=xKubernetesPreserveUnknownFields\"`\n}\n"
  },
  {
    "path": "storage/kubernetes/k8sapi/doc.go",
    "content": "// Package k8sapi holds vendored Kubernetes types.\npackage k8sapi\n"
  },
  {
    "path": "storage/kubernetes/k8sapi/extensions.go",
    "content": "/*\nCopyright 2015 The Kubernetes Authors.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage k8sapi\n\n// An APIVersion represents a single concrete version of an object model.\ntype APIVersion struct {\n\t// Name of this version (e.g. 'v1').\n\tName string `json:\"name,omitempty\" protobuf:\"bytes,1,opt,name=name\"`\n}\n"
  },
  {
    "path": "storage/kubernetes/k8sapi/time.go",
    "content": "/*\nCopyright 2014 The Kubernetes Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage k8sapi\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n)\n\n// Time is a wrapper around time.Time which supports correct\n// marshaling to YAML and JSON.  Wrappers are provided for many\n// of the factory methods that the time package offers.\n//\n// +protobuf.options.marshal=false\n// +protobuf.as=Timestamp\ntype Time struct {\n\ttime.Time `protobuf:\"-\"`\n}\n\n// NewTime returns a wrapped instance of the provided time\nfunc NewTime(time time.Time) Time {\n\treturn Time{time}\n}\n\n// Date returns the Time corresponding to the supplied parameters\n// by wrapping time.Date.\nfunc Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Time {\n\treturn Time{time.Date(year, month, day, hour, min, sec, nsec, loc)}\n}\n\n// Now returns the current local time.\nfunc Now() Time {\n\treturn Time{time.Now()}\n}\n\n// IsZero returns true if the value is nil or time is zero.\nfunc (t *Time) IsZero() bool {\n\tif t == nil {\n\t\treturn true\n\t}\n\treturn t.Time.IsZero()\n}\n\n// Before reports whether the time instant t is before u.\nfunc (t Time) Before(u Time) bool {\n\treturn t.Time.Before(u.Time)\n}\n\n// Equal reports whether the time instant t is equal to u.\nfunc (t Time) Equal(u Time) bool {\n\treturn t.Time.Equal(u.Time)\n}\n\n// Unix returns the local time corresponding to the given Unix time\n// by wrapping time.Unix.\nfunc Unix(sec int64, nsec int64) Time {\n\treturn Time{time.Unix(sec, nsec)}\n}\n\n// Rfc3339Copy returns a copy of the Time at second-level precision.\nfunc (t Time) Rfc3339Copy() Time {\n\tcopied, _ := time.Parse(time.RFC3339, t.Format(time.RFC3339))\n\treturn Time{copied}\n}\n\n// UnmarshalJSON implements the json.Unmarshaller interface.\nfunc (t *Time) UnmarshalJSON(b []byte) error {\n\tif len(b) == 4 && string(b) == \"null\" {\n\t\tt.Time = time.Time{}\n\t\treturn nil\n\t}\n\n\tvar str string\n\tjson.Unmarshal(b, &str)\n\n\tpt, err := time.Parse(time.RFC3339, str)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.Time = pt.Local()\n\treturn nil\n}\n\n// UnmarshalQueryParameter converts from a URL query parameter value to an object\nfunc (t *Time) UnmarshalQueryParameter(str string) error {\n\tif len(str) == 0 {\n\t\tt.Time = time.Time{}\n\t\treturn nil\n\t}\n\t// Tolerate requests from older clients that used JSON serialization to build query params\n\tif len(str) == 4 && str == \"null\" {\n\t\tt.Time = time.Time{}\n\t\treturn nil\n\t}\n\n\tpt, err := time.Parse(time.RFC3339, str)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tt.Time = pt.Local()\n\treturn nil\n}\n\n// MarshalJSON implements the json.Marshaler interface.\nfunc (t Time) MarshalJSON() ([]byte, error) {\n\tif t.IsZero() {\n\t\t// Encode unset/nil objects as JSON's \"null\".\n\t\treturn []byte(\"null\"), nil\n\t}\n\n\treturn json.Marshal(t.UTC().Format(time.RFC3339))\n}\n\n// MarshalQueryParameter converts to a URL query parameter value\nfunc (t Time) MarshalQueryParameter() (string, error) {\n\tif t.IsZero() {\n\t\t// Encode unset/nil objects as an empty string\n\t\treturn \"\", nil\n\t}\n\n\treturn t.UTC().Format(time.RFC3339), nil\n}\n"
  },
  {
    "path": "storage/kubernetes/k8sapi/unversioned.go",
    "content": "/*\nCopyright 2015 The Kubernetes Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage k8sapi\n\n// TypeMeta describes an individual object in an API response or request\n// with strings representing the type of the object and its API schema version.\n// Structures that are versioned or persisted should inline TypeMeta.\ntype TypeMeta struct {\n\t// Kind is a string value representing the REST resource this object represents.\n\t// Servers may infer this from the endpoint the client submits requests to.\n\t// Cannot be updated.\n\t// In CamelCase.\n\t// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#types-kinds\n\tKind string `json:\"kind,omitempty\" protobuf:\"bytes,1,opt,name=kind\"`\n\n\t// APIVersion defines the versioned schema of this representation of an object.\n\t// Servers should convert recognized schemas to the latest internal value, and\n\t// may reject unrecognized values.\n\t// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#resources\n\tAPIVersion string `json:\"apiVersion,omitempty\" protobuf:\"bytes,2,opt,name=apiVersion\"`\n}\n\n// ListMeta describes metadata that synthetic resources must have, including lists and\n// various status objects. A resource may have only one of {ObjectMeta, ListMeta}.\ntype ListMeta struct {\n\t// SelfLink is a URL representing this object.\n\t// Populated by the system.\n\t// Read-only.\n\tSelfLink string `json:\"selfLink,omitempty\" protobuf:\"bytes,1,opt,name=selfLink\"`\n\n\t// String that identifies the server's internal version of this object that\n\t// can be used by clients to determine when objects have changed.\n\t// Value must be treated as opaque by clients and passed unmodified back to the server.\n\t// Populated by the system.\n\t// Read-only.\n\t// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#concurrency-control-and-consistency\n\tResourceVersion string `json:\"resourceVersion,omitempty\" protobuf:\"bytes,2,opt,name=resourceVersion\"`\n}\n"
  },
  {
    "path": "storage/kubernetes/k8sapi/v1.go",
    "content": "/*\nCopyright 2015 The Kubernetes Authors All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage k8sapi\n\n// ObjectMeta is metadata that all persisted resources must have, which includes all objects\n// users must create.\ntype ObjectMeta struct {\n\t// Name must be unique within a namespace. Is required when creating resources, although\n\t// some resources may allow a client to request the generation of an appropriate name\n\t// automatically. Name is primarily intended for creation idempotence and configuration\n\t// definition.\n\t// Cannot be updated.\n\t// More info: http://releases.k8s.io/release-1.3/docs/user-guide/identifiers.md#names\n\tName string `json:\"name,omitempty\" protobuf:\"bytes,1,opt,name=name\"`\n\n\t// GenerateName is an optional prefix, used by the server, to generate a unique\n\t// name ONLY IF the Name field has not been provided.\n\t// If this field is used, the name returned to the client will be different\n\t// than the name passed. This value will also be combined with a unique suffix.\n\t// The provided value has the same validation rules as the Name field,\n\t// and may be truncated by the length of the suffix required to make the value\n\t// unique on the server.\n\t//\n\t// If this field is specified and the generated name exists, the server will\n\t// NOT return a 409 - instead, it will either return 201 Created or 500 with Reason\n\t// ServerTimeout indicating a unique name could not be found in the time allotted, and the client\n\t// should retry (optionally after the time indicated in the Retry-After header).\n\t//\n\t// Applied only if Name is not specified.\n\t// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#idempotency\n\tGenerateName string `json:\"generateName,omitempty\" protobuf:\"bytes,2,opt,name=generateName\"`\n\n\t// Namespace defines the space within each name must be unique. An empty namespace is\n\t// equivalent to the \"default\" namespace, but \"default\" is the canonical representation.\n\t// Not all objects are required to be scoped to a namespace - the value of this field for\n\t// those objects will be empty.\n\t//\n\t// Must be a DNS_LABEL.\n\t// Cannot be updated.\n\t// More info: http://releases.k8s.io/release-1.3/docs/user-guide/namespaces.md\n\tNamespace string `json:\"namespace,omitempty\" protobuf:\"bytes,3,opt,name=namespace\"`\n\n\t// SelfLink is a URL representing this object.\n\t// Populated by the system.\n\t// Read-only.\n\tSelfLink string `json:\"selfLink,omitempty\" protobuf:\"bytes,4,opt,name=selfLink\"`\n\n\t// UID is the unique in time and space value for this object. It is typically generated by\n\t// the server on successful creation of a resource and is not allowed to change on PUT\n\t// operations.\n\t//\n\t// Populated by the system.\n\t// Read-only.\n\t// More info: http://releases.k8s.io/release-1.3/docs/user-guide/identifiers.md#uids\n\tUID string `json:\"uid,omitempty\" protobuf:\"bytes,5,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID\"`\n\n\t// An opaque value that represents the internal version of this object that can\n\t// be used by clients to determine when objects have changed. May be used for optimistic\n\t// concurrency, change detection, and the watch operation on a resource or set of resources.\n\t// Clients must treat these values as opaque and passed unmodified back to the server.\n\t// They may only be valid for a particular resource or set of resources.\n\t//\n\t// Populated by the system.\n\t// Read-only.\n\t// Value must be treated as opaque by clients and .\n\t// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#concurrency-control-and-consistency\n\tResourceVersion string `json:\"resourceVersion,omitempty\" protobuf:\"bytes,6,opt,name=resourceVersion\"`\n\n\t// A sequence number representing a specific generation of the desired state.\n\t// Populated by the system. Read-only.\n\tGeneration int64 `json:\"generation,omitempty\" protobuf:\"varint,7,opt,name=generation\"`\n\n\t// CreationTimestamp is a timestamp representing the server time when this object was\n\t// created. It is not guaranteed to be set in happens-before order across separate operations.\n\t// Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\t//\n\t// Populated by the system.\n\t// Read-only.\n\t// Null for lists.\n\t// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#metadata\n\tCreationTimestamp Time `json:\"creationTimestamp,omitempty\" protobuf:\"bytes,8,opt,name=creationTimestamp\"`\n\n\t// DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This\n\t// field is set by the server when a graceful deletion is requested by the user, and is not\n\t// directly settable by a client. The resource will be deleted (no longer visible from\n\t// resource lists, and not reachable by name) after the time in this field. Once set, this\n\t// value may not be unset or be set further into the future, although it may be shortened\n\t// or the resource may be deleted prior to this time. For example, a user may request that\n\t// a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination\n\t// signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet\n\t// will send a hard termination signal to the container.\n\t// If not set, graceful deletion of the object has not been requested.\n\t//\n\t// Populated by the system when a graceful deletion is requested.\n\t// Read-only.\n\t// More info: http://releases.k8s.io/release-1.3/docs/devel/api-conventions.md#metadata\n\tDeletionTimestamp *Time `json:\"deletionTimestamp,omitempty\" protobuf:\"bytes,9,opt,name=deletionTimestamp\"`\n\n\t// Number of seconds allowed for this object to gracefully terminate before\n\t// it will be removed from the system. Only set when deletionTimestamp is also set.\n\t// May only be shortened.\n\t// Read-only.\n\tDeletionGracePeriodSeconds *int64 `json:\"deletionGracePeriodSeconds,omitempty\" protobuf:\"varint,10,opt,name=deletionGracePeriodSeconds\"`\n\n\t// Map of string keys and values that can be used to organize and categorize\n\t// (scope and select) objects. May match selectors of replication controllers\n\t// and services.\n\t// More info: http://releases.k8s.io/release-1.3/docs/user-guide/labels.md\n\t// TODO: replace map[string]string with labels.LabelSet type\n\tLabels map[string]string `json:\"labels,omitempty\" protobuf:\"bytes,11,rep,name=labels\"`\n\n\t// Annotations is an unstructured key value map stored with a resource that may be\n\t// set by external tools to store and retrieve arbitrary metadata. They are not\n\t// queryable and should be preserved when modifying objects.\n\t// More info: http://releases.k8s.io/release-1.3/docs/user-guide/annotations.md\n\tAnnotations map[string]string `json:\"annotations,omitempty\" protobuf:\"bytes,12,rep,name=annotations\"`\n\n\t// List of objects depended by this object. If ALL objects in the list have\n\t// been deleted, this object will be garbage collected. If this object is managed by a controller,\n\t// then an entry in this list will point to this controller, with the controller field set to true.\n\t// There cannot be more than one managing controller.\n\tOwnerReferences []OwnerReference `json:\"ownerReferences,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"uid\" protobuf:\"bytes,13,rep,name=ownerReferences\"`\n\n\t// Must be empty before the object is deleted from the registry. Each entry\n\t// is an identifier for the responsible component that will remove the entry\n\t// from the list. If the deletionTimestamp of the object is non-nil, entries\n\t// in this list can only be removed.\n\tFinalizers []string `json:\"finalizers,omitempty\" patchStrategy:\"merge\" protobuf:\"bytes,14,rep,name=finalizers\"`\n}\n\n// OwnerReference contains enough information to let you identify an owning\n// object. Currently, an owning object must be in the same namespace, so there\n// is no namespace field.\ntype OwnerReference struct {\n\t// API version of the referent.\n\tAPIVersion string `json:\"apiVersion\" protobuf:\"bytes,5,opt,name=apiVersion\"`\n\t// Kind of the referent.\n\t// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds\n\tKind string `json:\"kind\" protobuf:\"bytes,1,opt,name=kind\"`\n\t// Name of the referent.\n\t// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#names\n\tName string `json:\"name\" protobuf:\"bytes,3,opt,name=name\"`\n\t// UID of the referent.\n\t// More info: http://releases.k8s.io/HEAD/docs/user-guide/identifiers.md#uids\n\tUID string `json:\"uid\" protobuf:\"bytes,4,opt,name=uid,casttype=k8s.io/kubernetes/pkg/types.UID\"`\n\t// If true, this reference points to the managing controller.\n\tController *bool `json:\"controller,omitempty\" protobuf:\"varint,6,opt,name=controller\"`\n}\n"
  },
  {
    "path": "storage/kubernetes/lock.go",
    "content": "package kubernetes\n\nimport (\n\t\"fmt\"\n\t\"time\"\n)\n\nconst (\n\tlockAnnotation = \"dexidp.com/resource-lock\"\n\tlockTimeFormat = time.RFC3339\n)\n\nvar (\n\tlockTimeout     = 10 * time.Second\n\tlockCheckPeriod = 100 * time.Millisecond\n)\n\n// refreshTokenLock is an implementation of annotation-based optimistic locking.\n//\n// Refresh token contains data to refresh identity in external authentication system.\n// There is a requirement that refresh should be called only once because of several reasons:\n//   - Some of OIDC providers could use the refresh token rotation feature which requires calling refresh only once.\n//   - Providers can limit the rate of requests to the token endpoint, which will lead to the error\n//     in case of many concurrent requests.\n//\n// The lock uses a Kubernetes annotation on the refresh token resource as a mutex.\n// Only one goroutine can hold the lock at a time; others poll until the annotation\n// is removed (unlocked) or expires (broken). The Kubernetes resourceVersion on put\n// acts as compare-and-swap: if two goroutines race to set the annotation, only one\n// succeeds and the other gets a 409 Conflict.\ntype refreshTokenLock struct {\n\tcli *client\n\t// waitingState tracks whether this lock instance has lost a compare-and-swap race\n\t// and is now polling for the lock to be released. Used by Unlock to skip the\n\t// annotation removal — only the goroutine that successfully wrote the annotation\n\t// should remove it.\n\twaitingState bool\n}\n\nfunc newRefreshTokenLock(cli *client) *refreshTokenLock {\n\treturn &refreshTokenLock{cli: cli}\n}\n\n// Lock polls until the lock annotation can be set on the refresh token resource.\n// Returns nil when the lock is acquired, or an error on timeout (200 attempts × 100ms).\nfunc (l *refreshTokenLock) Lock(id string) error {\n\tfor i := 0; i <= 200; i++ {\n\t\tok, err := l.setLockAnnotation(id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif !ok {\n\t\t\treturn nil\n\t\t}\n\t\ttime.Sleep(lockCheckPeriod)\n\t}\n\treturn fmt.Errorf(\"timeout waiting for refresh token %s lock\", id)\n}\n\n// Unlock removes the lock annotation from the refresh token resource.\n// Only the holder of the lock (waitingState == false) performs the removal.\nfunc (l *refreshTokenLock) Unlock(id string) {\n\tif l.waitingState {\n\t\t// This goroutine never successfully wrote the annotation, so there's\n\t\t// nothing to remove. Another goroutine holds (or held) the lock.\n\t\treturn\n\t}\n\n\tr, err := l.cli.getRefreshToken(id)\n\tif err != nil {\n\t\tl.cli.logger.Debug(\"failed to get resource to release lock for refresh token\", \"token_id\", id, \"err\", err)\n\t\treturn\n\t}\n\n\tr.Annotations = nil\n\terr = l.cli.put(resourceRefreshToken, r.ObjectMeta.Name, r)\n\tif err != nil {\n\t\tl.cli.logger.Debug(\"failed to release lock for refresh token\", \"token_id\", id, \"err\", err)\n\t}\n}\n\n// setLockAnnotation attempts to acquire the lock by writing an annotation with\n// an expiration timestamp. Returns (true, nil) when the caller should keep waiting,\n// (false, nil) when the lock is acquired, or (false, err) on a non-retriable error.\n//\n// The locking protocol relies on Kubernetes optimistic concurrency: every put\n// includes the resource's current resourceVersion, so concurrent writes to the\n// same object result in a 409 Conflict for all but one writer.\nfunc (l *refreshTokenLock) setLockAnnotation(id string) (bool, error) {\n\tr, err := l.cli.getRefreshToken(id)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\n\tcurrentTime := time.Now()\n\tlockData := map[string]string{\n\t\tlockAnnotation: currentTime.Add(lockTimeout).Format(lockTimeFormat),\n\t}\n\n\tval, ok := r.Annotations[lockAnnotation]\n\tif !ok {\n\t\t// No annotation means the lock is free. Every goroutine — whether it's\n\t\t// a first-time caller or was previously waiting — must compete by writing\n\t\t// the annotation. The put uses the current resourceVersion, so only one\n\t\t// writer succeeds; the rest get a 409 Conflict and go back to polling.\n\t\tr.Annotations = lockData\n\t\terr := l.cli.put(resourceRefreshToken, r.ObjectMeta.Name, r)\n\t\tif err == nil {\n\t\t\tl.waitingState = false\n\t\t\treturn false, nil\n\t\t}\n\n\t\tif isKubernetesAPIConflictError(err) {\n\t\t\tl.waitingState = true\n\t\t\treturn true, nil\n\t\t}\n\t\treturn false, err\n\t}\n\n\tuntil, err := time.Parse(lockTimeFormat, val)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"lock annotation value is malformed: %v\", err)\n\t}\n\n\tif !currentTime.After(until) {\n\t\t// Lock is held by another goroutine and has not expired yet — keep polling.\n\t\tl.waitingState = true\n\t\treturn true, nil\n\t}\n\n\t// Lock has expired (holder crashed or is too slow). Attempt to break it by\n\t// overwriting the annotation with a new expiration. Again, only one writer\n\t// can win the compare-and-swap race.\n\tr.Annotations = lockData\n\n\terr = l.cli.put(resourceRefreshToken, r.ObjectMeta.Name, r)\n\tif err == nil {\n\t\treturn false, nil\n\t}\n\n\tl.cli.logger.Debug(\"break lock annotation\", \"error\", err)\n\tif isKubernetesAPIConflictError(err) {\n\t\tl.waitingState = true\n\t\treturn true, nil\n\t}\n\treturn false, err\n}\n"
  },
  {
    "path": "storage/kubernetes/storage.go",
    "content": "package kubernetes\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/kubernetes/k8sapi\"\n)\n\nconst (\n\tkindAuthCode        = \"AuthCode\"\n\tkindAuthRequest     = \"AuthRequest\"\n\tkindClient          = \"OAuth2Client\"\n\tkindRefreshToken    = \"RefreshToken\"\n\tkindKeys            = \"SigningKey\"\n\tkindPassword        = \"Password\"\n\tkindOfflineSessions = \"OfflineSessions\"\n\tkindConnector       = \"Connector\"\n\tkindDeviceRequest   = \"DeviceRequest\"\n\tkindDeviceToken     = \"DeviceToken\"\n\tkindUserIdentity    = \"UserIdentity\"\n\tkindAuthSession     = \"AuthSession\"\n)\n\nconst (\n\tresourceAuthCode        = \"authcodes\"\n\tresourceAuthRequest     = \"authrequests\"\n\tresourceClient          = \"oauth2clients\"\n\tresourceRefreshToken    = \"refreshtokens\"\n\tresourceKeys            = \"signingkeies\" // Kubernetes attempts to pluralize.\n\tresourcePassword        = \"passwords\"\n\tresourceOfflineSessions = \"offlinesessionses\" // Again attempts to pluralize.\n\tresourceConnector       = \"connectors\"\n\tresourceDeviceRequest   = \"devicerequests\"\n\tresourceDeviceToken     = \"devicetokens\"\n\tresourceUserIdentity    = \"useridentities\"\n\tresourceAuthSession     = \"authsessions\"\n)\n\nconst (\n\tcrdHandlingEnsure = \"ensure\"\n\tcrdHandlingCheck  = \"check\"\n)\n\nvar _ storage.Storage = (*client)(nil)\n\nconst (\n\tgcResultLimit = 500\n)\n\n// Config values for the Kubernetes storage type.\ntype Config struct {\n\tInCluster      bool   `json:\"inCluster\"`\n\tKubeConfigFile string `json:\"kubeConfigFile\"`\n\t// CRDHandling controls how the storage handles Custom Resource Definitions (CRDs).\n\t// Supported values:\n\t// - \"ensure\": Attempt to create all missing CRDs. If any CRD creation fails, initialization fails. (default)\n\t// - \"check\": Fail immediately if any CRDs are missing with message \"storage is not initialized, CRDs are not created\"\n\tCRDHandling string `json:\"crdHandling\"`\n}\n\n// Open returns a storage using Kubernetes third party resource.\nfunc (c *Config) Open(logger *slog.Logger) (storage.Storage, error) {\n\treturn c.open(logger, false)\n}\n\n// open returns a kubernetes client, initializing the third party resources used\n// by dex.\n//\n// waitForResources controls if errors creating the resources cause this method to return\n// immediately (used during testing), or if the client will asynchronously retry.\nfunc (c *Config) open(logger *slog.Logger, waitForResources bool) (*client, error) {\n\tif c.CRDHandling == \"\" {\n\t\tc.CRDHandling = crdHandlingEnsure\n\t}\n\tif c.InCluster && (c.KubeConfigFile != \"\") {\n\t\treturn nil, errors.New(\"cannot specify both 'inCluster' and 'kubeConfigFile'\")\n\t}\n\tif !c.InCluster && (c.KubeConfigFile == \"\") {\n\t\treturn nil, errors.New(\"must specify either 'inCluster' or 'kubeConfigFile'\")\n\t}\n\n\tvar (\n\t\tcluster   k8sapi.Cluster\n\t\tuser      k8sapi.AuthInfo\n\t\tnamespace string\n\t\terr       error\n\t)\n\tif c.InCluster {\n\t\tcluster, user, namespace, err = inClusterConfig()\n\t} else {\n\t\tcluster, user, namespace, err = loadKubeConfig(c.KubeConfigFile)\n\t}\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tcli, err := newClient(cluster, user, namespace, logger, c.InCluster, c.CRDHandling)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"create client: %v\", err)\n\t}\n\n\tif err = cli.detectKubernetesVersion(); err != nil {\n\t\treturn nil, fmt.Errorf(\"cannot get kubernetes version: %v\", err)\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tlogger.Info(\"creating custom Kubernetes resources\")\n\tif !cli.registerCustomResources() {\n\t\tif waitForResources {\n\t\t\tcancel()\n\t\t\treturn nil, fmt.Errorf(\"failed creating custom resources\")\n\t\t}\n\n\t\t// Try to synchronously create the custom resources once. This doesn't mean\n\t\t// they'll immediately be available, but ensures that the client will actually try\n\t\t// once.\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\tif cli.registerCustomResources() {\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tselect {\n\t\t\t\tcase <-ctx.Done():\n\t\t\t\t\treturn\n\t\t\t\tcase <-time.After(30 * time.Second):\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t}\n\n\tif waitForResources {\n\t\tif err := cli.waitForCRDs(ctx); err != nil {\n\t\t\tcancel()\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\t// If the client is closed, stop trying to create resources.\n\tcli.cancel = cancel\n\treturn cli, nil\n}\n\n// registerCustomResources attempts to create the custom resources dex\n// requires or identifies that they're already enabled. This function creates\n// custom resource definitions(CRDs)\n// It logs all errors, returning true if the resources were created successfully.\n//\n// Creating a custom resource does not mean that they'll be immediately available.\nfunc (cli *client) registerCustomResources() bool {\n\tdefinitions := customResourceDefinitions(cli.crdAPIVersion)\n\n\t// First pass: collect all CRDs that don't exist\n\tvar missingCRDs []k8sapi.CustomResourceDefinition\n\n\tfor _, r := range definitions {\n\t\tvar i interface{}\n\t\tcli.logger.Info(\"checking if custom resource has already been created...\", \"object\", r.ObjectMeta.Name)\n\t\tif err := cli.listN(r.Spec.Names.Plural, &i, 1); err == nil {\n\t\t\tcli.logger.Info(\"the custom resource already available, skipping create\", \"object\", r.ObjectMeta.Name)\n\t\t} else {\n\t\t\tcli.logger.Info(\"custom resource not found\", \"object\", r.ObjectMeta.Name, \"err\", err)\n\t\t\tmissingCRDs = append(missingCRDs, r)\n\t\t}\n\t}\n\n\t// Second pass: handle missing CRDs based on crdHandling option\n\tif len(missingCRDs) > 0 {\n\t\tcli.logger.Info(\"found missing CRDs\", \"count\", len(missingCRDs))\n\t\tswitch cli.crdHandling {\n\t\tcase crdHandlingCheck:\n\t\t\t// For \"check\" mode, fail and report that CRDs are not initialized\n\t\t\tcli.logger.Error(\"storage is not initialized, CRDs are not created\", \"crdHandling\", cli.crdHandling, \"missing_count\", len(missingCRDs))\n\t\t\treturn false\n\t\tcase crdHandlingEnsure:\n\t\t\tcli.logger.Info(\"crdHandling is 'ensure', attempting to create missing CRDs\")\n\t\t\tfor _, r := range missingCRDs {\n\t\t\t\tresourceName := r.ObjectMeta.Name\n\t\t\t\terr := cli.postResource(cli.crdAPIVersion, \"\", \"customresourcedefinitions\", r)\n\t\t\t\tif err != nil {\n\t\t\t\t\tif !errors.Is(err, storage.ErrAlreadyExists) {\n\t\t\t\t\t\tcli.logger.Error(\"failed to create custom resource\", \"object\", resourceName, \"err\", err)\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tcli.logger.Info(\"custom resource already created\", \"object\", resourceName)\n\t\t\t\t} else {\n\t\t\t\t\tcli.logger.Info(\"successfully created custom resource\", \"object\", resourceName)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true\n\t\tdefault:\n\t\t\tcli.logger.Error(\"invalid crdHandling value\", \"value\", cli.crdHandling)\n\t\t\treturn false\n\t\t}\n\t}\n\n\t// All CRDs exist\n\treturn true\n}\n\n// waitForCRDs waits for all CRDs to be in a ready state, and is used\n// by the tests to synchronize before running conformance.\nfunc (cli *client) waitForCRDs(ctx context.Context) error {\n\tctx, cancel := context.WithTimeout(ctx, time.Second*30)\n\tdefer cancel()\n\n\tfor _, crd := range customResourceDefinitions(cli.crdAPIVersion) {\n\t\tfor {\n\t\t\terr := cli.isCRDReady(crd.Name)\n\t\t\tif err == nil {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tcli.logger.ErrorContext(ctx, \"checking CRD\", \"err\", err)\n\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn errors.New(\"timed out waiting for CRDs to be available\")\n\t\t\tcase <-time.After(time.Millisecond * 100):\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// isCRDReady determines if a CRD is ready by inspecting its conditions.\nfunc (cli *client) isCRDReady(name string) error {\n\tvar r k8sapi.CustomResourceDefinition\n\terr := cli.getResource(cli.crdAPIVersion, \"\", \"customresourcedefinitions\", name, &r)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"get crd %s: %v\", name, err)\n\t}\n\n\tconds := make(map[string]string) // For debugging, keep the conditions around.\n\tfor _, c := range r.Status.Conditions {\n\t\tif c.Type == k8sapi.Established && c.Status == k8sapi.ConditionTrue {\n\t\t\treturn nil\n\t\t}\n\t\tconds[string(c.Type)] = string(c.Status)\n\t}\n\treturn fmt.Errorf(\"crd %s not ready %#v\", name, conds)\n}\n\nfunc (cli *client) Close() error {\n\tif cli.cancel != nil {\n\t\tcli.cancel()\n\t}\n\treturn nil\n}\n\nfunc (cli *client) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) error {\n\treturn cli.post(resourceAuthRequest, cli.fromStorageAuthRequest(a))\n}\n\nfunc (cli *client) CreateClient(ctx context.Context, c storage.Client) error {\n\treturn cli.post(resourceClient, cli.fromStorageClient(c))\n}\n\nfunc (cli *client) CreateAuthCode(ctx context.Context, c storage.AuthCode) error {\n\treturn cli.post(resourceAuthCode, cli.fromStorageAuthCode(c))\n}\n\nfunc (cli *client) CreatePassword(ctx context.Context, p storage.Password) error {\n\treturn cli.post(resourcePassword, cli.fromStoragePassword(p))\n}\n\nfunc (cli *client) CreateRefresh(ctx context.Context, r storage.RefreshToken) error {\n\treturn cli.post(resourceRefreshToken, cli.fromStorageRefreshToken(r))\n}\n\nfunc (cli *client) CreateOfflineSessions(ctx context.Context, o storage.OfflineSessions) error {\n\treturn cli.post(resourceOfflineSessions, cli.fromStorageOfflineSessions(o))\n}\n\nfunc (cli *client) CreateConnector(ctx context.Context, c storage.Connector) error {\n\treturn cli.post(resourceConnector, cli.fromStorageConnector(c))\n}\n\nfunc (cli *client) GetAuthRequest(ctx context.Context, id string) (storage.AuthRequest, error) {\n\tvar req AuthRequest\n\tif err := cli.get(resourceAuthRequest, id, &req); err != nil {\n\t\treturn storage.AuthRequest{}, err\n\t}\n\treturn toStorageAuthRequest(req), nil\n}\n\nfunc (cli *client) GetAuthCode(ctx context.Context, id string) (storage.AuthCode, error) {\n\tvar code AuthCode\n\tif err := cli.get(resourceAuthCode, id, &code); err != nil {\n\t\treturn storage.AuthCode{}, err\n\t}\n\treturn toStorageAuthCode(code), nil\n}\n\nfunc (cli *client) GetClient(ctx context.Context, id string) (storage.Client, error) {\n\tc, err := cli.getClient(id)\n\tif err != nil {\n\t\treturn storage.Client{}, err\n\t}\n\treturn toStorageClient(c), nil\n}\n\nfunc (cli *client) getClient(id string) (Client, error) {\n\tvar c Client\n\tname := cli.idToName(id)\n\tif err := cli.get(resourceClient, name, &c); err != nil {\n\t\treturn Client{}, err\n\t}\n\tif c.ID != id {\n\t\treturn Client{}, fmt.Errorf(\"get client: ID %q mapped to client with ID %q\", id, c.ID)\n\t}\n\treturn c, nil\n}\n\nfunc (cli *client) GetPassword(ctx context.Context, email string) (storage.Password, error) {\n\tp, err := cli.getPassword(email)\n\tif err != nil {\n\t\treturn storage.Password{}, err\n\t}\n\treturn toStoragePassword(p), nil\n}\n\nfunc (cli *client) getPassword(email string) (Password, error) {\n\t// TODO(ericchiang): Figure out whose job it is to lowercase emails.\n\temail = strings.ToLower(email)\n\tvar p Password\n\tname := cli.idToName(email)\n\tif err := cli.get(resourcePassword, name, &p); err != nil {\n\t\treturn Password{}, err\n\t}\n\tif email != p.Email {\n\t\treturn Password{}, fmt.Errorf(\"get email: email %q mapped to password with email %q\", email, p.Email)\n\t}\n\treturn p, nil\n}\n\nfunc (cli *client) GetKeys(ctx context.Context) (storage.Keys, error) {\n\tvar keys Keys\n\tif err := cli.get(resourceKeys, keysName, &keys); err != nil {\n\t\treturn storage.Keys{}, err\n\t}\n\treturn toStorageKeys(keys), nil\n}\n\nfunc (cli *client) GetRefresh(ctx context.Context, id string) (storage.RefreshToken, error) {\n\tr, err := cli.getRefreshToken(id)\n\tif err != nil {\n\t\treturn storage.RefreshToken{}, err\n\t}\n\treturn toStorageRefreshToken(r), nil\n}\n\nfunc (cli *client) getRefreshToken(id string) (r RefreshToken, err error) {\n\terr = cli.get(resourceRefreshToken, id, &r)\n\treturn\n}\n\nfunc (cli *client) GetOfflineSessions(ctx context.Context, userID string, connID string) (storage.OfflineSessions, error) {\n\to, err := cli.getOfflineSessions(userID, connID)\n\tif err != nil {\n\t\treturn storage.OfflineSessions{}, err\n\t}\n\treturn toStorageOfflineSessions(o), nil\n}\n\nfunc (cli *client) getOfflineSessions(userID string, connID string) (o OfflineSessions, err error) {\n\tname := cli.offlineTokenName(userID, connID)\n\tif err = cli.get(resourceOfflineSessions, name, &o); err != nil {\n\t\treturn OfflineSessions{}, err\n\t}\n\tif userID != o.UserID || connID != o.ConnID {\n\t\treturn OfflineSessions{}, fmt.Errorf(\"get offline session: wrong session retrieved\")\n\t}\n\treturn o, nil\n}\n\nfunc (cli *client) GetConnector(ctx context.Context, id string) (storage.Connector, error) {\n\tvar c Connector\n\tif err := cli.get(resourceConnector, id, &c); err != nil {\n\t\treturn storage.Connector{}, err\n\t}\n\treturn toStorageConnector(c), nil\n}\n\nfunc (cli *client) ListClients(ctx context.Context) ([]storage.Client, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\nfunc (cli *client) ListRefreshTokens(ctx context.Context) ([]storage.RefreshToken, error) {\n\treturn nil, errors.New(\"not implemented\")\n}\n\nfunc (cli *client) ListPasswords(ctx context.Context) (passwords []storage.Password, err error) {\n\tvar passwordList PasswordList\n\tif err = cli.list(resourcePassword, &passwordList); err != nil {\n\t\treturn passwords, fmt.Errorf(\"failed to list passwords: %v\", err)\n\t}\n\n\tfor _, password := range passwordList.Passwords {\n\t\tpasswords = append(passwords, toStoragePassword(password))\n\t}\n\n\treturn\n}\n\nfunc (cli *client) ListConnectors(ctx context.Context) (connectors []storage.Connector, err error) {\n\tvar connectorList ConnectorList\n\tif err = cli.list(resourceConnector, &connectorList); err != nil {\n\t\treturn connectors, fmt.Errorf(\"failed to list connectors: %v\", err)\n\t}\n\n\tconnectors = make([]storage.Connector, len(connectorList.Connectors))\n\tfor i, connector := range connectorList.Connectors {\n\t\tconnectors[i] = toStorageConnector(connector)\n\t}\n\n\treturn\n}\n\nfunc (cli *client) DeleteAuthRequest(ctx context.Context, id string) error {\n\treturn cli.delete(resourceAuthRequest, id)\n}\n\nfunc (cli *client) DeleteAuthCode(ctx context.Context, code string) error {\n\treturn cli.delete(resourceAuthCode, code)\n}\n\nfunc (cli *client) DeleteClient(ctx context.Context, id string) error {\n\t// Check for hash collision.\n\tc, err := cli.getClient(id)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.delete(resourceClient, c.ObjectMeta.Name)\n}\n\nfunc (cli *client) DeleteRefresh(ctx context.Context, id string) error {\n\treturn cli.delete(resourceRefreshToken, id)\n}\n\nfunc (cli *client) DeletePassword(ctx context.Context, email string) error {\n\t// Check for hash collision.\n\tp, err := cli.getPassword(email)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.delete(resourcePassword, p.ObjectMeta.Name)\n}\n\nfunc (cli *client) DeleteOfflineSessions(ctx context.Context, userID string, connID string) error {\n\t// Check for hash collision.\n\to, err := cli.getOfflineSessions(userID, connID)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.delete(resourceOfflineSessions, o.ObjectMeta.Name)\n}\n\nfunc (cli *client) DeleteConnector(ctx context.Context, id string) error {\n\treturn cli.delete(resourceConnector, id)\n}\n\nfunc (cli *client) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error {\n\tlock := newRefreshTokenLock(cli)\n\n\tif err := lock.Lock(id); err != nil {\n\t\treturn err\n\t}\n\tdefer lock.Unlock(id)\n\n\treturn retryOnConflict(ctx, func() error {\n\t\tr, err := cli.getRefreshToken(id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tupdated, err := updater(toStorageRefreshToken(r))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tupdated.ID = id\n\n\t\tnewToken := cli.fromStorageRefreshToken(updated)\n\t\tnewToken.ObjectMeta = r.ObjectMeta\n\n\t\treturn cli.put(resourceRefreshToken, r.ObjectMeta.Name, newToken)\n\t})\n}\n\nfunc (cli *client) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error {\n\tc, err := cli.getClient(id)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tupdated, err := updater(toStorageClient(c))\n\tif err != nil {\n\t\treturn err\n\t}\n\tupdated.ID = c.ID\n\n\tnewClient := cli.fromStorageClient(updated)\n\tnewClient.ObjectMeta = c.ObjectMeta\n\treturn cli.put(resourceClient, c.ObjectMeta.Name, newClient)\n}\n\nfunc (cli *client) UpdatePassword(ctx context.Context, email string, updater func(old storage.Password) (storage.Password, error)) error {\n\tp, err := cli.getPassword(email)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tupdated, err := updater(toStoragePassword(p))\n\tif err != nil {\n\t\treturn err\n\t}\n\tupdated.Email = p.Email\n\n\tnewPassword := cli.fromStoragePassword(updated)\n\tnewPassword.ObjectMeta = p.ObjectMeta\n\treturn cli.put(resourcePassword, p.ObjectMeta.Name, newPassword)\n}\n\nfunc (cli *client) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(old storage.OfflineSessions) (storage.OfflineSessions, error)) error {\n\treturn retryOnConflict(ctx, func() error {\n\t\to, err := cli.getOfflineSessions(userID, connID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tupdated, err := updater(toStorageOfflineSessions(o))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewOfflineSessions := cli.fromStorageOfflineSessions(updated)\n\t\tnewOfflineSessions.ObjectMeta = o.ObjectMeta\n\t\treturn cli.put(resourceOfflineSessions, o.ObjectMeta.Name, newOfflineSessions)\n\t})\n}\n\nfunc (cli *client) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error {\n\tfirstUpdate := false\n\tvar keys Keys\n\tif err := cli.get(resourceKeys, keysName, &keys); err != nil {\n\t\tif err != storage.ErrNotFound {\n\t\t\treturn err\n\t\t}\n\t\tfirstUpdate = true\n\t}\n\n\tvar oldKeys storage.Keys\n\tif !firstUpdate {\n\t\toldKeys = toStorageKeys(keys)\n\t}\n\n\tupdated, err := updater(oldKeys)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnewKeys := cli.fromStorageKeys(updated)\n\tif firstUpdate {\n\t\terr = cli.post(resourceKeys, newKeys)\n\t\tif err != nil && errors.Is(err, storage.ErrAlreadyExists) {\n\t\t\t// We need to tolerate conflicts here in case of HA mode.\n\t\t\tcli.logger.Debug(\"Keys creation failed. It is possible that keys have already been created by another dex instance.\", \"err\", err)\n\t\t\treturn errors.New(\"keys already created by another server instance\")\n\t\t}\n\n\t\treturn err\n\t}\n\n\tnewKeys.ObjectMeta = keys.ObjectMeta\n\n\terr = cli.put(resourceKeys, keysName, newKeys)\n\tif isKubernetesAPIConflictError(err) {\n\t\t// We need to tolerate conflicts here in case of HA mode.\n\t\t// Dex instances run keys rotation at the same time because they use SigningKey.nextRotation CR field as a trigger.\n\t\tcli.logger.Debug(\"Keys rotation failed. It is possible that keys have already been rotated by another dex instance.\", \"err\", err)\n\t\treturn errors.New(\"keys already rotated by another server instance\")\n\t}\n\n\treturn err\n}\n\nfunc (cli *client) UpdateAuthRequest(ctx context.Context, id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error {\n\tvar req AuthRequest\n\terr := cli.get(resourceAuthRequest, id, &req)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tupdated, err := updater(toStorageAuthRequest(req))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnewReq := cli.fromStorageAuthRequest(updated)\n\tnewReq.ObjectMeta = req.ObjectMeta\n\treturn cli.put(resourceAuthRequest, id, newReq)\n}\n\nfunc (cli *client) UpdateConnector(ctx context.Context, id string, updater func(a storage.Connector) (storage.Connector, error)) error {\n\treturn retryOnConflict(ctx, func() error {\n\t\tvar c Connector\n\t\terr := cli.get(resourceConnector, id, &c)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tupdated, err := updater(toStorageConnector(c))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewConn := cli.fromStorageConnector(updated)\n\t\tnewConn.ObjectMeta = c.ObjectMeta\n\t\treturn cli.put(resourceConnector, id, newConn)\n\t})\n}\n\nfunc (cli *client) GarbageCollect(ctx context.Context, now time.Time) (result storage.GCResult, err error) {\n\tvar authRequests AuthRequestList\n\tif err := cli.listN(resourceAuthRequest, &authRequests, gcResultLimit); err != nil {\n\t\treturn result, fmt.Errorf(\"failed to list auth requests: %v\", err)\n\t}\n\n\tvar delErr error\n\tfor _, authRequest := range authRequests.AuthRequests {\n\t\tif now.After(authRequest.Expiry) {\n\t\t\tif err := cli.delete(resourceAuthRequest, authRequest.ObjectMeta.Name); err != nil {\n\t\t\t\tcli.logger.Error(\"failed to delete auth request\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete auth request: %v\", err)\n\t\t\t}\n\t\t\tresult.AuthRequests++\n\t\t}\n\t}\n\tif delErr != nil {\n\t\treturn result, delErr\n\t}\n\n\tvar authCodes AuthCodeList\n\tif err := cli.listN(resourceAuthCode, &authCodes, gcResultLimit); err != nil {\n\t\treturn result, fmt.Errorf(\"failed to list auth codes: %v\", err)\n\t}\n\n\tfor _, authCode := range authCodes.AuthCodes {\n\t\tif now.After(authCode.Expiry) {\n\t\t\tif err := cli.delete(resourceAuthCode, authCode.ObjectMeta.Name); err != nil {\n\t\t\t\tcli.logger.Error(\"failed to delete auth code\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete auth code: %v\", err)\n\t\t\t}\n\t\t\tresult.AuthCodes++\n\t\t}\n\t}\n\n\tvar deviceRequests DeviceRequestList\n\tif err := cli.listN(resourceDeviceRequest, &deviceRequests, gcResultLimit); err != nil {\n\t\treturn result, fmt.Errorf(\"failed to list device requests: %v\", err)\n\t}\n\n\tfor _, deviceRequest := range deviceRequests.DeviceRequests {\n\t\tif now.After(deviceRequest.Expiry) {\n\t\t\tif err := cli.delete(resourceDeviceRequest, deviceRequest.ObjectMeta.Name); err != nil {\n\t\t\t\tcli.logger.Error(\"failed to delete device request\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete device request: %v\", err)\n\t\t\t}\n\t\t\tresult.DeviceRequests++\n\t\t}\n\t}\n\n\tvar deviceTokens DeviceTokenList\n\tif err := cli.listN(resourceDeviceToken, &deviceTokens, gcResultLimit); err != nil {\n\t\treturn result, fmt.Errorf(\"failed to list device tokens: %v\", err)\n\t}\n\n\tfor _, deviceToken := range deviceTokens.DeviceTokens {\n\t\tif now.After(deviceToken.Expiry) {\n\t\t\tif err := cli.delete(resourceDeviceToken, deviceToken.ObjectMeta.Name); err != nil {\n\t\t\t\tcli.logger.Error(\"failed to delete device token\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete device token: %v\", err)\n\t\t\t}\n\t\t\tresult.DeviceTokens++\n\t\t}\n\t}\n\n\tvar authSessions AuthSessionList\n\tif err := cli.listN(resourceAuthSession, &authSessions, gcResultLimit); err != nil {\n\t\treturn result, fmt.Errorf(\"failed to list auth sessions: %v\", err)\n\t}\n\n\tfor _, authSession := range authSessions.AuthSessions {\n\t\tif now.After(authSession.AbsoluteExpiry) || now.After(authSession.IdleExpiry) {\n\t\t\tif err := cli.delete(resourceAuthSession, authSession.ObjectMeta.Name); err != nil {\n\t\t\t\tcli.logger.Error(\"failed to delete auth session\", \"err\", err)\n\t\t\t\tdelErr = fmt.Errorf(\"failed to delete auth session: %v\", err)\n\t\t\t} else {\n\t\t\t\tresult.AuthSessions++\n\t\t\t}\n\t\t}\n\t}\n\n\tif delErr != nil {\n\t\treturn result, delErr\n\t}\n\treturn result, delErr\n}\n\nfunc (cli *client) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) error {\n\treturn cli.post(resourceDeviceRequest, cli.fromStorageDeviceRequest(d))\n}\n\nfunc (cli *client) GetDeviceRequest(ctx context.Context, userCode string) (storage.DeviceRequest, error) {\n\tvar req DeviceRequest\n\tif err := cli.get(resourceDeviceRequest, strings.ToLower(userCode), &req); err != nil {\n\t\treturn storage.DeviceRequest{}, err\n\t}\n\treturn toStorageDeviceRequest(req), nil\n}\n\nfunc (cli *client) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) error {\n\treturn cli.post(resourceDeviceToken, cli.fromStorageDeviceToken(t))\n}\n\nfunc (cli *client) GetDeviceToken(ctx context.Context, deviceCode string) (storage.DeviceToken, error) {\n\tvar token DeviceToken\n\tif err := cli.get(resourceDeviceToken, deviceCode, &token); err != nil {\n\t\treturn storage.DeviceToken{}, err\n\t}\n\treturn toStorageDeviceToken(token), nil\n}\n\nfunc (cli *client) getDeviceToken(deviceCode string) (t DeviceToken, err error) {\n\terr = cli.get(resourceDeviceToken, deviceCode, &t)\n\treturn\n}\n\nfunc (cli *client) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error {\n\treturn retryOnConflict(ctx, func() error {\n\t\tr, err := cli.getDeviceToken(deviceCode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tupdated, err := updater(toStorageDeviceToken(r))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tupdated.DeviceCode = deviceCode\n\n\t\tnewToken := cli.fromStorageDeviceToken(updated)\n\t\tnewToken.ObjectMeta = r.ObjectMeta\n\t\treturn cli.put(resourceDeviceToken, r.ObjectMeta.Name, newToken)\n\t})\n}\n\nfunc (cli *client) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) error {\n\treturn cli.post(resourceUserIdentity, cli.fromStorageUserIdentity(u))\n}\n\nfunc (cli *client) GetUserIdentity(ctx context.Context, userID, connectorID string) (storage.UserIdentity, error) {\n\tu, err := cli.getUserIdentity(userID, connectorID)\n\tif err != nil {\n\t\treturn storage.UserIdentity{}, err\n\t}\n\treturn toStorageUserIdentity(u), nil\n}\n\nfunc (cli *client) getUserIdentity(userID, connectorID string) (u UserIdentity, err error) {\n\tname := cli.offlineTokenName(userID, connectorID)\n\tif err = cli.get(resourceUserIdentity, name, &u); err != nil {\n\t\treturn UserIdentity{}, err\n\t}\n\tif userID != u.UserID || connectorID != u.ConnectorID {\n\t\treturn UserIdentity{}, fmt.Errorf(\"get user identity: wrong identity retrieved\")\n\t}\n\treturn u, nil\n}\n\nfunc (cli *client) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(old storage.UserIdentity) (storage.UserIdentity, error)) error {\n\treturn retryOnConflict(ctx, func() error {\n\t\tu, err := cli.getUserIdentity(userID, connectorID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tupdated, err := updater(toStorageUserIdentity(u))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewUserIdentity := cli.fromStorageUserIdentity(updated)\n\t\tnewUserIdentity.ObjectMeta = u.ObjectMeta\n\t\treturn cli.put(resourceUserIdentity, u.ObjectMeta.Name, newUserIdentity)\n\t})\n}\n\nfunc (cli *client) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error {\n\t// Check for hash collision.\n\tu, err := cli.getUserIdentity(userID, connectorID)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.delete(resourceUserIdentity, u.ObjectMeta.Name)\n}\n\nfunc (cli *client) ListUserIdentities(ctx context.Context) ([]storage.UserIdentity, error) {\n\tvar userIdentityList UserIdentityList\n\tif err := cli.list(resourceUserIdentity, &userIdentityList); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list user identities: %v\", err)\n\t}\n\n\tuserIdentities := make([]storage.UserIdentity, len(userIdentityList.UserIdentities))\n\tfor i, u := range userIdentityList.UserIdentities {\n\t\tuserIdentities[i] = toStorageUserIdentity(u)\n\t}\n\n\treturn userIdentities, nil\n}\n\nfunc (cli *client) CreateAuthSession(ctx context.Context, s storage.AuthSession) error {\n\treturn cli.post(resourceAuthSession, cli.fromStorageAuthSession(s))\n}\n\nfunc (cli *client) getAuthSession(userID, connectorID string) (AuthSession, error) {\n\tvar s AuthSession\n\tname := offlineTokenName(userID, connectorID, cli.hash)\n\tif err := cli.get(resourceAuthSession, name, &s); err != nil {\n\t\treturn AuthSession{}, err\n\t}\n\treturn s, nil\n}\n\nfunc (cli *client) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) {\n\ts, err := cli.getAuthSession(userID, connectorID)\n\tif err != nil {\n\t\treturn storage.AuthSession{}, err\n\t}\n\treturn toStorageAuthSession(s), nil\n}\n\nfunc (cli *client) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(old storage.AuthSession) (storage.AuthSession, error)) error {\n\treturn retryOnConflict(ctx, func() error {\n\t\ts, err := cli.getAuthSession(userID, connectorID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tupdated, err := updater(toStorageAuthSession(s))\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewSession := cli.fromStorageAuthSession(updated)\n\t\tnewSession.ObjectMeta = s.ObjectMeta\n\t\treturn cli.put(resourceAuthSession, s.ObjectMeta.Name, newSession)\n\t})\n}\n\nfunc (cli *client) ListAuthSessions(ctx context.Context) ([]storage.AuthSession, error) {\n\tvar authSessionList AuthSessionList\n\tif err := cli.list(resourceAuthSession, &authSessionList); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to list auth sessions: %v\", err)\n\t}\n\n\tsessions := make([]storage.AuthSession, len(authSessionList.AuthSessions))\n\tfor i, s := range authSessionList.AuthSessions {\n\t\tsessions[i] = toStorageAuthSession(s)\n\t}\n\n\treturn sessions, nil\n}\n\nfunc (cli *client) DeleteAuthSession(ctx context.Context, userID, connectorID string) error {\n\ts, err := cli.getAuthSession(userID, connectorID)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn cli.delete(resourceAuthSession, s.ObjectMeta.Name)\n}\n\nfunc isKubernetesAPIConflictError(err error) bool {\n\tif httpErr, ok := err.(httpError); ok {\n\t\tif httpErr.StatusCode() == http.StatusConflict {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\nfunc retryOnConflict(ctx context.Context, action func() error) error {\n\tpolicy := []int{10, 20, 100, 300, 600}\n\n\tattempts := 0\n\tgetNextStep := func() time.Duration {\n\t\tstep := policy[attempts]\n\t\treturn time.Duration(step*5+rand.Intn(step)) * time.Microsecond\n\t}\n\n\tif err := action(); err == nil || !isKubernetesAPIConflictError(err) {\n\t\treturn err\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase <-time.After(getNextStep()):\n\t\t\terr := action()\n\t\t\tif err == nil || !isKubernetesAPIConflictError(err) {\n\t\t\t\treturn err\n\t\t\t}\n\n\t\t\tattempts++\n\t\t\tif attempts >= 4 {\n\t\t\t\treturn fmt.Errorf(\"maximum timeout reached while retrying a conflicted request: %w\", err)\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn errors.New(\"canceled\")\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "storage/kubernetes/storage_test.go",
    "content": "package kubernetes\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/conformance\"\n)\n\nconst kubeconfigPathVariableName = \"DEX_KUBERNETES_CONFIG_PATH\"\n\nfunc TestStorage(t *testing.T) {\n\tif os.Getenv(kubeconfigPathVariableName) == \"\" {\n\t\tt.Skipf(\"variable %q not set, skipping kubernetes storage tests\\n\", kubeconfigPathVariableName)\n\t}\n\n\tsuite.Run(t, new(StorageTestSuite))\n}\n\ntype StorageTestSuite struct {\n\tsuite.Suite\n\tclient *client\n}\n\nfunc expandDir(dir string) (string, error) {\n\tdir = strings.Trim(dir, `\"`)\n\tif strings.HasPrefix(dir, \"~/\") {\n\t\thomedir, err := os.UserHomeDir()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\tdir = filepath.Join(homedir, strings.TrimPrefix(dir, \"~/\"))\n\t}\n\treturn dir, nil\n}\n\nfunc (s *StorageTestSuite) SetupTest() {\n\tkubeconfigPath, err := expandDir(os.Getenv(kubeconfigPathVariableName))\n\ts.Require().NoError(err)\n\n\tconfig := Config{\n\t\tKubeConfigFile: kubeconfigPath,\n\t}\n\n\tlogger := slog.New(slog.NewTextHandler(s.T().Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\n\tkubeClient, err := config.open(logger, true)\n\ts.Require().NoError(err)\n\n\ts.client = kubeClient\n}\n\nfunc (s *StorageTestSuite) TestStorage() {\n\tnewStorage := func(t *testing.T) storage.Storage {\n\t\tfor _, resource := range []string{\n\t\t\tresourceAuthCode,\n\t\t\tresourceAuthRequest,\n\t\t\tresourceDeviceRequest,\n\t\t\tresourceDeviceToken,\n\t\t\tresourceClient,\n\t\t\tresourceRefreshToken,\n\t\t\tresourceKeys,\n\t\t\tresourcePassword,\n\t\t} {\n\t\t\tif err := s.client.deleteAll(resource); err != nil {\n\t\t\t\ts.T().Fatalf(\"delete all %q failed: %v\", resource, err)\n\t\t\t}\n\t\t}\n\t\treturn s.client\n\t}\n\n\tconformance.RunTests(s.T(), newStorage)\n\tconformance.RunConcurrencyTests(s.T(), newStorage)\n\tconformance.RunTransactionTests(s.T(), newStorage)\n}\n\nfunc TestURLFor(t *testing.T) {\n\ttests := []struct {\n\t\tapiVersion, namespace, resource, name string\n\n\t\tbaseURL string\n\t\twant    string\n\t}{\n\t\t{\n\t\t\t\"v1\", \"default\", \"pods\", \"a\",\n\t\t\t\"https://k8s.example.com\",\n\t\t\t\"https://k8s.example.com/api/v1/namespaces/default/pods/a\",\n\t\t},\n\t\t{\n\t\t\t\"foo/v1\", \"default\", \"bar\", \"a\",\n\t\t\t\"https://k8s.example.com\",\n\t\t\t\"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a\",\n\t\t},\n\t\t{\n\t\t\t\"foo/v1\", \"default\", \"bar\", \"a\",\n\t\t\t\"https://k8s.example.com/\",\n\t\t\t\"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a\",\n\t\t},\n\t\t{\n\t\t\t\"foo/v1\", \"default\", \"bar\", \"a\",\n\t\t\t\"https://k8s.example.com/\",\n\t\t\t\"https://k8s.example.com/apis/foo/v1/namespaces/default/bar/a\",\n\t\t},\n\t\t{\n\t\t\t// no namespace\n\t\t\t\"foo/v1\", \"\", \"bar\", \"a\",\n\t\t\t\"https://k8s.example.com\",\n\t\t\t\"https://k8s.example.com/apis/foo/v1/bar/a\",\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tc := &client{baseURL: test.baseURL}\n\t\tgot, err := c.urlFor(test.apiVersion, test.namespace, test.resource, test.name)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"got error: %v\", err)\n\t\t}\n\n\t\tif got != test.want {\n\t\t\tt.Errorf(\"(&client{baseURL:%q}).urlFor(%q, %q, %q, %q): expected %q got %q\",\n\t\t\t\ttest.baseURL,\n\t\t\t\ttest.apiVersion, test.namespace, test.resource, test.name,\n\t\t\t\ttest.want, got,\n\t\t\t)\n\t\t}\n\t}\n}\n\nfunc TestUpdateKeys(t *testing.T) {\n\tfakeUpdater := func(old storage.Keys) (storage.Keys, error) { return storage.Keys{}, nil }\n\n\ttests := []struct {\n\t\tname               string\n\t\tupdater            func(old storage.Keys) (storage.Keys, error)\n\t\tgetResponseCode    int\n\t\tactionResponseCode int\n\t\twantErr            bool\n\t\texactErr           error\n\t}{\n\t\t{\n\t\t\t\"Create OK test\",\n\t\t\tfakeUpdater,\n\t\t\t404,\n\t\t\t201,\n\t\t\tfalse,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"Update should be OK\",\n\t\t\tfakeUpdater,\n\t\t\t200,\n\t\t\t200,\n\t\t\tfalse,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"Create conflict should be OK\",\n\t\t\tfakeUpdater,\n\t\t\t404,\n\t\t\t409,\n\t\t\ttrue,\n\t\t\terrors.New(\"keys already created by another server instance\"),\n\t\t},\n\t\t{\n\t\t\t\"Update conflict should be OK\",\n\t\t\tfakeUpdater,\n\t\t\t200,\n\t\t\t409,\n\t\t\ttrue,\n\t\t\terrors.New(\"keys already rotated by another server instance\"),\n\t\t},\n\t\t{\n\t\t\t\"Client error is error\",\n\t\t\tfakeUpdater,\n\t\t\t404,\n\t\t\t500,\n\t\t\ttrue,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"Client error during update is error\",\n\t\t\tfakeUpdater,\n\t\t\t200,\n\t\t\t500,\n\t\t\ttrue,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"Get error is error\",\n\t\t\tfakeUpdater,\n\t\t\t500,\n\t\t\t200,\n\t\t\ttrue,\n\t\t\tnil,\n\t\t},\n\t\t{\n\t\t\t\"Updater error is error\",\n\t\t\tfunc(old storage.Keys) (storage.Keys, error) { return storage.Keys{}, fmt.Errorf(\"test\") },\n\t\t\t200,\n\t\t\t201,\n\t\t\ttrue,\n\t\t\tnil,\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tclient := newStatusCodesResponseTestClient(test.getResponseCode, test.actionResponseCode)\n\n\t\terr := client.UpdateKeys(context.TODO(), test.updater)\n\t\tif err != nil {\n\t\t\tif !test.wantErr {\n\t\t\t\tt.Fatalf(\"Test %q: %v\", test.name, err)\n\t\t\t}\n\n\t\t\tif test.exactErr != nil && test.exactErr.Error() != err.Error() {\n\t\t\t\tt.Fatalf(\"Test %q: %v, wanted: %v\", test.name, err, test.exactErr)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc newStatusCodesResponseTestClient(getResponseCode, actionResponseCode int) *client {\n\ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method == http.MethodGet {\n\t\t\tw.WriteHeader(getResponseCode)\n\t\t} else {\n\t\t\tw.WriteHeader(actionResponseCode)\n\t\t}\n\t\tw.Write([]byte(`{}`)) // Empty json is enough, we will test only response codes here\n\t}))\n\n\ttr := &http.Transport{\n\t\tTLSClientConfig: &tls.Config{InsecureSkipVerify: true},\n\t}\n\treturn &client{\n\t\tclient:  &http.Client{Transport: tr},\n\t\tbaseURL: s.URL,\n\t\tlogger:  slog.New(slog.DiscardHandler),\n\t}\n}\n\nfunc TestRetryOnConflict(t *testing.T) {\n\ttests := []struct {\n\t\tname     string\n\t\taction   func() error\n\t\texactErr string\n\t}{\n\t\t{\n\t\t\t\"Timeout reached\",\n\t\t\tfunc() error { err := httpErr{status: 409}; return error(&err) },\n\t\t\t\"maximum timeout reached while retrying a conflicted request:   Conflict: response from server \\\"\\\"\",\n\t\t},\n\t\t{\n\t\t\t\"HTTP Error\",\n\t\t\tfunc() error { err := httpErr{status: 500}; return error(&err) },\n\t\t\t\"  Internal Server Error: response from server \\\"\\\"\",\n\t\t},\n\t\t{\n\t\t\t\"Error\",\n\t\t\tfunc() error { return errors.New(\"test\") },\n\t\t\t\"test\",\n\t\t},\n\t\t{\n\t\t\t\"OK\",\n\t\t\tfunc() error { return nil },\n\t\t\t\"\",\n\t\t},\n\t}\n\n\tfor _, testCase := range tests {\n\t\tt.Run(testCase.name, func(t *testing.T) {\n\t\t\terr := retryOnConflict(context.TODO(), testCase.action)\n\t\t\tif testCase.exactErr != \"\" {\n\t\t\t\trequire.EqualError(t, err, testCase.exactErr)\n\t\t\t} else {\n\t\t\t\trequire.NoError(t, err)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestRefreshTokenLock(t *testing.T) {\n\tctx := context.Background()\n\tif os.Getenv(kubeconfigPathVariableName) == \"\" {\n\t\tt.Skipf(\"variable %q not set, skipping kubernetes storage tests\\n\", kubeconfigPathVariableName)\n\t}\n\n\tkubeconfigPath, err := expandDir(os.Getenv(kubeconfigPathVariableName))\n\trequire.NoError(t, err)\n\n\tconfig := Config{\n\t\tKubeConfigFile: kubeconfigPath,\n\t}\n\n\tlogger := slog.New(slog.DiscardHandler)\n\n\tkubeClient, err := config.open(logger, true)\n\trequire.NoError(t, err)\n\n\tlockCheckPeriod = time.Nanosecond\n\n\t// Creating a storage with an existing refresh token and offline session for the user.\n\tid := storage.NewID()\n\tr := storage.RefreshToken{\n\t\tID:          id,\n\t\tToken:       \"bar\",\n\t\tNonce:       \"foo\",\n\t\tClientID:    \"client_id\",\n\t\tConnectorID: \"client_secret\",\n\t\tScopes:      []string{\"openid\", \"email\", \"profile\"},\n\t\tCreatedAt:   time.Now().UTC().Round(time.Millisecond),\n\t\tLastUsed:    time.Now().UTC().Round(time.Millisecond),\n\t\tClaims: storage.Claims{\n\t\t\tUserID:        \"1\",\n\t\t\tUsername:      \"jane\",\n\t\t\tEmail:         \"jane.doe@example.com\",\n\t\t\tEmailVerified: true,\n\t\t\tGroups:        []string{\"a\", \"b\"},\n\t\t},\n\t\tConnectorData: []byte(`{\"some\":\"data\"}`),\n\t}\n\n\terr = kubeClient.CreateRefresh(ctx, r)\n\trequire.NoError(t, err)\n\n\tt.Run(\"Timeout lock error\", func(t *testing.T) {\n\t\terr = kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) {\n\t\t\tr.Token = \"update-result-1\"\n\t\t\terr := kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) {\n\t\t\t\tr.Token = \"timeout-err\"\n\t\t\t\treturn r, nil\n\t\t\t})\n\t\t\trequire.Equal(t, fmt.Errorf(\"timeout waiting for refresh token %s lock\", r.ID), err)\n\t\t\treturn r, nil\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\ttoken, err := kubeClient.GetRefresh(context.TODO(), r.ID)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"update-result-1\", token.Token)\n\t})\n\n\tt.Run(\"Break the lock\", func(t *testing.T) {\n\t\tvar lockBroken bool\n\t\tlockTimeout = -time.Hour\n\n\t\terr = kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) {\n\t\t\tr.Token = \"update-result-2\"\n\t\t\tif lockBroken {\n\t\t\t\treturn r, nil\n\t\t\t}\n\n\t\t\terr := kubeClient.UpdateRefreshToken(ctx, r.ID, func(r storage.RefreshToken) (storage.RefreshToken, error) {\n\t\t\t\tr.Token = \"should-break-the-lock-and-finish-updating\"\n\t\t\t\treturn r, nil\n\t\t\t})\n\t\t\trequire.NoError(t, err)\n\n\t\t\tlockBroken = true\n\t\t\treturn r, nil\n\t\t})\n\t\trequire.NoError(t, err)\n\n\t\ttoken, err := kubeClient.GetRefresh(context.TODO(), r.ID)\n\t\trequire.NoError(t, err)\n\t\t// Because concurrent update breaks the lock, the final result will be the value of the first update\n\t\trequire.Equal(t, \"update-result-2\", token.Token)\n\t})\n}\n"
  },
  {
    "path": "storage/kubernetes/transport.go",
    "content": "package kubernetes\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage/kubernetes/k8sapi\"\n)\n\n// transport is a simple http.Transport wrapper\ntype transport struct {\n\tupdateReq func(r *http.Request)\n\tbase      http.RoundTripper\n}\n\nfunc (t transport) RoundTrip(r *http.Request) (*http.Response, error) {\n\t// shallow copy of the struct\n\tr2 := new(http.Request)\n\t*r2 = *r\n\t// deep copy of the Header\n\tr2.Header = make(http.Header, len(r.Header))\n\tfor k, s := range r.Header {\n\t\tr2.Header[k] = append([]string(nil), s...)\n\t}\n\tt.updateReq(r2)\n\treturn t.base.RoundTrip(r2)\n}\n\nfunc wrapRoundTripper(base http.RoundTripper, user k8sapi.AuthInfo, inCluster bool) http.RoundTripper {\n\tif inCluster {\n\t\tinClusterTransportHelper := newInClusterTransportHelper(user)\n\t\treturn transport{\n\t\t\tupdateReq: func(r *http.Request) {\n\t\t\t\tinClusterTransportHelper.UpdateToken()\n\t\t\t\tr.Header.Set(\"Authorization\", \"Bearer \"+inClusterTransportHelper.GetToken())\n\t\t\t},\n\t\t\tbase: base,\n\t\t}\n\t}\n\n\tif user.Token != \"\" {\n\t\treturn transport{\n\t\t\tupdateReq: func(r *http.Request) {\n\t\t\t\tr.Header.Set(\"Authorization\", \"Bearer \"+user.Token)\n\t\t\t},\n\t\t\tbase: base,\n\t\t}\n\t}\n\n\tif user.Username != \"\" && user.Password != \"\" {\n\t\treturn transport{\n\t\t\tupdateReq: func(r *http.Request) {\n\t\t\t\tr.SetBasicAuth(user.Username, user.Password)\n\t\t\t},\n\t\t\tbase: base,\n\t\t}\n\t}\n\n\treturn base\n}\n\n// renewTokenPeriod is the interval after which dex will read the token from a well-known file.\n//\n//\tBy Kubernetes documentation, this interval should be at least one minute long.\n//\tKubernetes client-go v0.15+ uses 10 seconds long interval.\n//\tDex uses the reasonable value between these two.\nconst renewTokenPeriod = 30 * time.Second\n\n// inClusterTransportHelper is capable of safely updating the user token.\n//\n//\tBoundServiceAccountTokenVolume feature is enabled in Kubernetes >=1.21 by default.\n//\tWith this feature, the service account token in the pod becomes periodically updated.\n//\tTherefore, Dex needs to re-read the token from the disk after some time to be sure that it uses the valid token.\ntype inClusterTransportHelper struct {\n\tmu   sync.RWMutex\n\tinfo k8sapi.AuthInfo\n\n\texpiry time.Time\n\tnow    func() time.Time\n\n\ttokenLocation string\n}\n\nfunc newInClusterTransportHelper(info k8sapi.AuthInfo) *inClusterTransportHelper {\n\tuser := &inClusterTransportHelper{\n\t\tinfo:          info,\n\t\tnow:           time.Now,\n\t\ttokenLocation: \"/var/run/secrets/kubernetes.io/serviceaccount/token\",\n\t}\n\n\tuser.UpdateToken()\n\n\treturn user\n}\n\nfunc (c *inClusterTransportHelper) UpdateToken() {\n\tc.mu.RLock()\n\texp := c.expiry\n\tc.mu.RUnlock()\n\n\tif !c.now().After(exp) {\n\t\t// Do not need to update token yet\n\t\treturn\n\t}\n\n\ttoken, err := os.ReadFile(c.tokenLocation)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\tc.info.Token = string(token)\n\tc.expiry = c.now().Add(renewTokenPeriod)\n}\n\nfunc (c *inClusterTransportHelper) GetToken() string {\n\tc.mu.RLock()\n\tdefer c.mu.RUnlock()\n\treturn c.info.Token\n}\n"
  },
  {
    "path": "storage/kubernetes/types.go",
    "content": "package kubernetes\n\nimport (\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/kubernetes/k8sapi\"\n)\n\nconst (\n\tapiGroup = \"dex.coreos.com\"\n\n\tlegacyCRDAPIVersion = \"apiextensions.k8s.io/v1beta1\"\n\tcrdAPIVersion       = \"apiextensions.k8s.io/v1\"\n)\n\n// The set of custom resource definitions required by the storage. These are managed by\n// the storage so it can migrate itself by creating new resources.\nfunc customResourceDefinitions(apiVersion string) []k8sapi.CustomResourceDefinition {\n\tcrdMeta := k8sapi.TypeMeta{\n\t\tAPIVersion: apiVersion,\n\t\tKind:       \"CustomResourceDefinition\",\n\t}\n\n\tvar version string\n\tvar scope k8sapi.ResourceScope\n\tvar versions []k8sapi.CustomResourceDefinitionVersion\n\n\tswitch apiVersion {\n\tcase crdAPIVersion:\n\t\tpreserveUnknownFields := true\n\t\tversions = []k8sapi.CustomResourceDefinitionVersion{\n\t\t\t{\n\t\t\t\tName:    \"v1\",\n\t\t\t\tServed:  true,\n\t\t\t\tStorage: true,\n\t\t\t\tSchema: &k8sapi.CustomResourceValidation{\n\t\t\t\t\tOpenAPIV3Schema: &k8sapi.JSONSchemaProps{\n\t\t\t\t\t\tType:                   \"object\",\n\t\t\t\t\t\tXPreserveUnknownFields: &preserveUnknownFields,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\t\tscope = k8sapi.NamespaceScoped\n\tcase legacyCRDAPIVersion:\n\t\tversion = \"v1\"\n\tdefault:\n\t\tpanic(\"unknown apiVersion \" + apiVersion)\n\t}\n\n\treturn []k8sapi.CustomResourceDefinition{\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"authcodes.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"authcodes\",\n\t\t\t\t\tSingular: \"authcode\",\n\t\t\t\t\tKind:     \"AuthCode\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"authrequests.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"authrequests\",\n\t\t\t\t\tSingular: \"authrequest\",\n\t\t\t\t\tKind:     \"AuthRequest\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"oauth2clients.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"oauth2clients\",\n\t\t\t\t\tSingular: \"oauth2client\",\n\t\t\t\t\tKind:     \"OAuth2Client\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"signingkeies.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\t// `signingkeies` is an artifact from the old TPR pluralization.\n\t\t\t\t\t// Users don't directly interact with this value, hence leaving it\n\t\t\t\t\t// as is.\n\t\t\t\t\tPlural:   \"signingkeies\",\n\t\t\t\t\tSingular: \"signingkey\",\n\t\t\t\t\tKind:     \"SigningKey\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"refreshtokens.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"refreshtokens\",\n\t\t\t\t\tSingular: \"refreshtoken\",\n\t\t\t\t\tKind:     \"RefreshToken\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"passwords.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"passwords\",\n\t\t\t\t\tSingular: \"password\",\n\t\t\t\t\tKind:     \"Password\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"offlinesessionses.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"offlinesessionses\",\n\t\t\t\t\tSingular: \"offlinesessions\",\n\t\t\t\t\tKind:     \"OfflineSessions\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"connectors.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"connectors\",\n\t\t\t\t\tSingular: \"connector\",\n\t\t\t\t\tKind:     \"Connector\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"devicerequests.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"devicerequests\",\n\t\t\t\t\tSingular: \"devicerequest\",\n\t\t\t\t\tKind:     \"DeviceRequest\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"devicetokens.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"devicetokens\",\n\t\t\t\t\tSingular: \"devicetoken\",\n\t\t\t\t\tKind:     \"DeviceToken\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"useridentities.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"useridentities\",\n\t\t\t\t\tSingular: \"useridentity\",\n\t\t\t\t\tKind:     \"UserIdentity\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\t\tName: \"authsessions.dex.coreos.com\",\n\t\t\t},\n\t\t\tTypeMeta: crdMeta,\n\t\t\tSpec: k8sapi.CustomResourceDefinitionSpec{\n\t\t\t\tGroup:    apiGroup,\n\t\t\t\tVersion:  version,\n\t\t\t\tVersions: versions,\n\t\t\t\tScope:    scope,\n\t\t\t\tNames: k8sapi.CustomResourceDefinitionNames{\n\t\t\t\t\tPlural:   \"authsessions\",\n\t\t\t\t\tSingular: \"authsession\",\n\t\t\t\t\tKind:     \"AuthSession\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n}\n\n// There will only ever be a single keys resource. Maintain this by setting a\n// common name.\nconst keysName = \"openid-connect-keys\"\n\n// Client is a mirrored struct from storage with JSON struct tags and\n// Kubernetes type metadata.\ntype Client struct {\n\t// Name is a hash of the ID.\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// ID is immutable, since it's a primary key and should not be changed.\n\tID string `json:\"id,omitempty\"`\n\n\tSecret       string   `json:\"secret,omitempty\"`\n\tRedirectURIs []string `json:\"redirectURIs,omitempty\"`\n\tTrustedPeers []string `json:\"trustedPeers,omitempty\"`\n\n\tPublic bool `json:\"public\"`\n\n\tName    string `json:\"name,omitempty\"`\n\tLogoURL string `json:\"logoURL,omitempty\"`\n\n\tAllowedConnectors []string `json:\"allowedConnectors,omitempty\"`\n\n\tMFAChain []string `json:\"mfaChain,omitempty\"`\n}\n\n// ClientList is a list of Clients.\ntype ClientList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tClients         []Client `json:\"items\"`\n}\n\nfunc (cli *client) fromStorageClient(c storage.Client) Client {\n\treturn Client{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindClient,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      cli.idToName(c.ID),\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tID:                c.ID,\n\t\tSecret:            c.Secret,\n\t\tRedirectURIs:      c.RedirectURIs,\n\t\tTrustedPeers:      c.TrustedPeers,\n\t\tPublic:            c.Public,\n\t\tName:              c.Name,\n\t\tLogoURL:           c.LogoURL,\n\t\tAllowedConnectors: c.AllowedConnectors,\n\t\tMFAChain:          c.MFAChain,\n\t}\n}\n\nfunc toStorageClient(c Client) storage.Client {\n\treturn storage.Client{\n\t\tID:                c.ID,\n\t\tSecret:            c.Secret,\n\t\tRedirectURIs:      c.RedirectURIs,\n\t\tTrustedPeers:      c.TrustedPeers,\n\t\tPublic:            c.Public,\n\t\tName:              c.Name,\n\t\tLogoURL:           c.LogoURL,\n\t\tAllowedConnectors: c.AllowedConnectors,\n\t\tMFAChain:          c.MFAChain,\n\t}\n}\n\n// Claims is a mirrored struct from storage with JSON struct tags.\ntype Claims struct {\n\tUserID            string   `json:\"userID\"`\n\tUsername          string   `json:\"username\"`\n\tPreferredUsername string   `json:\"preferredUsername\"`\n\tEmail             string   `json:\"email\"`\n\tEmailVerified     bool     `json:\"emailVerified\"`\n\tGroups            []string `json:\"groups,omitempty\"`\n}\n\nfunc fromStorageClaims(i storage.Claims) Claims {\n\treturn Claims{\n\t\tUserID:            i.UserID,\n\t\tUsername:          i.Username,\n\t\tPreferredUsername: i.PreferredUsername,\n\t\tEmail:             i.Email,\n\t\tEmailVerified:     i.EmailVerified,\n\t\tGroups:            i.Groups,\n\t}\n}\n\nfunc toStorageClaims(i Claims) storage.Claims {\n\treturn storage.Claims{\n\t\tUserID:            i.UserID,\n\t\tUsername:          i.Username,\n\t\tPreferredUsername: i.PreferredUsername,\n\t\tEmail:             i.Email,\n\t\tEmailVerified:     i.EmailVerified,\n\t\tGroups:            i.Groups,\n\t}\n}\n\n// AuthRequest is a mirrored struct from storage with JSON struct tags and\n// Kubernetes type metadata.\ntype AuthRequest struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tClientID      string   `json:\"clientID\"`\n\tResponseTypes []string `json:\"responseTypes,omitempty\"`\n\tScopes        []string `json:\"scopes,omitempty\"`\n\tRedirectURI   string   `json:\"redirectURI\"`\n\n\tNonce string `json:\"nonce,omitempty\"`\n\tState string `json:\"state,omitempty\"`\n\n\t// The client has indicated that the end user must be shown an approval prompt\n\t// on all requests. The server cannot cache their initial action for subsequent\n\t// attempts.\n\tForceApprovalPrompt bool `json:\"forceApprovalPrompt,omitempty\"`\n\n\tLoggedIn bool `json:\"loggedIn\"`\n\n\t// The identity of the end user. Generally nil until the user authenticates\n\t// with a backend.\n\tClaims Claims `json:\"claims,omitempty\"`\n\t// The connector used to login the user. Set when the user authenticates.\n\tConnectorID   string `json:\"connectorID,omitempty\"`\n\tConnectorData []byte `json:\"connectorData,omitempty\"`\n\n\tExpiry time.Time `json:\"expiry\"`\n\n\tCodeChallenge       string `json:\"code_challenge,omitempty\"`\n\tCodeChallengeMethod string `json:\"code_challenge_method,omitempty\"`\n\n\tHMACKey []byte `json:\"hmac_key\"`\n\n\tMFAValidated bool `json:\"mfa_validated\"`\n\n\tPrompt   string    `json:\"prompt,omitempty\"`\n\tMaxAge   int       `json:\"maxAge\"`\n\tAuthTime time.Time `json:\"authTime,omitempty\"`\n}\n\n// AuthRequestList is a list of AuthRequests.\ntype AuthRequestList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tAuthRequests    []AuthRequest `json:\"items\"`\n}\n\nfunc toStorageAuthRequest(req AuthRequest) storage.AuthRequest {\n\ta := storage.AuthRequest{\n\t\tID:                  req.ObjectMeta.Name,\n\t\tClientID:            req.ClientID,\n\t\tResponseTypes:       req.ResponseTypes,\n\t\tScopes:              req.Scopes,\n\t\tRedirectURI:         req.RedirectURI,\n\t\tNonce:               req.Nonce,\n\t\tState:               req.State,\n\t\tForceApprovalPrompt: req.ForceApprovalPrompt,\n\t\tLoggedIn:            req.LoggedIn,\n\t\tConnectorID:         req.ConnectorID,\n\t\tConnectorData:       req.ConnectorData,\n\t\tExpiry:              req.Expiry,\n\t\tClaims:              toStorageClaims(req.Claims),\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       req.CodeChallenge,\n\t\t\tCodeChallengeMethod: req.CodeChallengeMethod,\n\t\t},\n\t\tHMACKey:      req.HMACKey,\n\t\tMFAValidated: req.MFAValidated,\n\t\tPrompt:       req.Prompt,\n\t\tMaxAge:       req.MaxAge,\n\t\tAuthTime:     req.AuthTime,\n\t}\n\treturn a\n}\n\nfunc (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest {\n\treq := AuthRequest{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindAuthRequest,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      a.ID,\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tClientID:            a.ClientID,\n\t\tResponseTypes:       a.ResponseTypes,\n\t\tScopes:              a.Scopes,\n\t\tRedirectURI:         a.RedirectURI,\n\t\tNonce:               a.Nonce,\n\t\tState:               a.State,\n\t\tLoggedIn:            a.LoggedIn,\n\t\tForceApprovalPrompt: a.ForceApprovalPrompt,\n\t\tConnectorID:         a.ConnectorID,\n\t\tConnectorData:       a.ConnectorData,\n\t\tExpiry:              a.Expiry,\n\t\tClaims:              fromStorageClaims(a.Claims),\n\t\tCodeChallenge:       a.PKCE.CodeChallenge,\n\t\tCodeChallengeMethod: a.PKCE.CodeChallengeMethod,\n\t\tHMACKey:             a.HMACKey,\n\t\tMFAValidated:        a.MFAValidated,\n\t\tPrompt:              a.Prompt,\n\t\tMaxAge:              a.MaxAge,\n\t\tAuthTime:            a.AuthTime,\n\t}\n\treturn req\n}\n\n// Password is a mirrored struct from the storage with JSON struct tags and\n// Kubernetes type metadata.\ntype Password struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// The Kubernetes name is actually an encoded version of this value.\n\t//\n\t// This field is IMMUTABLE. Do not change.\n\tEmail string `json:\"email,omitempty\"`\n\n\tHash              []byte   `json:\"hash,omitempty\"`\n\tUsername          string   `json:\"username,omitempty\"`\n\tName              string   `json:\"name,omitempty\"`\n\tPreferredUsername string   `json:\"preferredUsername,omitempty\"`\n\tEmailVerified     *bool    `json:\"emailVerified,omitempty\"`\n\tUserID            string   `json:\"userID,omitempty\"`\n\tGroups            []string `json:\"groups,omitempty\"`\n}\n\n// PasswordList is a list of Passwords.\ntype PasswordList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tPasswords       []Password `json:\"items\"`\n}\n\nfunc (cli *client) fromStoragePassword(p storage.Password) Password {\n\temail := strings.ToLower(p.Email)\n\treturn Password{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindPassword,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      cli.idToName(email),\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tEmail:             email,\n\t\tHash:              p.Hash,\n\t\tUsername:          p.Username,\n\t\tName:              p.Name,\n\t\tPreferredUsername: p.PreferredUsername,\n\t\tEmailVerified:     p.EmailVerified,\n\t\tUserID:            p.UserID,\n\t\tGroups:            p.Groups,\n\t}\n}\n\nfunc toStoragePassword(p Password) storage.Password {\n\treturn storage.Password{\n\t\tEmail:             p.Email,\n\t\tHash:              p.Hash,\n\t\tUsername:          p.Username,\n\t\tName:              p.Name,\n\t\tPreferredUsername: p.PreferredUsername,\n\t\tEmailVerified:     p.EmailVerified,\n\t\tUserID:            p.UserID,\n\t\tGroups:            p.Groups,\n\t}\n}\n\n// AuthCode is a mirrored struct from storage with JSON struct tags and\n// Kubernetes type metadata.\ntype AuthCode struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tClientID    string   `json:\"clientID\"`\n\tScopes      []string `json:\"scopes,omitempty\"`\n\tRedirectURI string   `json:\"redirectURI\"`\n\n\tNonce string `json:\"nonce,omitempty\"`\n\tState string `json:\"state,omitempty\"`\n\n\tClaims Claims `json:\"claims,omitempty\"`\n\n\tConnectorID   string `json:\"connectorID,omitempty\"`\n\tConnectorData []byte `json:\"connectorData,omitempty\"`\n\n\tExpiry time.Time `json:\"expiry\"`\n\n\tCodeChallenge       string `json:\"code_challenge,omitempty\"`\n\tCodeChallengeMethod string `json:\"code_challenge_method,omitempty\"`\n\n\tAuthTime time.Time `json:\"authTime,omitempty\"`\n}\n\n// AuthCodeList is a list of AuthCodes.\ntype AuthCodeList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tAuthCodes       []AuthCode `json:\"items\"`\n}\n\nfunc (cli *client) fromStorageAuthCode(a storage.AuthCode) AuthCode {\n\treturn AuthCode{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindAuthCode,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      a.ID,\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tClientID:            a.ClientID,\n\t\tRedirectURI:         a.RedirectURI,\n\t\tConnectorID:         a.ConnectorID,\n\t\tConnectorData:       a.ConnectorData,\n\t\tNonce:               a.Nonce,\n\t\tScopes:              a.Scopes,\n\t\tClaims:              fromStorageClaims(a.Claims),\n\t\tExpiry:              a.Expiry,\n\t\tCodeChallenge:       a.PKCE.CodeChallenge,\n\t\tCodeChallengeMethod: a.PKCE.CodeChallengeMethod,\n\t\tAuthTime:            a.AuthTime,\n\t}\n}\n\nfunc toStorageAuthCode(a AuthCode) storage.AuthCode {\n\treturn storage.AuthCode{\n\t\tID:            a.ObjectMeta.Name,\n\t\tClientID:      a.ClientID,\n\t\tRedirectURI:   a.RedirectURI,\n\t\tConnectorID:   a.ConnectorID,\n\t\tConnectorData: a.ConnectorData,\n\t\tNonce:         a.Nonce,\n\t\tScopes:        a.Scopes,\n\t\tClaims:        toStorageClaims(a.Claims),\n\t\tExpiry:        a.Expiry,\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       a.CodeChallenge,\n\t\t\tCodeChallengeMethod: a.CodeChallengeMethod,\n\t\t},\n\t\tAuthTime: a.AuthTime,\n\t}\n}\n\n// RefreshToken is a mirrored struct from storage with JSON struct tags and\n// Kubernetes type metadata.\ntype RefreshToken struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tCreatedAt time.Time\n\tLastUsed  time.Time\n\n\tClientID string   `json:\"clientID\"`\n\tScopes   []string `json:\"scopes,omitempty\"`\n\n\tToken         string `json:\"token,omitempty\"`\n\tObsoleteToken string `json:\"obsoleteToken,omitempty\"`\n\n\tNonce string `json:\"nonce,omitempty\"`\n\n\tClaims        Claims `json:\"claims,omitempty\"`\n\tConnectorID   string `json:\"connectorID,omitempty\"`\n\tConnectorData []byte `json:\"connectorData,omitempty\"`\n}\n\n// RefreshList is a list of refresh tokens.\ntype RefreshList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tRefreshTokens   []RefreshToken `json:\"items\"`\n}\n\nfunc toStorageRefreshToken(r RefreshToken) storage.RefreshToken {\n\treturn storage.RefreshToken{\n\t\tID:            r.ObjectMeta.Name,\n\t\tToken:         r.Token,\n\t\tObsoleteToken: r.ObsoleteToken,\n\t\tCreatedAt:     r.CreatedAt,\n\t\tLastUsed:      r.LastUsed,\n\t\tClientID:      r.ClientID,\n\t\tConnectorID:   r.ConnectorID,\n\t\tConnectorData: r.ConnectorData,\n\t\tScopes:        r.Scopes,\n\t\tNonce:         r.Nonce,\n\t\tClaims:        toStorageClaims(r.Claims),\n\t}\n}\n\nfunc (cli *client) fromStorageRefreshToken(r storage.RefreshToken) RefreshToken {\n\treturn RefreshToken{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindRefreshToken,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      r.ID,\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tToken:         r.Token,\n\t\tObsoleteToken: r.ObsoleteToken,\n\t\tCreatedAt:     r.CreatedAt,\n\t\tLastUsed:      r.LastUsed,\n\t\tClientID:      r.ClientID,\n\t\tConnectorID:   r.ConnectorID,\n\t\tConnectorData: r.ConnectorData,\n\t\tScopes:        r.Scopes,\n\t\tNonce:         r.Nonce,\n\t\tClaims:        fromStorageClaims(r.Claims),\n\t}\n}\n\n// Keys is a mirrored struct from storage with JSON struct tags and Kubernetes\n// type metadata.\ntype Keys struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\t// Key for creating and verifying signatures. These may be nil.\n\tSigningKey    *jose.JSONWebKey `json:\"signingKey,omitempty\"`\n\tSigningKeyPub *jose.JSONWebKey `json:\"signingKeyPub,omitempty\"`\n\t// Old signing keys which have been rotated but can still be used to validate\n\t// existing signatures.\n\tVerificationKeys []storage.VerificationKey `json:\"verificationKeys,omitempty\"`\n\n\t// The next time the signing key will rotate.\n\t//\n\t// For caching purposes, implementations MUST NOT update keys before this time.\n\tNextRotation time.Time `json:\"nextRotation\"`\n}\n\nfunc (cli *client) fromStorageKeys(keys storage.Keys) Keys {\n\treturn Keys{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindKeys,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      keysName,\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tSigningKey:       keys.SigningKey,\n\t\tSigningKeyPub:    keys.SigningKeyPub,\n\t\tVerificationKeys: keys.VerificationKeys,\n\t\tNextRotation:     keys.NextRotation,\n\t}\n}\n\nfunc toStorageKeys(keys Keys) storage.Keys {\n\treturn storage.Keys{\n\t\tSigningKey:       keys.SigningKey,\n\t\tSigningKeyPub:    keys.SigningKeyPub,\n\t\tVerificationKeys: keys.VerificationKeys,\n\t\tNextRotation:     keys.NextRotation,\n\t}\n}\n\n// OfflineSessions is a mirrored struct from storage with JSON struct tags and Kubernetes\n// type metadata.\ntype OfflineSessions struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tUserID        string                              `json:\"userID,omitempty\"`\n\tConnID        string                              `json:\"connID,omitempty\"`\n\tRefresh       map[string]*storage.RefreshTokenRef `json:\"refresh,omitempty\"`\n\tConnectorData []byte                              `json:\"connectorData,omitempty\"`\n}\n\nfunc (cli *client) fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions {\n\treturn OfflineSessions{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindOfflineSessions,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      cli.offlineTokenName(o.UserID, o.ConnID),\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tUserID:        o.UserID,\n\t\tConnID:        o.ConnID,\n\t\tRefresh:       o.Refresh,\n\t\tConnectorData: o.ConnectorData,\n\t}\n}\n\nfunc toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions {\n\ts := storage.OfflineSessions{\n\t\tUserID:        o.UserID,\n\t\tConnID:        o.ConnID,\n\t\tRefresh:       o.Refresh,\n\t\tConnectorData: o.ConnectorData,\n\t}\n\tif s.Refresh == nil {\n\t\t// Server code assumes this will be non-nil.\n\t\ts.Refresh = make(map[string]*storage.RefreshTokenRef)\n\t}\n\treturn s\n}\n\n// Connector is a mirrored struct from storage with JSON struct tags and Kubernetes\n// type metadata.\ntype Connector struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tID   string `json:\"id,omitempty\"`\n\tType string `json:\"type,omitempty\"`\n\tName string `json:\"name,omitempty\"`\n\t// Config holds connector specific configuration information\n\tConfig []byte `json:\"config,omitempty\"`\n\t// GrantTypes is a list of grant types that this connector is allowed to be used with.\n\tGrantTypes []string `json:\"grantTypes,omitempty\"`\n}\n\nfunc (cli *client) fromStorageConnector(c storage.Connector) Connector {\n\treturn Connector{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindConnector,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      c.ID,\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tID:         c.ID,\n\t\tType:       c.Type,\n\t\tName:       c.Name,\n\t\tConfig:     c.Config,\n\t\tGrantTypes: c.GrantTypes,\n\t}\n}\n\nfunc toStorageConnector(c Connector) storage.Connector {\n\treturn storage.Connector{\n\t\tID:              c.ID,\n\t\tType:            c.Type,\n\t\tName:            c.Name,\n\t\tResourceVersion: c.ObjectMeta.ResourceVersion,\n\t\tConfig:          c.Config,\n\t\tGrantTypes:      c.GrantTypes,\n\t}\n}\n\n// ConnectorList is a list of Connectors.\ntype ConnectorList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tConnectors      []Connector `json:\"items\"`\n}\n\n// DeviceRequest is a mirrored struct from storage with JSON struct tags and\n// Kubernetes type metadata.\ntype DeviceRequest struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tDeviceCode   string    `json:\"device_code,omitempty\"`\n\tClientID     string    `json:\"client_id,omitempty\"`\n\tClientSecret string    `json:\"client_secret,omitempty\"`\n\tScopes       []string  `json:\"scopes,omitempty\"`\n\tExpiry       time.Time `json:\"expiry\"`\n}\n\n// DeviceRequestList is a list of DeviceRequests.\ntype DeviceRequestList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tDeviceRequests  []DeviceRequest `json:\"items\"`\n}\n\nfunc (cli *client) fromStorageDeviceRequest(a storage.DeviceRequest) DeviceRequest {\n\treq := DeviceRequest{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindDeviceRequest,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      strings.ToLower(a.UserCode),\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tDeviceCode:   a.DeviceCode,\n\t\tClientID:     a.ClientID,\n\t\tClientSecret: a.ClientSecret,\n\t\tScopes:       a.Scopes,\n\t\tExpiry:       a.Expiry,\n\t}\n\treturn req\n}\n\nfunc toStorageDeviceRequest(req DeviceRequest) storage.DeviceRequest {\n\treturn storage.DeviceRequest{\n\t\tUserCode:     strings.ToUpper(req.ObjectMeta.Name),\n\t\tDeviceCode:   req.DeviceCode,\n\t\tClientID:     req.ClientID,\n\t\tClientSecret: req.ClientSecret,\n\t\tScopes:       req.Scopes,\n\t\tExpiry:       req.Expiry,\n\t}\n}\n\n// DeviceToken is a mirrored struct from storage with JSON struct tags and\n// Kubernetes type metadata.\ntype DeviceToken struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tStatus              string    `json:\"status,omitempty\"`\n\tToken               string    `json:\"token,omitempty\"`\n\tExpiry              time.Time `json:\"expiry\"`\n\tLastRequestTime     time.Time `json:\"last_request\"`\n\tPollIntervalSeconds int       `json:\"poll_interval\"`\n\tCodeChallenge       string    `json:\"code_challenge,omitempty\"`\n\tCodeChallengeMethod string    `json:\"code_challenge_method,omitempty\"`\n}\n\n// DeviceTokenList is a list of DeviceTokens.\ntype DeviceTokenList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tDeviceTokens    []DeviceToken `json:\"items\"`\n}\n\nfunc (cli *client) fromStorageDeviceToken(t storage.DeviceToken) DeviceToken {\n\treq := DeviceToken{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindDeviceToken,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      t.DeviceCode,\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tStatus:              t.Status,\n\t\tToken:               t.Token,\n\t\tExpiry:              t.Expiry,\n\t\tLastRequestTime:     t.LastRequestTime,\n\t\tPollIntervalSeconds: t.PollIntervalSeconds,\n\t\tCodeChallenge:       t.PKCE.CodeChallenge,\n\t\tCodeChallengeMethod: t.PKCE.CodeChallengeMethod,\n\t}\n\treturn req\n}\n\nfunc toStorageDeviceToken(t DeviceToken) storage.DeviceToken {\n\treturn storage.DeviceToken{\n\t\tDeviceCode:          t.ObjectMeta.Name,\n\t\tStatus:              t.Status,\n\t\tToken:               t.Token,\n\t\tExpiry:              t.Expiry,\n\t\tLastRequestTime:     t.LastRequestTime,\n\t\tPollIntervalSeconds: t.PollIntervalSeconds,\n\t\tPKCE: storage.PKCE{\n\t\t\tCodeChallenge:       t.CodeChallenge,\n\t\t\tCodeChallengeMethod: t.CodeChallengeMethod,\n\t\t},\n\t}\n}\n\n// UserIdentity is a mirrored struct from storage with JSON struct tags and Kubernetes\n// type metadata.\ntype UserIdentity struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tUserID       string                        `json:\"userID,omitempty\"`\n\tConnectorID  string                        `json:\"connectorID,omitempty\"`\n\tClaims       Claims                        `json:\"claims,omitempty\"`\n\tConsents     map[string][]string           `json:\"consents,omitempty\"`\n\tMFASecrets   map[string]*storage.MFASecret `json:\"mfaSecrets,omitempty\"`\n\tCreatedAt    time.Time                     `json:\"createdAt,omitempty\"`\n\tLastLogin    time.Time                     `json:\"lastLogin,omitempty\"`\n\tBlockedUntil time.Time                     `json:\"blockedUntil,omitempty\"`\n}\n\n// UserIdentityList is a list of UserIdentities.\ntype UserIdentityList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tUserIdentities  []UserIdentity `json:\"items\"`\n}\n\nfunc (cli *client) fromStorageUserIdentity(u storage.UserIdentity) UserIdentity {\n\treturn UserIdentity{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindUserIdentity,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      cli.offlineTokenName(u.UserID, u.ConnectorID),\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tUserID:       u.UserID,\n\t\tConnectorID:  u.ConnectorID,\n\t\tClaims:       fromStorageClaims(u.Claims),\n\t\tConsents:     u.Consents,\n\t\tMFASecrets:   u.MFASecrets,\n\t\tCreatedAt:    u.CreatedAt,\n\t\tLastLogin:    u.LastLogin,\n\t\tBlockedUntil: u.BlockedUntil,\n\t}\n}\n\nfunc toStorageUserIdentity(u UserIdentity) storage.UserIdentity {\n\ts := storage.UserIdentity{\n\t\tUserID:       u.UserID,\n\t\tConnectorID:  u.ConnectorID,\n\t\tClaims:       toStorageClaims(u.Claims),\n\t\tConsents:     u.Consents,\n\t\tMFASecrets:   u.MFASecrets,\n\t\tCreatedAt:    u.CreatedAt,\n\t\tLastLogin:    u.LastLogin,\n\t\tBlockedUntil: u.BlockedUntil,\n\t}\n\tif s.Consents == nil {\n\t\t// Server code assumes this will be non-nil.\n\t\ts.Consents = make(map[string][]string)\n\t}\n\treturn s\n}\n\n// AuthSession is a Kubernetes representation of a storage AuthSession.\ntype AuthSession struct {\n\tk8sapi.TypeMeta   `json:\",inline\"`\n\tk8sapi.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tUserID         string                              `json:\"userID,omitempty\"`\n\tConnectorID    string                              `json:\"connectorID,omitempty\"`\n\tNonce          string                              `json:\"nonce,omitempty\"`\n\tClientStates   map[string]*storage.ClientAuthState `json:\"clientStates,omitempty\"`\n\tCreatedAt      time.Time                           `json:\"createdAt,omitempty\"`\n\tLastActivity   time.Time                           `json:\"lastActivity,omitempty\"`\n\tIPAddress      string                              `json:\"ipAddress,omitempty\"`\n\tUserAgent      string                              `json:\"userAgent,omitempty\"`\n\tAbsoluteExpiry time.Time                           `json:\"absoluteExpiry,omitempty\"`\n\tIdleExpiry     time.Time                           `json:\"idleExpiry,omitempty\"`\n}\n\n// AuthSessionList is a list of AuthSessions.\ntype AuthSessionList struct {\n\tk8sapi.TypeMeta `json:\",inline\"`\n\tk8sapi.ListMeta `json:\"metadata,omitempty\"`\n\tAuthSessions    []AuthSession `json:\"items\"`\n}\n\nfunc (cli *client) fromStorageAuthSession(s storage.AuthSession) AuthSession {\n\treturn AuthSession{\n\t\tTypeMeta: k8sapi.TypeMeta{\n\t\t\tKind:       kindAuthSession,\n\t\t\tAPIVersion: cli.apiVersion,\n\t\t},\n\t\tObjectMeta: k8sapi.ObjectMeta{\n\t\t\tName:      offlineTokenName(s.UserID, s.ConnectorID, cli.hash),\n\t\t\tNamespace: cli.namespace,\n\t\t},\n\t\tUserID:         s.UserID,\n\t\tConnectorID:    s.ConnectorID,\n\t\tNonce:          s.Nonce,\n\t\tClientStates:   s.ClientStates,\n\t\tCreatedAt:      s.CreatedAt,\n\t\tLastActivity:   s.LastActivity,\n\t\tIPAddress:      s.IPAddress,\n\t\tUserAgent:      s.UserAgent,\n\t\tAbsoluteExpiry: s.AbsoluteExpiry,\n\t\tIdleExpiry:     s.IdleExpiry,\n\t}\n}\n\nfunc toStorageAuthSession(s AuthSession) storage.AuthSession {\n\tresult := storage.AuthSession{\n\t\tUserID:         s.UserID,\n\t\tConnectorID:    s.ConnectorID,\n\t\tNonce:          s.Nonce,\n\t\tClientStates:   s.ClientStates,\n\t\tCreatedAt:      s.CreatedAt,\n\t\tLastActivity:   s.LastActivity,\n\t\tIPAddress:      s.IPAddress,\n\t\tUserAgent:      s.UserAgent,\n\t\tAbsoluteExpiry: s.AbsoluteExpiry,\n\t\tIdleExpiry:     s.IdleExpiry,\n\t}\n\tif result.ClientStates == nil {\n\t\tresult.ClientStates = make(map[string]*storage.ClientAuthState)\n\t}\n\treturn result\n}\n"
  },
  {
    "path": "storage/memory/memory.go",
    "content": "// Package memory provides an in memory implementation of the storage interface.\npackage memory\n\nimport (\n\t\"context\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\nvar _ storage.Storage = (*memStorage)(nil)\n\n// New returns an in memory storage.\nfunc New(logger *slog.Logger) storage.Storage {\n\treturn &memStorage{\n\t\tclients:         make(map[string]storage.Client),\n\t\tauthCodes:       make(map[string]storage.AuthCode),\n\t\trefreshTokens:   make(map[string]storage.RefreshToken),\n\t\tauthReqs:        make(map[string]storage.AuthRequest),\n\t\tpasswords:       make(map[string]storage.Password),\n\t\tofflineSessions: make(map[compositeKeyID]storage.OfflineSessions),\n\t\tuserIdentities:  make(map[compositeKeyID]storage.UserIdentity),\n\t\tauthSessions:    make(map[compositeKeyID]storage.AuthSession),\n\t\tconnectors:      make(map[string]storage.Connector),\n\t\tdeviceRequests:  make(map[string]storage.DeviceRequest),\n\t\tdeviceTokens:    make(map[string]storage.DeviceToken),\n\t\tlogger:          logger,\n\t}\n}\n\n// Config is an implementation of a storage configuration.\n//\n// TODO(ericchiang): Actually define a storage config interface and have registration.\ntype Config struct { // The in memory implementation has no config.\n}\n\n// Open always returns a new in memory storage.\nfunc (c *Config) Open(logger *slog.Logger) (storage.Storage, error) {\n\treturn New(logger), nil\n}\n\ntype memStorage struct {\n\tmu sync.Mutex\n\n\tclients         map[string]storage.Client\n\tauthCodes       map[string]storage.AuthCode\n\trefreshTokens   map[string]storage.RefreshToken\n\tauthReqs        map[string]storage.AuthRequest\n\tpasswords       map[string]storage.Password\n\tofflineSessions map[compositeKeyID]storage.OfflineSessions\n\tuserIdentities  map[compositeKeyID]storage.UserIdentity\n\tauthSessions    map[compositeKeyID]storage.AuthSession\n\tconnectors      map[string]storage.Connector\n\tdeviceRequests  map[string]storage.DeviceRequest\n\tdeviceTokens    map[string]storage.DeviceToken\n\n\tkeys storage.Keys\n\n\tlogger *slog.Logger\n}\n\ntype compositeKeyID struct {\n\tuserID string\n\tconnID string\n}\n\nfunc (s *memStorage) tx(f func()) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tf()\n}\n\nfunc (s *memStorage) Close() error { return nil }\n\nfunc (s *memStorage) GarbageCollect(ctx context.Context, now time.Time) (result storage.GCResult, err error) {\n\ts.tx(func() {\n\t\tfor id, a := range s.authCodes {\n\t\t\tif now.After(a.Expiry) {\n\t\t\t\tdelete(s.authCodes, id)\n\t\t\t\tresult.AuthCodes++\n\t\t\t}\n\t\t}\n\t\tfor id, a := range s.authReqs {\n\t\t\tif now.After(a.Expiry) {\n\t\t\t\tdelete(s.authReqs, id)\n\t\t\t\tresult.AuthRequests++\n\t\t\t}\n\t\t}\n\t\tfor id, a := range s.deviceRequests {\n\t\t\tif now.After(a.Expiry) {\n\t\t\t\tdelete(s.deviceRequests, id)\n\t\t\t\tresult.DeviceRequests++\n\t\t\t}\n\t\t}\n\t\tfor id, a := range s.deviceTokens {\n\t\t\tif now.After(a.Expiry) {\n\t\t\t\tdelete(s.deviceTokens, id)\n\t\t\t\tresult.DeviceTokens++\n\t\t\t}\n\t\t}\n\t\tfor id, a := range s.authSessions {\n\t\t\tif now.After(a.AbsoluteExpiry) || now.After(a.IdleExpiry) {\n\t\t\t\tdelete(s.authSessions, id)\n\t\t\t\tresult.AuthSessions++\n\t\t\t}\n\t\t}\n\t})\n\treturn result, nil\n}\n\nfunc (s *memStorage) CreateClient(ctx context.Context, c storage.Client) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.clients[c.ID]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.clients[c.ID] = c\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateAuthCode(ctx context.Context, c storage.AuthCode) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.authCodes[c.ID]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.authCodes[c.ID] = c\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateRefresh(ctx context.Context, r storage.RefreshToken) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.refreshTokens[r.ID]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.refreshTokens[r.ID] = r\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.authReqs[a.ID]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.authReqs[a.ID] = a\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreatePassword(ctx context.Context, p storage.Password) (err error) {\n\tlowerEmail := strings.ToLower(p.Email)\n\ts.tx(func() {\n\t\tif _, ok := s.passwords[lowerEmail]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.passwords[lowerEmail] = p\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateOfflineSessions(ctx context.Context, o storage.OfflineSessions) (err error) {\n\tid := compositeKeyID{\n\t\tuserID: o.UserID,\n\t\tconnID: o.ConnID,\n\t}\n\ts.tx(func() {\n\t\tif _, ok := s.offlineSessions[id]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.offlineSessions[id] = o\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) (err error) {\n\tid := compositeKeyID{\n\t\tuserID: u.UserID,\n\t\tconnID: u.ConnectorID,\n\t}\n\ts.tx(func() {\n\t\tif _, ok := s.userIdentities[id]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.userIdentities[id] = u\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetUserIdentity(ctx context.Context, userID, connectorID string) (u storage.UserIdentity, err error) {\n\tid := compositeKeyID{\n\t\tuserID: userID,\n\t\tconnID: connectorID,\n\t}\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif u, ok = s.userIdentities[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) (err error) {\n\tid := compositeKeyID{\n\t\tuserID: userID,\n\t\tconnID: connectorID,\n\t}\n\ts.tx(func() {\n\t\tr, ok := s.userIdentities[id]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif r, err = updater(r); err == nil {\n\t\t\ts.userIdentities[id] = r\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteUserIdentity(ctx context.Context, userID, connectorID string) (err error) {\n\tid := compositeKeyID{\n\t\tuserID: userID,\n\t\tconnID: connectorID,\n\t}\n\ts.tx(func() {\n\t\tif _, ok := s.userIdentities[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.userIdentities, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) ListUserIdentities(ctx context.Context) (identities []storage.UserIdentity, err error) {\n\ts.tx(func() {\n\t\tfor _, u := range s.userIdentities {\n\t\t\tidentities = append(identities, u)\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) ListAuthSessions(ctx context.Context) (sessions []storage.AuthSession, err error) {\n\ts.tx(func() {\n\t\tfor _, session := range s.authSessions {\n\t\t\tsessions = append(sessions, session)\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateAuthSession(ctx context.Context, session storage.AuthSession) (err error) {\n\tid := compositeKeyID{userID: session.UserID, connID: session.ConnectorID}\n\ts.tx(func() {\n\t\tif _, ok := s.authSessions[id]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.authSessions[id] = session\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetAuthSession(ctx context.Context, userID, connectorID string) (session storage.AuthSession, err error) {\n\tid := compositeKeyID{userID: userID, connID: connectorID}\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif session, ok = s.authSessions[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) (err error) {\n\tid := compositeKeyID{userID: userID, connID: connectorID}\n\ts.tx(func() {\n\t\tr, ok := s.authSessions[id]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif r, err = updater(r); err == nil {\n\t\t\ts.authSessions[id] = r\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteAuthSession(ctx context.Context, userID, connectorID string) (err error) {\n\tid := compositeKeyID{userID: userID, connID: connectorID}\n\ts.tx(func() {\n\t\tif _, ok := s.authSessions[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.authSessions, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateConnector(ctx context.Context, connector storage.Connector) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.connectors[connector.ID]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.connectors[connector.ID] = connector\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetAuthCode(ctx context.Context, id string) (c storage.AuthCode, err error) {\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif c, ok = s.authCodes[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetPassword(ctx context.Context, email string) (p storage.Password, err error) {\n\temail = strings.ToLower(email)\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif p, ok = s.passwords[email]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetClient(ctx context.Context, id string) (client storage.Client, err error) {\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif client, ok = s.clients[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetKeys(ctx context.Context) (keys storage.Keys, err error) {\n\ts.tx(func() { keys = s.keys })\n\treturn\n}\n\nfunc (s *memStorage) GetRefresh(ctx context.Context, id string) (tok storage.RefreshToken, err error) {\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif tok, ok = s.refreshTokens[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetAuthRequest(ctx context.Context, id string) (req storage.AuthRequest, err error) {\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif req, ok = s.authReqs[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetOfflineSessions(ctx context.Context, userID string, connID string) (o storage.OfflineSessions, err error) {\n\tid := compositeKeyID{\n\t\tuserID: userID,\n\t\tconnID: connID,\n\t}\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif o, ok = s.offlineSessions[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetConnector(ctx context.Context, id string) (connector storage.Connector, err error) {\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif connector, ok = s.connectors[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) ListClients(ctx context.Context) (clients []storage.Client, err error) {\n\ts.tx(func() {\n\t\tfor _, client := range s.clients {\n\t\t\tclients = append(clients, client)\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) ListRefreshTokens(ctx context.Context) (tokens []storage.RefreshToken, err error) {\n\ts.tx(func() {\n\t\tfor _, refresh := range s.refreshTokens {\n\t\t\ttokens = append(tokens, refresh)\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) ListPasswords(ctx context.Context) (passwords []storage.Password, err error) {\n\ts.tx(func() {\n\t\tfor _, password := range s.passwords {\n\t\t\tpasswords = append(passwords, password)\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) ListConnectors(ctx context.Context) (conns []storage.Connector, err error) {\n\ts.tx(func() {\n\t\tfor _, c := range s.connectors {\n\t\t\tconns = append(conns, c)\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeletePassword(ctx context.Context, email string) (err error) {\n\temail = strings.ToLower(email)\n\ts.tx(func() {\n\t\tif _, ok := s.passwords[email]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.passwords, email)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteClient(ctx context.Context, id string) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.clients[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.clients, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteRefresh(ctx context.Context, id string) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.refreshTokens[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.refreshTokens, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteAuthCode(ctx context.Context, id string) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.authCodes[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.authCodes, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteAuthRequest(ctx context.Context, id string) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.authReqs[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.authReqs, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteOfflineSessions(ctx context.Context, userID string, connID string) (err error) {\n\tid := compositeKeyID{\n\t\tuserID: userID,\n\t\tconnID: connID,\n\t}\n\ts.tx(func() {\n\t\tif _, ok := s.offlineSessions[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.offlineSessions, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) DeleteConnector(ctx context.Context, id string) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.connectors[id]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tdelete(s.connectors, id)\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) (err error) {\n\ts.tx(func() {\n\t\tclient, ok := s.clients[id]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif client, err = updater(client); err == nil {\n\t\t\ts.clients[id] = client\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) (err error) {\n\ts.tx(func() {\n\t\tvar keys storage.Keys\n\t\tif keys, err = updater(s.keys); err == nil {\n\t\t\ts.keys = keys\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateAuthRequest(ctx context.Context, id string, updater func(old storage.AuthRequest) (storage.AuthRequest, error)) (err error) {\n\ts.tx(func() {\n\t\treq, ok := s.authReqs[id]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif req, err = updater(req); err == nil {\n\t\t\ts.authReqs[id] = req\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdatePassword(ctx context.Context, email string, updater func(p storage.Password) (storage.Password, error)) (err error) {\n\temail = strings.ToLower(email)\n\ts.tx(func() {\n\t\treq, ok := s.passwords[email]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif req, err = updater(req); err == nil {\n\t\t\ts.passwords[email] = req\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateRefreshToken(ctx context.Context, id string, updater func(p storage.RefreshToken) (storage.RefreshToken, error)) (err error) {\n\ts.tx(func() {\n\t\tr, ok := s.refreshTokens[id]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif r, err = updater(r); err == nil {\n\t\t\ts.refreshTokens[id] = r\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(o storage.OfflineSessions) (storage.OfflineSessions, error)) (err error) {\n\tid := compositeKeyID{\n\t\tuserID: userID,\n\t\tconnID: connID,\n\t}\n\ts.tx(func() {\n\t\tr, ok := s.offlineSessions[id]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif r, err = updater(r); err == nil {\n\t\t\ts.offlineSessions[id] = r\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateConnector(ctx context.Context, id string, updater func(c storage.Connector) (storage.Connector, error)) (err error) {\n\ts.tx(func() {\n\t\tr, ok := s.connectors[id]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif r, err = updater(r); err == nil {\n\t\t\ts.connectors[id] = r\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.deviceRequests[d.UserCode]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.deviceRequests[d.UserCode] = d\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetDeviceRequest(ctx context.Context, userCode string) (req storage.DeviceRequest, err error) {\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif req, ok = s.deviceRequests[userCode]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) (err error) {\n\ts.tx(func() {\n\t\tif _, ok := s.deviceTokens[t.DeviceCode]; ok {\n\t\t\terr = storage.ErrAlreadyExists\n\t\t} else {\n\t\t\ts.deviceTokens[t.DeviceCode] = t\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) GetDeviceToken(ctx context.Context, deviceCode string) (t storage.DeviceToken, err error) {\n\ts.tx(func() {\n\t\tvar ok bool\n\t\tif t, ok = s.deviceTokens[deviceCode]; !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t})\n\treturn\n}\n\nfunc (s *memStorage) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(p storage.DeviceToken) (storage.DeviceToken, error)) (err error) {\n\ts.tx(func() {\n\t\tr, ok := s.deviceTokens[deviceCode]\n\t\tif !ok {\n\t\t\terr = storage.ErrNotFound\n\t\t\treturn\n\t\t}\n\t\tif r, err = updater(r); err == nil {\n\t\t\ts.deviceTokens[deviceCode] = r\n\t\t}\n\t})\n\treturn\n}\n"
  },
  {
    "path": "storage/memory/memory_test.go",
    "content": "package memory\n\nimport (\n\t\"log/slog\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/conformance\"\n)\n\nfunc TestStorage(t *testing.T) {\n\tnewStorage := func(t *testing.T) storage.Storage {\n\t\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\n\t\treturn New(logger)\n\t}\n\tconformance.RunTests(t, newStorage)\n\tconformance.RunConcurrencyTests(t, newStorage)\n}\n"
  },
  {
    "path": "storage/memory/static_test.go",
    "content": "package memory\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\nfunc TestStaticClients(t *testing.T) {\n\tctx := context.Background()\n\tlogger := slog.New(slog.DiscardHandler)\n\tbacking := New(logger)\n\n\tc1 := storage.Client{ID: \"foo\", Secret: \"foo_secret\"}\n\tc2 := storage.Client{ID: \"bar\", Secret: \"bar_secret\"}\n\tc3 := storage.Client{ID: \"spam\", Secret: \"spam_secret\"}\n\n\tbacking.CreateClient(ctx, c1)\n\ts := storage.WithStaticClients(backing, []storage.Client{c2})\n\n\ttests := []struct {\n\t\tname    string\n\t\taction  func() error\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"get client from static storage\",\n\t\t\taction: func() error {\n\t\t\t\t_, err := s.GetClient(ctx, c2.ID)\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"get client from backing storage\",\n\t\t\taction: func() error {\n\t\t\t\t_, err := s.GetClient(ctx, c1.ID)\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"update static client\",\n\t\t\taction: func() error {\n\t\t\t\tupdater := func(c storage.Client) (storage.Client, error) {\n\t\t\t\t\tc.Secret = \"new_\" + c.Secret\n\t\t\t\t\treturn c, nil\n\t\t\t\t}\n\t\t\t\treturn s.UpdateClient(ctx, c2.ID, updater)\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"update non-static client\",\n\t\t\taction: func() error {\n\t\t\t\tupdater := func(c storage.Client) (storage.Client, error) {\n\t\t\t\t\tc.Secret = \"new_\" + c.Secret\n\t\t\t\t\treturn c, nil\n\t\t\t\t}\n\t\t\t\treturn s.UpdateClient(ctx, c1.ID, updater)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"list clients\",\n\t\t\taction: func() error {\n\t\t\t\tclients, err := s.ListClients(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif n := len(clients); n != 2 {\n\t\t\t\t\treturn fmt.Errorf(\"expected 2 clients got %d\", n)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"create client\",\n\t\t\taction: func() error {\n\t\t\t\treturn s.CreateClient(ctx, c3)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\terr := tc.action()\n\t\tif err != nil && !tc.wantErr {\n\t\t\tt.Errorf(\"%s: %v\", tc.name, err)\n\t\t}\n\t\tif err == nil && tc.wantErr {\n\t\t\tt.Errorf(\"%s: expected error, didn't get one\", tc.name)\n\t\t}\n\t}\n}\n\nfunc TestStaticPasswords(t *testing.T) {\n\tctx := context.Background()\n\tlogger := slog.New(slog.DiscardHandler)\n\tbacking := New(logger)\n\n\tp1 := storage.Password{Email: \"foo@example.com\", Username: \"foo_secret\"}\n\tp2 := storage.Password{Email: \"bar@example.com\", Username: \"bar_secret\"}\n\tp3 := storage.Password{Email: \"spam@example.com\", Username: \"spam_secret\"}\n\tp4 := storage.Password{Email: \"Spam@example.com\", Username: \"Spam_secret\"}\n\n\tbacking.CreatePassword(ctx, p1)\n\ts := storage.WithStaticPasswords(backing, []storage.Password{p2}, logger)\n\n\ttests := []struct {\n\t\tname    string\n\t\taction  func() error\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"get password from static storage\",\n\t\t\taction: func() error {\n\t\t\t\t_, err := s.GetPassword(ctx, p2.Email)\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"get password from backing storage\",\n\t\t\taction: func() error {\n\t\t\t\t_, err := s.GetPassword(ctx, p1.Email)\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"get password from static storage with casing\",\n\t\t\taction: func() error {\n\t\t\t\t_, err := s.GetPassword(ctx, strings.ToUpper(p2.Email))\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"update static password\",\n\t\t\taction: func() error {\n\t\t\t\tupdater := func(p storage.Password) (storage.Password, error) {\n\t\t\t\t\tp.Username = \"new_\" + p.Username\n\t\t\t\t\treturn p, nil\n\t\t\t\t}\n\t\t\t\treturn s.UpdatePassword(ctx, p2.Email, updater)\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"update non-static password\",\n\t\t\taction: func() error {\n\t\t\t\tupdater := func(p storage.Password) (storage.Password, error) {\n\t\t\t\t\tp.Username = \"new_\" + p.Username\n\t\t\t\t\treturn p, nil\n\t\t\t\t}\n\t\t\t\treturn s.UpdatePassword(ctx, p1.Email, updater)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"create passwords\",\n\t\t\taction: func() error {\n\t\t\t\tif err := s.CreatePassword(ctx, p4); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\treturn s.CreatePassword(ctx, p3)\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"get password\",\n\t\t\taction: func() error {\n\t\t\t\tp, err := s.GetPassword(ctx, p4.Email)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif strings.Compare(p.Email, p4.Email) != 0 {\n\t\t\t\t\treturn fmt.Errorf(\"expected %s passwords got %s\", p4.Email, p.Email)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"list passwords\",\n\t\t\taction: func() error {\n\t\t\t\tpasswords, err := s.ListPasswords(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif n := len(passwords); n != 3 {\n\t\t\t\t\treturn fmt.Errorf(\"expected 3 passwords got %d\", n)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\terr := tc.action()\n\t\tif err != nil && !tc.wantErr {\n\t\t\tt.Errorf(\"%s: %v\", tc.name, err)\n\t\t}\n\t\tif err == nil && tc.wantErr {\n\t\t\tt.Errorf(\"%s: expected error, didn't get one\", tc.name)\n\t\t}\n\t}\n}\n\nfunc TestStaticConnectors(t *testing.T) {\n\tctx := context.Background()\n\tlogger := slog.New(slog.DiscardHandler)\n\tbacking := New(logger)\n\n\tconfig1 := []byte(`{\"issuer\": \"https://accounts.google.com\"}`)\n\tconfig2 := []byte(`{\"host\": \"ldap.example.com:636\"}`)\n\tconfig3 := []byte(`{\"issuer\": \"https://example.com\"}`)\n\n\tc1 := storage.Connector{ID: storage.NewID(), Type: \"oidc\", Name: \"oidc\", ResourceVersion: \"1\", Config: config1}\n\tc2 := storage.Connector{ID: storage.NewID(), Type: \"ldap\", Name: \"ldap\", ResourceVersion: \"1\", Config: config2}\n\tc3 := storage.Connector{ID: storage.NewID(), Type: \"saml\", Name: \"saml\", ResourceVersion: \"1\", Config: config3}\n\n\tbacking.CreateConnector(ctx, c1)\n\ts := storage.WithStaticConnectors(backing, []storage.Connector{c2})\n\n\ttests := []struct {\n\t\tname    string\n\t\taction  func() error\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"get connector from static storage\",\n\t\t\taction: func() error {\n\t\t\t\t_, err := s.GetConnector(ctx, c2.ID)\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"get connector from backing storage\",\n\t\t\taction: func() error {\n\t\t\t\t_, err := s.GetConnector(ctx, c1.ID)\n\t\t\t\treturn err\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"update static connector\",\n\t\t\taction: func() error {\n\t\t\t\tupdater := func(c storage.Connector) (storage.Connector, error) {\n\t\t\t\t\tc.Name = \"New\"\n\t\t\t\t\treturn c, nil\n\t\t\t\t}\n\t\t\t\treturn s.UpdateConnector(ctx, c2.ID, updater)\n\t\t\t},\n\t\t\twantErr: true,\n\t\t},\n\t\t{\n\t\t\tname: \"update non-static connector\",\n\t\t\taction: func() error {\n\t\t\t\tupdater := func(c storage.Connector) (storage.Connector, error) {\n\t\t\t\t\tc.Name = \"New\"\n\t\t\t\t\treturn c, nil\n\t\t\t\t}\n\t\t\t\treturn s.UpdateConnector(ctx, c1.ID, updater)\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"list connectors\",\n\t\t\taction: func() error {\n\t\t\t\tconnectors, err := s.ListConnectors(ctx)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif n := len(connectors); n != 2 {\n\t\t\t\t\treturn fmt.Errorf(\"expected 2 connectors got %d\", n)\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"create connector\",\n\t\t\taction: func() error {\n\t\t\t\treturn s.CreateConnector(ctx, c3)\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\terr := tc.action()\n\t\tif err != nil && !tc.wantErr {\n\t\t\tt.Errorf(\"%s: %v\", tc.name, err)\n\t\t}\n\t\tif err == nil && tc.wantErr {\n\t\t\tt.Errorf(\"%s: expected error, didn't get one\", tc.name)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "storage/sql/config.go",
    "content": "package sql\n\nimport (\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log/slog\"\n\t\"net\"\n\t\"os\"\n\t\"regexp\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/lib/pq\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\nconst (\n\t// postgres error codes\n\tpgErrUniqueViolation = \"23505\" // unique_violation\n)\n\nconst (\n\t// MySQL error codes\n\tmysqlErrDupEntry            = 1062\n\tmysqlErrDupEntryWithKeyName = 1586\n\tmysqlErrUnknownSysVar       = 1193\n)\n\nconst (\n\t// postgres SSL modes\n\tpgSSLDisable    = \"disable\"\n\tpgSSLRequire    = \"require\"\n\tpgSSLVerifyCA   = \"verify-ca\"\n\tpgSSLVerifyFull = \"verify-full\"\n)\n\nconst (\n\t// MySQL SSL modes\n\tmysqlSSLTrue       = \"true\"\n\tmysqlSSLFalse      = \"false\"\n\tmysqlSSLSkipVerify = \"skip-verify\"\n\tmysqlSSLCustom     = \"custom\"\n)\n\n// NetworkDB contains options common to SQL databases accessed over network.\ntype NetworkDB struct {\n\tDatabase string\n\tUser     string\n\tPassword string\n\tHost     string\n\tPort     uint16\n\n\tConnectionTimeout int // Seconds\n\n\t// database/sql tunables, see\n\t// https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime and below\n\t// Note: defaults will be set if these are 0\n\tMaxOpenConns    int // default: 5\n\tMaxIdleConns    int // default: 5\n\tConnMaxLifetime int // Seconds, default: not set\n}\n\n// SSL represents SSL options for network databases.\ntype SSL struct {\n\tMode   string\n\tCAFile string\n\t// Files for client auth.\n\tKeyFile  string\n\tCertFile string\n}\n\n// Postgres options for creating an SQL db.\ntype Postgres struct {\n\tNetworkDB\n\n\tSSL SSL `json:\"ssl\"`\n}\n\n// Open creates a new storage implementation backed by Postgres.\nfunc (p *Postgres) Open(logger *slog.Logger) (storage.Storage, error) {\n\tconn, err := p.open(logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nvar strEsc = regexp.MustCompile(`([\\\\'])`)\n\nfunc dataSourceStr(str string) string {\n\treturn \"'\" + strEsc.ReplaceAllString(str, `\\$1`) + \"'\"\n}\n\n// createDataSourceName takes the configuration provided via the Postgres\n// struct to create a data-source name that Go's database/sql package can\n// make use of.\nfunc (p *Postgres) createDataSourceName() string {\n\tparameters := []string{}\n\n\taddParam := func(key, val string) {\n\t\tparameters = append(parameters, fmt.Sprintf(\"%s=%s\", key, val))\n\t}\n\n\taddParam(\"connect_timeout\", strconv.Itoa(p.ConnectionTimeout))\n\n\t// detect host:port for backwards-compatibility\n\thost, port, err := net.SplitHostPort(p.Host)\n\tif err != nil {\n\t\t// not host:port, probably unix socket or bare address\n\n\t\thost = p.Host\n\n\t\tif p.Port != 0 {\n\t\t\tport = strconv.Itoa(int(p.Port))\n\t\t}\n\t}\n\n\tif host != \"\" {\n\t\taddParam(\"host\", dataSourceStr(host))\n\t}\n\n\tif port != \"\" {\n\t\taddParam(\"port\", port)\n\t}\n\n\tif p.User != \"\" {\n\t\taddParam(\"user\", dataSourceStr(p.User))\n\t}\n\n\tif p.Password != \"\" {\n\t\taddParam(\"password\", dataSourceStr(p.Password))\n\t}\n\n\tif p.Database != \"\" {\n\t\taddParam(\"dbname\", dataSourceStr(p.Database))\n\t}\n\n\tif p.SSL.Mode == \"\" {\n\t\t// Assume the strictest mode if unspecified.\n\t\taddParam(\"sslmode\", dataSourceStr(pgSSLVerifyFull))\n\t} else {\n\t\taddParam(\"sslmode\", dataSourceStr(p.SSL.Mode))\n\t}\n\n\tif p.SSL.CAFile != \"\" {\n\t\taddParam(\"sslrootcert\", dataSourceStr(p.SSL.CAFile))\n\t}\n\n\tif p.SSL.CertFile != \"\" {\n\t\taddParam(\"sslcert\", dataSourceStr(p.SSL.CertFile))\n\t}\n\n\tif p.SSL.KeyFile != \"\" {\n\t\taddParam(\"sslkey\", dataSourceStr(p.SSL.KeyFile))\n\t}\n\n\treturn strings.Join(parameters, \" \")\n}\n\nfunc (p *Postgres) open(logger *slog.Logger) (*conn, error) {\n\tdataSourceName := p.createDataSourceName()\n\n\tdb, err := sql.Open(\"postgres\", dataSourceName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// set database/sql tunables if configured\n\tif p.ConnMaxLifetime != 0 {\n\t\tdb.SetConnMaxLifetime(time.Duration(p.ConnMaxLifetime) * time.Second)\n\t}\n\n\tif p.MaxIdleConns == 0 {\n\t\tdb.SetMaxIdleConns(5)\n\t} else {\n\t\tdb.SetMaxIdleConns(p.MaxIdleConns)\n\t}\n\n\tif p.MaxOpenConns == 0 {\n\t\tdb.SetMaxOpenConns(5)\n\t} else {\n\t\tdb.SetMaxOpenConns(p.MaxOpenConns)\n\t}\n\n\terrCheck := func(err error) bool {\n\t\tsqlErr, ok := err.(*pq.Error)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn sqlErr.Code == pgErrUniqueViolation\n\t}\n\n\tc := &conn{db, &flavorPostgres, logger, errCheck}\n\tif _, err := c.migrate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to perform migrations: %v\", err)\n\t}\n\treturn c, nil\n}\n\n// MySQL options for creating a MySQL db.\ntype MySQL struct {\n\tNetworkDB\n\n\tSSL SSL `json:\"ssl\"`\n\n\t// TODO(pborzenkov): used by tests to reduce lock wait timeout. Should\n\t// we make it exported and allow users to provide arbitrary params?\n\tparams map[string]string\n}\n\n// Open creates a new storage implementation backed by MySQL.\nfunc (s *MySQL) Open(logger *slog.Logger) (storage.Storage, error) {\n\tconn, err := s.open(logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nfunc (s *MySQL) open(logger *slog.Logger) (*conn, error) {\n\tcfg := mysql.Config{\n\t\tUser:                 s.User,\n\t\tPasswd:               s.Password,\n\t\tDBName:               s.Database,\n\t\tAllowNativePasswords: true,\n\n\t\tTimeout: time.Second * time.Duration(s.ConnectionTimeout),\n\n\t\tParseTime: true,\n\t\tParams: map[string]string{\n\t\t\t\"transaction_isolation\": \"'SERIALIZABLE'\",\n\t\t},\n\t}\n\tif s.Host != \"\" {\n\t\tif s.Host[0] != '/' {\n\t\t\tcfg.Net = \"tcp\"\n\t\t\tcfg.Addr = s.Host\n\n\t\t\tif s.Port != 0 {\n\t\t\t\tcfg.Addr = net.JoinHostPort(s.Host, strconv.Itoa(int(s.Port)))\n\t\t\t}\n\t\t} else {\n\t\t\tcfg.Net = \"unix\"\n\t\t\tcfg.Addr = s.Host\n\t\t}\n\t}\n\n\tswitch {\n\tcase s.SSL.CAFile != \"\" || s.SSL.CertFile != \"\" || s.SSL.KeyFile != \"\":\n\t\tif err := s.makeTLSConfig(); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to make TLS config: %v\", err)\n\t\t}\n\t\tcfg.TLSConfig = mysqlSSLCustom\n\tcase s.SSL.Mode == \"\":\n\t\tcfg.TLSConfig = mysqlSSLTrue\n\tdefault:\n\t\tcfg.TLSConfig = s.SSL.Mode\n\t}\n\n\tfor k, v := range s.params {\n\t\tcfg.Params[k] = v\n\t}\n\n\tdb, err := sql.Open(\"mysql\", cfg.FormatDSN())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif s.MaxIdleConns == 0 {\n\t\t/*Override default behavior to fix https://github.com/dexidp/dex/issues/1608*/\n\t\tdb.SetMaxIdleConns(0)\n\t} else {\n\t\tdb.SetMaxIdleConns(s.MaxIdleConns)\n\t}\n\n\terr = db.Ping()\n\tif err != nil {\n\t\tif mysqlErr, ok := err.(*mysql.MySQLError); ok && mysqlErr.Number == mysqlErrUnknownSysVar {\n\t\t\tlogger.Info(\"reconnecting with MySQL pre-5.7.20 compatibility mode\")\n\n\t\t\t// MySQL 5.7.20 introduced transaction_isolation and deprecated tx_isolation.\n\t\t\t// MySQL 8.0 doesn't have tx_isolation at all.\n\t\t\t// https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_transaction_isolation\n\t\t\tdelete(cfg.Params, \"transaction_isolation\")\n\t\t\tcfg.Params[\"tx_isolation\"] = \"'SERIALIZABLE'\"\n\n\t\t\tdb, err = sql.Open(\"mysql\", cfg.FormatDSN())\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\terrCheck := func(err error) bool {\n\t\tsqlErr, ok := err.(*mysql.MySQLError)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn sqlErr.Number == mysqlErrDupEntry ||\n\t\t\tsqlErr.Number == mysqlErrDupEntryWithKeyName\n\t}\n\n\tc := &conn{db, &flavorMySQL, logger, errCheck}\n\tif _, err := c.migrate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to perform migrations: %v\", err)\n\t}\n\treturn c, nil\n}\n\nfunc (s *MySQL) makeTLSConfig() error {\n\tcfg := &tls.Config{}\n\tif s.SSL.CAFile != \"\" {\n\t\trootCertPool := x509.NewCertPool()\n\t\tpem, err := os.ReadFile(s.SSL.CAFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif ok := rootCertPool.AppendCertsFromPEM(pem); !ok {\n\t\t\treturn fmt.Errorf(\"failed to append PEM\")\n\t\t}\n\t\tcfg.RootCAs = rootCertPool\n\t}\n\tif s.SSL.CertFile != \"\" && s.SSL.KeyFile != \"\" {\n\t\tclientCert := make([]tls.Certificate, 0, 1)\n\t\tcerts, err := tls.LoadX509KeyPair(s.SSL.CertFile, s.SSL.KeyFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tclientCert = append(clientCert, certs)\n\t\tcfg.Certificates = clientCert\n\t}\n\n\tmysql.RegisterTLSConfig(mysqlSSLCustom, cfg)\n\treturn nil\n}\n"
  },
  {
    "path": "storage/sql/config_test.go",
    "content": "package sql\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\t\"os\"\n\t\"runtime\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n\t\"github.com/dexidp/dex/storage/conformance\"\n)\n\nfunc withTimeout(t time.Duration, f func()) {\n\tc := make(chan struct{})\n\tdefer close(c)\n\n\tgo func() {\n\t\tselect {\n\t\tcase <-c:\n\t\tcase <-time.After(t):\n\t\t\t// Dump a stack trace of the program. Useful for debugging deadlocks.\n\t\t\tbuf := make([]byte, 2<<20)\n\t\t\tfmt.Fprintf(os.Stderr, \"%s\\n\", buf[:runtime.Stack(buf, true)])\n\t\t\tpanic(\"test took too long\")\n\t\t}\n\t}()\n\n\tf()\n}\n\nfunc cleanDB(c *conn) error {\n\ttables := []string{\n\t\t\"client\", \"auth_request\", \"auth_code\",\n\t\t\"refresh_token\", \"keys\", \"password\",\n\t}\n\n\tfor _, tbl := range tables {\n\t\t_, err := c.Exec(\"delete from \" + tbl)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\ntype opener interface {\n\topen(logger *slog.Logger) (*conn, error)\n}\n\nfunc testDB(t *testing.T, o opener, withTransactions, withConcurrentTests bool) {\n\t// t.Fatal has a bad habit of not actually printing the error\n\tfatal := func(i any) {\n\t\tfmt.Fprintln(os.Stdout, i)\n\t\tt.Fatal(i)\n\t}\n\n\tnewStorage := func(t *testing.T) storage.Storage {\n\t\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\t\tconn, err := o.open(logger)\n\t\tif err != nil {\n\t\t\tfatal(err)\n\t\t}\n\t\tif err := cleanDB(conn); err != nil {\n\t\t\tfatal(err)\n\t\t}\n\t\treturn conn\n\t}\n\twithTimeout(time.Minute*1, func() {\n\t\tconformance.RunTests(t, newStorage)\n\t})\n\n\tif withTransactions {\n\t\twithTimeout(time.Minute*1, func() {\n\t\t\tconformance.RunTransactionTests(t, newStorage)\n\t\t})\n\t}\n\n\tif withConcurrentTests {\n\t\twithTimeout(time.Minute*1, func() {\n\t\t\tconformance.RunConcurrencyTests(t, newStorage)\n\t\t})\n\t}\n}\n\nfunc getenv(key, defaultVal string) string {\n\tif val := os.Getenv(key); val != \"\" {\n\t\treturn val\n\t}\n\treturn defaultVal\n}\n\nconst testPostgresEnv = \"DEX_POSTGRES_HOST\"\n\nfunc TestCreateDataSourceName(t *testing.T) {\n\ttestCases := []struct {\n\t\tdescription string\n\t\tinput       *Postgres\n\t\texpected    string\n\t}{\n\t\t{\n\t\t\tdescription: \"with no configuration\",\n\t\t\tinput:       &Postgres{},\n\t\t\texpected:    \"connect_timeout=0 sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with typical configuration\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost:     \"1.2.3.4\",\n\t\t\t\t\tPort:     6543,\n\t\t\t\t\tUser:     \"some-user\",\n\t\t\t\t\tPassword: \"some-password\",\n\t\t\t\t\tDatabase: \"some-db\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"connect_timeout=0 host='1.2.3.4' port=6543 user='some-user' password='some-password' dbname='some-db' sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with unix socket host\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"/var/run/postgres\",\n\t\t\t\t},\n\t\t\t\tSSL: SSL{\n\t\t\t\t\tMode: \"disable\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"connect_timeout=0 host='/var/run/postgres' sslmode='disable'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with tcp host\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"coreos.com\",\n\t\t\t\t},\n\t\t\t\tSSL: SSL{\n\t\t\t\t\tMode: \"disable\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"connect_timeout=0 host='coreos.com' sslmode='disable'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with tcp host:port\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"coreos.com:6543\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"connect_timeout=0 host='coreos.com' port=6543 sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with tcp host and port\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"coreos.com\",\n\t\t\t\t\tPort: 6543,\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"connect_timeout=0 host='coreos.com' port=6543 sslmode='verify-full'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with ssl ca cert\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"coreos.com\",\n\t\t\t\t},\n\t\t\t\tSSL: SSL{\n\t\t\t\t\tMode:   \"verify-ca\",\n\t\t\t\t\tCAFile: \"/some/file/path\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"connect_timeout=0 host='coreos.com' sslmode='verify-ca' sslrootcert='/some/file/path'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with ssl client cert\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost: \"coreos.com\",\n\t\t\t\t},\n\t\t\t\tSSL: SSL{\n\t\t\t\t\tMode:     \"verify-ca\",\n\t\t\t\t\tCAFile:   \"/some/ca/path\",\n\t\t\t\t\tCertFile: \"/some/cert/path\",\n\t\t\t\t\tKeyFile:  \"/some/key/path\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: \"connect_timeout=0 host='coreos.com' sslmode='verify-ca' sslrootcert='/some/ca/path' sslcert='/some/cert/path' sslkey='/some/key/path'\",\n\t\t},\n\t\t{\n\t\t\tdescription: \"with funny characters in credentials\",\n\t\t\tinput: &Postgres{\n\t\t\t\tNetworkDB: NetworkDB{\n\t\t\t\t\tHost:     \"coreos.com\",\n\t\t\t\t\tUser:     `some'user\\slashed`,\n\t\t\t\t\tPassword: \"some'password!\",\n\t\t\t\t},\n\t\t\t},\n\t\t\texpected: `connect_timeout=0 host='coreos.com' user='some\\'user\\\\slashed' password='some\\'password!' sslmode='verify-full'`,\n\t\t},\n\t}\n\n\tvar actual string\n\tfor _, testCase := range testCases {\n\t\tt.Run(testCase.description, func(t *testing.T) {\n\t\t\tactual = testCase.input.createDataSourceName()\n\n\t\t\tif actual != testCase.expected {\n\t\t\t\tt.Fatalf(\"%s != %s\", actual, testCase.expected)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestPostgres(t *testing.T) {\n\thost := os.Getenv(testPostgresEnv)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %q not set, skipping\", testPostgresEnv)\n\t}\n\n\tport := uint64(5432)\n\tif rawPort := os.Getenv(\"DEX_POSTGRES_PORT\"); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"invalid postgres port %q: %s\", rawPort, err)\n\t\t}\n\t}\n\n\tp := &Postgres{\n\t\tNetworkDB: NetworkDB{\n\t\t\tDatabase:          getenv(\"DEX_POSTGRES_DATABASE\", \"postgres\"),\n\t\t\tUser:              getenv(\"DEX_POSTGRES_USER\", \"postgres\"),\n\t\t\tPassword:          getenv(\"DEX_POSTGRES_PASSWORD\", \"postgres\"),\n\t\t\tHost:              host,\n\t\t\tPort:              uint16(port),\n\t\t\tConnectionTimeout: 5,\n\t\t},\n\t\tSSL: SSL{\n\t\t\tMode: pgSSLDisable, // Postgres container doesn't support SSL.\n\t\t},\n\t}\n\ttestDB(t, p, true, false)\n}\n\nconst testMySQLEnv = \"DEX_MYSQL_HOST\"\n\nfunc TestMySQL(t *testing.T) {\n\thost := os.Getenv(testMySQLEnv)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %q not set, skipping\", testMySQLEnv)\n\t}\n\n\tport := uint64(3306)\n\tif rawPort := os.Getenv(\"DEX_MYSQL_PORT\"); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"invalid mysql port %q: %s\", rawPort, err)\n\t\t}\n\t}\n\n\ts := &MySQL{\n\t\tNetworkDB: NetworkDB{\n\t\t\tDatabase:          getenv(\"DEX_MYSQL_DATABASE\", \"mysql\"),\n\t\t\tUser:              getenv(\"DEX_MYSQL_USER\", \"mysql\"),\n\t\t\tPassword:          getenv(\"DEX_MYSQL_PASSWORD\", \"mysql\"),\n\t\t\tHost:              host,\n\t\t\tPort:              uint16(port),\n\t\t\tConnectionTimeout: 5,\n\t\t},\n\t\tSSL: SSL{\n\t\t\tMode: mysqlSSLFalse,\n\t\t},\n\t\tparams: map[string]string{\n\t\t\t\"innodb_lock_wait_timeout\": \"3\",\n\t\t},\n\t}\n\ttestDB(t, s, true, false)\n}\n\nconst testMySQL8Env = \"DEX_MYSQL8_HOST\"\n\nfunc TestMySQL8(t *testing.T) {\n\thost := os.Getenv(testMySQL8Env)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %q not set, skipping\", testMySQL8Env)\n\t}\n\n\tport := uint64(3306)\n\tif rawPort := os.Getenv(\"DEX_MYSQL8_PORT\"); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"invalid mysql port %q: %s\", rawPort, err)\n\t\t}\n\t}\n\n\ts := &MySQL{\n\t\tNetworkDB: NetworkDB{\n\t\t\tDatabase:          getenv(\"DEX_MYSQL8_DATABASE\", \"mysql\"),\n\t\t\tUser:              getenv(\"DEX_MYSQL8_USER\", \"mysql\"),\n\t\t\tPassword:          getenv(\"DEX_MYSQL8_PASSWORD\", \"mysql\"),\n\t\t\tHost:              host,\n\t\t\tPort:              uint16(port),\n\t\t\tConnectionTimeout: 5,\n\t\t},\n\t\tSSL: SSL{\n\t\t\tMode: mysqlSSLFalse,\n\t\t},\n\t\tparams: map[string]string{\n\t\t\t\"innodb_lock_wait_timeout\": \"3\",\n\t\t},\n\t}\n\ttestDB(t, s, true, false)\n}\n"
  },
  {
    "path": "storage/sql/crud.go",
    "content": "package sql\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// TODO(ericchiang): The update, insert, and select methods queries are all\n// very repetitive. Consider creating them programmatically.\n\n// keysRowID is the ID of the only row we expect to populate the \"keys\" table.\nconst keysRowID = \"keys\"\n\n// encoder wraps the underlying value in a JSON marshaler which is automatically\n// called by the database/sql package.\n//\n//\ts := []string{\"planes\", \"bears\"}\n//\terr := db.Exec(`insert into t1 (id, things) values (1, $1)`, encoder(s))\n//\tif err != nil {\n//\t\t// handle error\n//\t}\n//\n//\tvar r []byte\n//\terr = db.QueryRow(`select things from t1 where id = 1;`).Scan(&r)\n//\tif err != nil {\n//\t\t// handle error\n//\t}\n//\tfmt.Printf(\"%s\\n\", r) // [\"planes\",\"bears\"]\nfunc encoder(i interface{}) driver.Valuer {\n\treturn jsonEncoder{i}\n}\n\n// decoder wraps the underlying value in a JSON unmarshaler which can then be passed\n// to a database Scan() method.\nfunc decoder(i interface{}) sql.Scanner {\n\treturn jsonDecoder{i}\n}\n\ntype jsonEncoder struct {\n\ti interface{}\n}\n\nfunc (j jsonEncoder) Value() (driver.Value, error) {\n\tb, err := json.Marshal(j.i)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"marshal: %v\", err)\n\t}\n\treturn b, nil\n}\n\ntype jsonDecoder struct {\n\ti interface{}\n}\n\nfunc (j jsonDecoder) Scan(dest interface{}) error {\n\tif dest == nil {\n\t\treturn errors.New(\"nil value\")\n\t}\n\tb, ok := dest.([]byte)\n\tif !ok {\n\t\treturn fmt.Errorf(\"expected []byte got %T\", dest)\n\t}\n\tif err := json.Unmarshal(b, &j.i); err != nil {\n\t\treturn fmt.Errorf(\"unmarshal: %v\", err)\n\t}\n\treturn nil\n}\n\n// Abstract conn vs trans.\ntype querier interface {\n\tQueryRow(query string, args ...interface{}) *sql.Row\n}\n\n// Abstract row vs rows.\ntype scanner interface {\n\tScan(dest ...interface{}) error\n}\n\nvar _ storage.Storage = (*conn)(nil)\n\nfunc (c *conn) GarbageCollect(ctc context.Context, now time.Time) (storage.GCResult, error) {\n\tresult := storage.GCResult{}\n\n\tr, err := c.Exec(`delete from auth_request where expiry < $1`, now)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"gc auth_request: %v\", err)\n\t}\n\tif n, err := r.RowsAffected(); err == nil {\n\t\tresult.AuthRequests = n\n\t}\n\n\tr, err = c.Exec(`delete from auth_code where expiry < $1`, now)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"gc auth_code: %v\", err)\n\t}\n\tif n, err := r.RowsAffected(); err == nil {\n\t\tresult.AuthCodes = n\n\t}\n\n\tr, err = c.Exec(`delete from device_request where expiry < $1`, now)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"gc device_request: %v\", err)\n\t}\n\tif n, err := r.RowsAffected(); err == nil {\n\t\tresult.DeviceRequests = n\n\t}\n\n\tr, err = c.Exec(`delete from device_token where expiry < $1`, now)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"gc device_token: %v\", err)\n\t}\n\tif n, err := r.RowsAffected(); err == nil {\n\t\tresult.DeviceTokens = n\n\t}\n\n\tr, err = c.Exec(`delete from auth_session where absolute_expiry < $1 OR idle_expiry < $2`, now, now)\n\tif err != nil {\n\t\treturn result, fmt.Errorf(\"gc auth_session: %v\", err)\n\t}\n\tif n, err := r.RowsAffected(); err == nil {\n\t\tresult.AuthSessions = n\n\t}\n\n\treturn result, nil\n}\n\nfunc (c *conn) CreateAuthRequest(ctx context.Context, a storage.AuthRequest) error {\n\t_, err := c.Exec(`\n\t\tinsert into auth_request (\n\t\t\tid, client_id, response_types, scopes, redirect_uri, nonce, state,\n\t\t\tforce_approval_prompt, logged_in,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconnector_id, connector_data,\n\t\t\texpiry,\n\t\t\tcode_challenge, code_challenge_method,\n\t\t\thmac_key,\n\t\t\tmfa_validated,\n\t\t\tprompt, max_age, auth_time\n\t\t)\n\t\tvalues (\n\t\t\t$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25\n\t\t);\n\t`,\n\t\ta.ID, a.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State,\n\t\ta.ForceApprovalPrompt, a.LoggedIn,\n\t\ta.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername,\n\t\ta.Claims.Email, a.Claims.EmailVerified, encoder(a.Claims.Groups),\n\t\ta.ConnectorID, a.ConnectorData,\n\t\ta.Expiry,\n\t\ta.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod,\n\t\ta.HMACKey,\n\t\ta.MFAValidated,\n\t\ta.Prompt, a.MaxAge, a.AuthTime,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert auth request: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) UpdateAuthRequest(ctx context.Context, id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tr, err := getAuthRequest(ctx, tx, id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ta, err := updater(r)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate auth_request\n\t\t\tset\n\t\t\t\tclient_id = $1, response_types = $2, scopes = $3, redirect_uri = $4,\n\t\t\t\tnonce = $5, state = $6, force_approval_prompt = $7, logged_in = $8,\n\t\t\t\tclaims_user_id = $9, claims_username = $10, claims_preferred_username = $11,\n\t\t\t\tclaims_email = $12, claims_email_verified = $13,\n\t\t\t\tclaims_groups = $14,\n\t\t\t\tconnector_id = $15, connector_data = $16,\n\t\t\t\texpiry = $17,\n\t\t\t\tcode_challenge = $18, code_challenge_method = $19,\n\t\t\t\thmac_key = $20,\n\t\t\t\tmfa_validated = $21,\n\t\t\t\tprompt = $22, max_age = $23, auth_time = $24\n\t\t\twhere id = $25;\n\t\t`,\n\t\t\ta.ClientID, encoder(a.ResponseTypes), encoder(a.Scopes), a.RedirectURI, a.Nonce, a.State,\n\t\t\ta.ForceApprovalPrompt, a.LoggedIn,\n\t\t\ta.Claims.UserID, a.Claims.Username, a.Claims.PreferredUsername,\n\t\t\ta.Claims.Email, a.Claims.EmailVerified,\n\t\t\tencoder(a.Claims.Groups),\n\t\t\ta.ConnectorID, a.ConnectorData,\n\t\t\ta.Expiry,\n\t\t\ta.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod, a.HMACKey,\n\t\t\ta.MFAValidated,\n\t\t\ta.Prompt, a.MaxAge, a.AuthTime,\n\t\t\tr.ID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update auth request: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetAuthRequest(ctx context.Context, id string) (storage.AuthRequest, error) {\n\treturn getAuthRequest(ctx, c, id)\n}\n\nfunc getAuthRequest(ctx context.Context, q querier, id string) (a storage.AuthRequest, err error) {\n\terr = q.QueryRow(`\n\t\tselect\n\t\t\tid, client_id, response_types, scopes, redirect_uri, nonce, state,\n\t\t\tforce_approval_prompt, logged_in,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconnector_id, connector_data, expiry,\n\t\t\tcode_challenge, code_challenge_method, hmac_key,\n\t\t\tmfa_validated,\n\t\t\tprompt, max_age, auth_time\n\t\tfrom auth_request where id = $1;\n\t`, id).Scan(\n\t\t&a.ID, &a.ClientID, decoder(&a.ResponseTypes), decoder(&a.Scopes), &a.RedirectURI, &a.Nonce, &a.State,\n\t\t&a.ForceApprovalPrompt, &a.LoggedIn,\n\t\t&a.Claims.UserID, &a.Claims.Username, &a.Claims.PreferredUsername,\n\t\t&a.Claims.Email, &a.Claims.EmailVerified,\n\t\tdecoder(&a.Claims.Groups),\n\t\t&a.ConnectorID, &a.ConnectorData, &a.Expiry,\n\t\t&a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod, &a.HMACKey,\n\t\t&a.MFAValidated,\n\t\t&a.Prompt, &a.MaxAge, &a.AuthTime,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn a, storage.ErrNotFound\n\t\t}\n\t\treturn a, fmt.Errorf(\"select auth request: %v\", err)\n\t}\n\treturn a, nil\n}\n\nfunc (c *conn) CreateAuthCode(ctx context.Context, a storage.AuthCode) error {\n\t_, err := c.Exec(`\n\t\tinsert into auth_code (\n\t\t\tid, client_id, scopes, nonce, redirect_uri,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconnector_id, connector_data,\n\t\t\texpiry,\n\t\t\tcode_challenge, code_challenge_method,\n\t\t\tauth_time\n\t\t)\n\t\tvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17);\n\t`,\n\t\ta.ID, a.ClientID, encoder(a.Scopes), a.Nonce, a.RedirectURI, a.Claims.UserID,\n\t\ta.Claims.Username, a.Claims.PreferredUsername, a.Claims.Email, a.Claims.EmailVerified,\n\t\tencoder(a.Claims.Groups), a.ConnectorID, a.ConnectorData, a.Expiry,\n\t\ta.PKCE.CodeChallenge, a.PKCE.CodeChallengeMethod,\n\t\ta.AuthTime,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert auth code: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) GetAuthCode(ctx context.Context, id string) (a storage.AuthCode, err error) {\n\terr = c.QueryRow(`\n\t\tselect\n\t\t\tid, client_id, scopes, nonce, redirect_uri,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconnector_id, connector_data,\n\t\t\texpiry,\n\t\t\tcode_challenge, code_challenge_method,\n\t\t\tauth_time\n\t\tfrom auth_code where id = $1;\n\t`, id).Scan(\n\t\t&a.ID, &a.ClientID, decoder(&a.Scopes), &a.Nonce, &a.RedirectURI, &a.Claims.UserID,\n\t\t&a.Claims.Username, &a.Claims.PreferredUsername, &a.Claims.Email, &a.Claims.EmailVerified,\n\t\tdecoder(&a.Claims.Groups), &a.ConnectorID, &a.ConnectorData, &a.Expiry,\n\t\t&a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod,\n\t\t&a.AuthTime,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn a, storage.ErrNotFound\n\t\t}\n\t\treturn a, fmt.Errorf(\"select auth code: %v\", err)\n\t}\n\treturn a, nil\n}\n\nfunc (c *conn) CreateRefresh(ctx context.Context, r storage.RefreshToken) error {\n\t_, err := c.Exec(`\n\t\tinsert into refresh_token (\n\t\t\tid, client_id, scopes, nonce,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconnector_id, connector_data,\n\t\t\ttoken, obsolete_token, created_at, last_used\n\t\t)\n\t\tvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16);\n\t`,\n\t\tr.ID, r.ClientID, encoder(r.Scopes), r.Nonce,\n\t\tr.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername,\n\t\tr.Claims.Email, r.Claims.EmailVerified,\n\t\tencoder(r.Claims.Groups),\n\t\tr.ConnectorID, r.ConnectorData,\n\t\tr.Token, r.ObsoleteToken, r.CreatedAt, r.LastUsed,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert refresh_token: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) UpdateRefreshToken(ctx context.Context, id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tr, err := getRefresh(ctx, tx, id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r, err = updater(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate refresh_token\n\t\t\tset\n\t\t\t\tclient_id = $1,\n\t\t\t\tscopes = $2,\n\t\t\t\tnonce = $3,\n\t\t\t\tclaims_user_id = $4,\n\t\t\t\tclaims_username = $5,\n\t\t\t\tclaims_preferred_username = $6,\n\t\t\t\tclaims_email = $7,\n\t\t\t\tclaims_email_verified = $8,\n\t\t\t\tclaims_groups = $9,\n\t\t\t\tconnector_id = $10,\n\t\t\t\tconnector_data = $11,\n\t\t\t\ttoken = $12,\n                obsolete_token = $13,\n\t\t\t\tcreated_at = $14,\n\t\t\t\tlast_used = $15\n\t\t\twhere\n\t\t\t\tid = $16\n\t\t`,\n\t\t\tr.ClientID, encoder(r.Scopes), r.Nonce,\n\t\t\tr.Claims.UserID, r.Claims.Username, r.Claims.PreferredUsername,\n\t\t\tr.Claims.Email, r.Claims.EmailVerified,\n\t\t\tencoder(r.Claims.Groups),\n\t\t\tr.ConnectorID, r.ConnectorData,\n\t\t\tr.Token, r.ObsoleteToken, r.CreatedAt, r.LastUsed, id,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update refresh token: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetRefresh(ctx context.Context, id string) (storage.RefreshToken, error) {\n\treturn getRefresh(ctx, c, id)\n}\n\nfunc getRefresh(ctx context.Context, q querier, id string) (storage.RefreshToken, error) {\n\treturn scanRefresh(q.QueryRow(`\n\t\tselect\n\t\t\tid, client_id, scopes, nonce,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified,\n\t\t\tclaims_groups,\n\t\t\tconnector_id, connector_data,\n\t\t\ttoken, obsolete_token, created_at, last_used\n\t\tfrom refresh_token where id = $1;\n\t`, id))\n}\n\nfunc (c *conn) ListRefreshTokens(ctx context.Context) ([]storage.RefreshToken, error) {\n\trows, err := c.Query(`\n\t\tselect\n\t\t\tid, client_id, scopes, nonce,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconnector_id, connector_data,\n\t\t\ttoken, obsolete_token, created_at, last_used\n\t\tfrom refresh_token;\n\t`)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"query: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar tokens []storage.RefreshToken\n\tfor rows.Next() {\n\t\tr, err := scanRefresh(rows)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttokens = append(tokens, r)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"scan: %v\", err)\n\t}\n\treturn tokens, nil\n}\n\nfunc scanRefresh(s scanner) (r storage.RefreshToken, err error) {\n\terr = s.Scan(\n\t\t&r.ID, &r.ClientID, decoder(&r.Scopes), &r.Nonce,\n\t\t&r.Claims.UserID, &r.Claims.Username, &r.Claims.PreferredUsername,\n\t\t&r.Claims.Email, &r.Claims.EmailVerified,\n\t\tdecoder(&r.Claims.Groups),\n\t\t&r.ConnectorID, &r.ConnectorData,\n\t\t&r.Token, &r.ObsoleteToken, &r.CreatedAt, &r.LastUsed,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn r, storage.ErrNotFound\n\t\t}\n\t\treturn r, fmt.Errorf(\"scan refresh_token: %v\", err)\n\t}\n\treturn r, nil\n}\n\nfunc (c *conn) UpdateKeys(ctx context.Context, updater func(old storage.Keys) (storage.Keys, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tfirstUpdate := false\n\t\t// TODO(ericchiang): errors may cause a transaction be rolled back by the SQL\n\t\t// server. Test this, and consider adding a COUNT() command beforehand.\n\t\told, err := getKeys(ctx, tx)\n\t\tif err != nil {\n\t\t\tif err != storage.ErrNotFound {\n\t\t\t\treturn fmt.Errorf(\"get keys: %v\", err)\n\t\t\t}\n\t\t\tfirstUpdate = true\n\t\t\told = storage.Keys{}\n\t\t}\n\n\t\tnk, err := updater(old)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif firstUpdate {\n\t\t\t_, err = tx.Exec(`\n\t\t\t\tinsert into keys (\n\t\t\t\t\tid, verification_keys, signing_key, signing_key_pub, next_rotation\n\t\t\t\t)\n\t\t\t\tvalues ($1, $2, $3, $4, $5);\n\t\t\t`,\n\t\t\t\tkeysRowID, encoder(nk.VerificationKeys), encoder(nk.SigningKey),\n\t\t\t\tencoder(nk.SigningKeyPub), nk.NextRotation,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"insert: %v\", err)\n\t\t\t}\n\t\t} else {\n\t\t\t_, err = tx.Exec(`\n\t\t\t\tupdate keys\n\t\t\t\tset\n\t\t\t\t    verification_keys = $1,\n\t\t\t\t\tsigning_key = $2,\n\t\t\t\t\tsigning_key_pub = $3,\n\t\t\t\t\tnext_rotation = $4\n\t\t\t\twhere id = $5;\n\t\t\t`,\n\t\t\t\tencoder(nk.VerificationKeys), encoder(nk.SigningKey),\n\t\t\t\tencoder(nk.SigningKeyPub), nk.NextRotation, keysRowID,\n\t\t\t)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"update: %v\", err)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetKeys(ctx context.Context) (keys storage.Keys, err error) {\n\treturn getKeys(ctx, c)\n}\n\nfunc getKeys(ctx context.Context, q querier) (keys storage.Keys, err error) {\n\terr = q.QueryRow(`\n\t\tselect\n\t\t\tverification_keys, signing_key, signing_key_pub, next_rotation\n\t\tfrom keys\n\t\twhere id=$1\n\t`, keysRowID).Scan(\n\t\tdecoder(&keys.VerificationKeys), decoder(&keys.SigningKey),\n\t\tdecoder(&keys.SigningKeyPub), &keys.NextRotation,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn keys, storage.ErrNotFound\n\t\t}\n\t\treturn keys, fmt.Errorf(\"query keys: %v\", err)\n\t}\n\treturn keys, nil\n}\n\nfunc (c *conn) UpdateClient(ctx context.Context, id string, updater func(old storage.Client) (storage.Client, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tcli, err := getClient(ctx, tx, id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tnc, err := updater(cli)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = tx.Exec(`\n\t\t\tupdate client\n\t\t\tset\n\t\t\t\tsecret = $1,\n\t\t\t\tredirect_uris = $2,\n\t\t\t\ttrusted_peers = $3,\n\t\t\t\tpublic = $4,\n\t\t\t\tname = $5,\n\t\t\t\tlogo_url = $6,\n\t\t\t\tallowed_connectors = $7,\n\t\t\t\tmfa_chain = $8\n\t\t\twhere id = $9;\n\t\t`, nc.Secret, encoder(nc.RedirectURIs), encoder(nc.TrustedPeers), nc.Public, nc.Name, nc.LogoURL, encoder(nc.AllowedConnectors), encoder(nc.MFAChain), id,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update client: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) CreateClient(ctx context.Context, cli storage.Client) error {\n\t_, err := c.Exec(`\n\t\tinsert into client (\n\t\t\tid, secret, redirect_uris, trusted_peers, public, name, logo_url, allowed_connectors, mfa_chain\n\t\t)\n\t\tvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9);\n\t`,\n\t\tcli.ID, cli.Secret, encoder(cli.RedirectURIs), encoder(cli.TrustedPeers),\n\t\tcli.Public, cli.Name, cli.LogoURL, encoder(cli.AllowedConnectors), encoder(cli.MFAChain),\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert client: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc getClient(ctx context.Context, q querier, id string) (storage.Client, error) {\n\treturn scanClient(q.QueryRow(`\n\t\tselect\n\t\t\tid, secret, redirect_uris, trusted_peers, public, name, logo_url, allowed_connectors, mfa_chain\n\t    from client where id = $1;\n\t`, id))\n}\n\nfunc (c *conn) GetClient(ctx context.Context, id string) (storage.Client, error) {\n\treturn getClient(ctx, c, id)\n}\n\nfunc (c *conn) ListClients(ctx context.Context) ([]storage.Client, error) {\n\trows, err := c.Query(`\n\t\tselect\n\t\t\tid, secret, redirect_uris, trusted_peers, public, name, logo_url, allowed_connectors, mfa_chain\n\t\tfrom client;\n\t`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rows.Close()\n\n\tvar clients []storage.Client\n\tfor rows.Next() {\n\t\tcli, err := scanClient(rows)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tclients = append(clients, cli)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn clients, nil\n}\n\nfunc scanClient(s scanner) (cli storage.Client, err error) {\n\tvar allowedConnectors []byte\n\tvar mfaChain []byte\n\terr = s.Scan(\n\t\t&cli.ID, &cli.Secret, decoder(&cli.RedirectURIs), decoder(&cli.TrustedPeers),\n\t\t&cli.Public, &cli.Name, &cli.LogoURL, &allowedConnectors, &mfaChain,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn cli, storage.ErrNotFound\n\t\t}\n\t\treturn cli, fmt.Errorf(\"get client: %v\", err)\n\t}\n\tif len(allowedConnectors) > 0 {\n\t\tif err := json.Unmarshal(allowedConnectors, &cli.AllowedConnectors); err != nil {\n\t\t\treturn cli, fmt.Errorf(\"unmarshal client allowed connectors: %v\", err)\n\t\t}\n\t}\n\tif len(mfaChain) > 0 {\n\t\tif err := json.Unmarshal(mfaChain, &cli.MFAChain); err != nil {\n\t\t\treturn cli, fmt.Errorf(\"unmarshal client mfa chain: %v\", err)\n\t\t}\n\t}\n\treturn cli, nil\n}\n\nfunc (c *conn) CreatePassword(ctx context.Context, p storage.Password) error {\n\tp.Email = strings.ToLower(p.Email)\n\t_, err := c.Exec(`\n\t\tinsert into password (\n\t\t\temail, hash, username, preferred_username, user_id, groups, name, email_verified\n\t\t)\n\t\tvalues (\n\t\t\t$1, $2, $3, $4, $5, $6, $7, $8\n\t\t);\n\t`,\n\t\tp.Email, p.Hash, p.Username, p.PreferredUsername, p.UserID, encoder(p.Groups), p.Name, p.EmailVerified,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert password: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) UpdatePassword(ctx context.Context, email string, updater func(p storage.Password) (storage.Password, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tp, err := getPassword(ctx, tx, email)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnp, err := updater(p)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate password\n\t\t\tset\n\t\t\t\thash = $1, username = $2, preferred_username = $3, user_id = $4, groups = $5, name = $6, email_verified = $7\n\t\t\twhere email = $8;\n\t\t`,\n\t\t\tnp.Hash, np.Username, np.PreferredUsername, np.UserID, encoder(np.Groups), np.Name, np.EmailVerified, p.Email,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update password: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetPassword(ctx context.Context, email string) (storage.Password, error) {\n\treturn getPassword(ctx, c, email)\n}\n\nfunc getPassword(ctx context.Context, q querier, email string) (p storage.Password, err error) {\n\treturn scanPassword(q.QueryRow(`\n\t\tselect\n\t\t\temail, hash, username, preferred_username, user_id, groups, name, email_verified\n\t\tfrom password where email = $1;\n\t`, strings.ToLower(email)))\n}\n\nfunc (c *conn) ListPasswords(ctx context.Context) ([]storage.Password, error) {\n\trows, err := c.Query(`\n\t\tselect\n\t\t\temail, hash, username, preferred_username, user_id, groups, name, email_verified\n\t\tfrom password;\n\t`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rows.Close()\n\n\tvar passwords []storage.Password\n\tfor rows.Next() {\n\t\tp, err := scanPassword(rows)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpasswords = append(passwords, p)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn passwords, nil\n}\n\nfunc scanPassword(s scanner) (p storage.Password, err error) {\n\tvar emailVerified sql.NullBool\n\terr = s.Scan(\n\t\t&p.Email, &p.Hash, &p.Username, &p.PreferredUsername, &p.UserID, decoder(&p.Groups), &p.Name, &emailVerified,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn p, storage.ErrNotFound\n\t\t}\n\t\treturn p, fmt.Errorf(\"select password: %v\", err)\n\t}\n\tif emailVerified.Valid {\n\t\tp.EmailVerified = &emailVerified.Bool\n\t}\n\treturn p, nil\n}\n\nfunc (c *conn) CreateOfflineSessions(ctx context.Context, s storage.OfflineSessions) error {\n\t_, err := c.Exec(`\n\t\tinsert into offline_session (\n\t\t\tuser_id, conn_id, refresh, connector_data\n\t\t)\n\t\tvalues (\n\t\t\t$1, $2, $3, $4\n\t\t);\n\t`,\n\t\ts.UserID, s.ConnID, encoder(s.Refresh), s.ConnectorData,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert offline session: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) UpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s storage.OfflineSessions) (storage.OfflineSessions, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\ts, err := getOfflineSessions(ctx, tx, userID, connID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewSession, err := updater(s)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate offline_session\n\t\t\tset\n\t\t\t\trefresh = $1,\n\t\t\t\tconnector_data = $2\n\t\t\twhere user_id = $3 AND conn_id = $4;\n\t\t`,\n\t\t\tencoder(newSession.Refresh), newSession.ConnectorData, s.UserID, s.ConnID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update offline session: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetOfflineSessions(ctx context.Context, userID string, connID string) (storage.OfflineSessions, error) {\n\treturn getOfflineSessions(ctx, c, userID, connID)\n}\n\nfunc getOfflineSessions(ctx context.Context, q querier, userID string, connID string) (storage.OfflineSessions, error) {\n\treturn scanOfflineSessions(q.QueryRow(`\n\t\tselect\n\t\t\tuser_id, conn_id, refresh, connector_data\n\t\tfrom offline_session\n\t\twhere user_id = $1 AND conn_id = $2;\n\t\t`, userID, connID))\n}\n\nfunc scanOfflineSessions(s scanner) (o storage.OfflineSessions, err error) {\n\terr = s.Scan(\n\t\t&o.UserID, &o.ConnID, decoder(&o.Refresh), &o.ConnectorData,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn o, storage.ErrNotFound\n\t\t}\n\t\treturn o, fmt.Errorf(\"select offline session: %v\", err)\n\t}\n\treturn o, nil\n}\n\nfunc (c *conn) CreateUserIdentity(ctx context.Context, u storage.UserIdentity) error {\n\t_, err := c.Exec(`\n\t\tinsert into user_identity (\n\t\t\tuser_id, connector_id,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconsents, mfa_secrets,\n\t\t\tcreated_at, last_login, blocked_until\n\t\t)\n\t\tvalues (\n\t\t\t$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13\n\t\t);\n\t`,\n\t\tu.UserID, u.ConnectorID,\n\t\tu.Claims.UserID, u.Claims.Username, u.Claims.PreferredUsername,\n\t\tu.Claims.Email, u.Claims.EmailVerified, encoder(u.Claims.Groups),\n\t\tencoder(u.Consents), encoder(u.MFASecrets),\n\t\tu.CreatedAt, u.LastLogin, u.BlockedUntil,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert user identity: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) UpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u storage.UserIdentity) (storage.UserIdentity, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tu, err := getUserIdentity(ctx, tx, userID, connectorID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewIdentity, err := updater(u)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate user_identity\n\t\t\tset\n\t\t\t\tclaims_user_id = $1,\n\t\t\t\tclaims_username = $2,\n\t\t\t\tclaims_preferred_username = $3,\n\t\t\t\tclaims_email = $4,\n\t\t\t\tclaims_email_verified = $5,\n\t\t\t\tclaims_groups = $6,\n\t\t\t\tconsents = $7,\n\t\t\t\tmfa_secrets = $8,\n\t\t\t\tcreated_at = $9,\n\t\t\t\tlast_login = $10,\n\t\t\t\tblocked_until = $11\n\t\t\twhere user_id = $12 AND connector_id = $13;\n\t\t`,\n\t\t\tnewIdentity.Claims.UserID, newIdentity.Claims.Username, newIdentity.Claims.PreferredUsername,\n\t\t\tnewIdentity.Claims.Email, newIdentity.Claims.EmailVerified, encoder(newIdentity.Claims.Groups),\n\t\t\tencoder(newIdentity.Consents), encoder(newIdentity.MFASecrets),\n\t\t\tnewIdentity.CreatedAt, newIdentity.LastLogin, newIdentity.BlockedUntil,\n\t\t\tu.UserID, u.ConnectorID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update user identity: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetUserIdentity(ctx context.Context, userID, connectorID string) (storage.UserIdentity, error) {\n\treturn getUserIdentity(ctx, c, userID, connectorID)\n}\n\nfunc getUserIdentity(ctx context.Context, q querier, userID, connectorID string) (storage.UserIdentity, error) {\n\treturn scanUserIdentity(q.QueryRow(`\n\t\tselect\n\t\t\tuser_id, connector_id,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconsents, mfa_secrets,\n\t\t\tcreated_at, last_login, blocked_until\n\t\tfrom user_identity\n\t\twhere user_id = $1 AND connector_id = $2;\n\t\t`, userID, connectorID))\n}\n\nfunc (c *conn) ListUserIdentities(ctx context.Context) ([]storage.UserIdentity, error) {\n\trows, err := c.Query(`\n\t\tselect\n\t\t\tuser_id, connector_id,\n\t\t\tclaims_user_id, claims_username, claims_preferred_username,\n\t\t\tclaims_email, claims_email_verified, claims_groups,\n\t\t\tconsents, mfa_secrets,\n\t\t\tcreated_at, last_login, blocked_until\n\t\tfrom user_identity;\n\t`)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"query: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar identities []storage.UserIdentity\n\tfor rows.Next() {\n\t\tu, err := scanUserIdentity(rows)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tidentities = append(identities, u)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"scan: %v\", err)\n\t}\n\treturn identities, nil\n}\n\nfunc scanUserIdentity(s scanner) (u storage.UserIdentity, err error) {\n\tvar mfaSecrets []byte\n\terr = s.Scan(\n\t\t&u.UserID, &u.ConnectorID,\n\t\t&u.Claims.UserID, &u.Claims.Username, &u.Claims.PreferredUsername,\n\t\t&u.Claims.Email, &u.Claims.EmailVerified, decoder(&u.Claims.Groups),\n\t\tdecoder(&u.Consents), &mfaSecrets,\n\t\t&u.CreatedAt, &u.LastLogin, &u.BlockedUntil,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn u, storage.ErrNotFound\n\t\t}\n\t\treturn u, fmt.Errorf(\"select user identity: %v\", err)\n\t}\n\tif u.Consents == nil {\n\t\tu.Consents = make(map[string][]string)\n\t}\n\tif len(mfaSecrets) > 0 {\n\t\tif err := json.Unmarshal(mfaSecrets, &u.MFASecrets); err != nil {\n\t\t\treturn u, fmt.Errorf(\"unmarshal user identity mfa secrets: %v\", err)\n\t\t}\n\t}\n\treturn u, nil\n}\n\nfunc (c *conn) DeleteUserIdentity(ctx context.Context, userID, connectorID string) error {\n\tresult, err := c.Exec(`delete from user_identity where user_id = $1 AND connector_id = $2`, userID, connectorID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"delete user_identity: user_id = %s, connector_id = %s: %w\", userID, connectorID, err)\n\t}\n\n\t// For now mandate that the driver implements RowsAffected. If we ever need to support\n\t// a driver that doesn't implement this, we can run this in a transaction with a get beforehand.\n\tn, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"rows affected: %v\", err)\n\t}\n\tif n < 1 {\n\t\treturn storage.ErrNotFound\n\t}\n\treturn nil\n}\n\nfunc (c *conn) CreateAuthSession(ctx context.Context, s storage.AuthSession) error {\n\t_, err := c.Exec(`\n\t\tinsert into auth_session (\n\t\t\tuser_id, connector_id, nonce,\n\t\t\tclient_states,\n\t\t\tcreated_at, last_activity,\n\t\t\tip_address, user_agent,\n\t\t\tabsolute_expiry, idle_expiry\n\t\t)\n\t\tvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);\n\t`,\n\t\ts.UserID, s.ConnectorID, s.Nonce,\n\t\tencoder(s.ClientStates),\n\t\ts.CreatedAt, s.LastActivity,\n\t\ts.IPAddress, s.UserAgent,\n\t\ts.AbsoluteExpiry, s.IdleExpiry,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert auth session: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) UpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s storage.AuthSession) (storage.AuthSession, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\ts, err := getAuthSession(ctx, tx, userID, connectorID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewSession, err := updater(s)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate auth_session\n\t\t\tset\n\t\t\t\tclient_states = $1,\n\t\t\t\tlast_activity = $2,\n\t\t\t\tip_address = $3,\n\t\t\t\tuser_agent = $4\n\t\t\twhere user_id = $5 AND connector_id = $6;\n\t\t`,\n\t\t\tencoder(newSession.ClientStates),\n\t\t\tnewSession.LastActivity,\n\t\t\tnewSession.IPAddress, newSession.UserAgent,\n\t\t\tuserID, connectorID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update auth session: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetAuthSession(ctx context.Context, userID, connectorID string) (storage.AuthSession, error) {\n\treturn getAuthSession(ctx, c, userID, connectorID)\n}\n\nfunc getAuthSession(ctx context.Context, q querier, userID, connectorID string) (storage.AuthSession, error) {\n\treturn scanAuthSession(q.QueryRow(`\n\t\tselect\n\t\t\tuser_id, connector_id, nonce,\n\t\t\tclient_states,\n\t\t\tcreated_at, last_activity,\n\t\t\tip_address, user_agent,\n\t\t\tabsolute_expiry, idle_expiry\n\t\tfrom auth_session\n\t\twhere user_id = $1 AND connector_id = $2;\n\t`, userID, connectorID))\n}\n\nfunc scanAuthSession(s scanner) (session storage.AuthSession, err error) {\n\terr = s.Scan(\n\t\t&session.UserID, &session.ConnectorID, &session.Nonce,\n\t\tdecoder(&session.ClientStates),\n\t\t&session.CreatedAt, &session.LastActivity,\n\t\t&session.IPAddress, &session.UserAgent,\n\t\t&session.AbsoluteExpiry, &session.IdleExpiry,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn session, storage.ErrNotFound\n\t\t}\n\t\treturn session, fmt.Errorf(\"select auth session: %v\", err)\n\t}\n\tif session.ClientStates == nil {\n\t\tsession.ClientStates = make(map[string]*storage.ClientAuthState)\n\t}\n\treturn session, nil\n}\n\nfunc (c *conn) ListAuthSessions(ctx context.Context) ([]storage.AuthSession, error) {\n\trows, err := c.Query(`\n\t\tselect\n\t\t\tuser_id, connector_id, nonce,\n\t\t\tclient_states,\n\t\t\tcreated_at, last_activity,\n\t\t\tip_address, user_agent,\n\t\t\tabsolute_expiry, idle_expiry\n\t\tfrom auth_session;\n\t`)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"query: %v\", err)\n\t}\n\tdefer rows.Close()\n\n\tvar sessions []storage.AuthSession\n\tfor rows.Next() {\n\t\ts, err := scanAuthSession(rows)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tsessions = append(sessions, s)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, fmt.Errorf(\"scan: %v\", err)\n\t}\n\treturn sessions, nil\n}\n\nfunc (c *conn) DeleteAuthSession(ctx context.Context, userID, connectorID string) error {\n\tresult, err := c.Exec(`delete from auth_session where user_id = $1 AND connector_id = $2`, userID, connectorID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"delete auth_session: user_id = %s, connector_id = %s: %w\", userID, connectorID, err)\n\t}\n\n\tn, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"rows affected: %v\", err)\n\t}\n\tif n < 1 {\n\t\treturn storage.ErrNotFound\n\t}\n\treturn nil\n}\n\nfunc (c *conn) CreateConnector(ctx context.Context, connector storage.Connector) error {\n\tgrantTypes, err := json.Marshal(connector.GrantTypes)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"marshal connector grant types: %v\", err)\n\t}\n\t_, err = c.Exec(`\n\t\tinsert into connector (\n\t\t\tid, type, name, resource_version, config, grant_types\n\t\t)\n\t\tvalues (\n\t\t\t$1, $2, $3, $4, $5, $6\n\t\t);\n\t`,\n\t\tconnector.ID, connector.Type, connector.Name, connector.ResourceVersion, connector.Config, grantTypes,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert connector: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) UpdateConnector(ctx context.Context, id string, updater func(s storage.Connector) (storage.Connector, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tconnector, err := getConnector(ctx, tx, id)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tnewConn, err := updater(connector)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tgrantTypes, err := json.Marshal(newConn.GrantTypes)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"marshal connector grant types: %v\", err)\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate connector\n\t\t\tset\n\t\t\t    type = $1,\n\t\t\t    name = $2,\n\t\t\t    resource_version = $3,\n\t\t\t    config = $4,\n\t\t\t    grant_types = $5\n\t\t\twhere id = $6;\n\t\t`,\n\t\t\tnewConn.Type, newConn.Name, newConn.ResourceVersion, newConn.Config, grantTypes, connector.ID,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update connector: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n\nfunc (c *conn) GetConnector(ctx context.Context, id string) (storage.Connector, error) {\n\treturn getConnector(ctx, c, id)\n}\n\nfunc getConnector(ctx context.Context, q querier, id string) (storage.Connector, error) {\n\treturn scanConnector(q.QueryRow(`\n\t\tselect\n\t\t\tid, type, name, resource_version, config, grant_types\n\t\tfrom connector\n\t\twhere id = $1;\n\t\t`, id))\n}\n\nfunc scanConnector(s scanner) (c storage.Connector, err error) {\n\tvar grantTypes []byte\n\terr = s.Scan(\n\t\t&c.ID, &c.Type, &c.Name, &c.ResourceVersion, &c.Config, &grantTypes,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn c, storage.ErrNotFound\n\t\t}\n\t\treturn c, fmt.Errorf(\"select connector: %v\", err)\n\t}\n\tif len(grantTypes) > 0 {\n\t\tif err := json.Unmarshal(grantTypes, &c.GrantTypes); err != nil {\n\t\t\treturn c, fmt.Errorf(\"unmarshal connector grant types: %v\", err)\n\t\t}\n\t}\n\treturn c, nil\n}\n\nfunc (c *conn) ListConnectors(ctx context.Context) ([]storage.Connector, error) {\n\trows, err := c.Query(`\n\t\tselect\n\t\t\tid, type, name, resource_version, config, grant_types\n\t\tfrom connector;\n\t`)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rows.Close()\n\n\tvar connectors []storage.Connector\n\tfor rows.Next() {\n\t\tconn, err := scanConnector(rows)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tconnectors = append(connectors, conn)\n\t}\n\tif err := rows.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn connectors, nil\n}\n\nfunc (c *conn) DeleteAuthRequest(ctx context.Context, id string) error {\n\treturn c.delete(\"auth_request\", \"id\", id)\n}\n\nfunc (c *conn) DeleteAuthCode(ctx context.Context, id string) error {\n\treturn c.delete(\"auth_code\", \"id\", id)\n}\n\nfunc (c *conn) DeleteClient(ctx context.Context, id string) error {\n\treturn c.delete(\"client\", \"id\", id)\n}\n\nfunc (c *conn) DeleteRefresh(ctx context.Context, id string) error {\n\treturn c.delete(\"refresh_token\", \"id\", id)\n}\n\nfunc (c *conn) DeletePassword(ctx context.Context, email string) error {\n\treturn c.delete(\"password\", \"email\", strings.ToLower(email))\n}\n\nfunc (c *conn) DeleteConnector(ctx context.Context, id string) error {\n\treturn c.delete(\"connector\", \"id\", id)\n}\n\nfunc (c *conn) DeleteOfflineSessions(ctx context.Context, userID string, connID string) error {\n\tresult, err := c.Exec(`delete from offline_session where user_id = $1 AND conn_id = $2`, userID, connID)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"delete offline_session: user_id = %s, conn_id = %s\", userID, connID)\n\t}\n\n\t// For now mandate that the driver implements RowsAffected. If we ever need to support\n\t// a driver that doesn't implement this, we can run this in a transaction with a get beforehand.\n\tn, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"rows affected: %v\", err)\n\t}\n\tif n < 1 {\n\t\treturn storage.ErrNotFound\n\t}\n\treturn nil\n}\n\n// Do NOT call directly. Does not escape table.\nfunc (c *conn) delete(table, field, id string) error {\n\tresult, err := c.Exec(`delete from `+table+` where `+field+` = $1`, id)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"delete %s: %v\", table, id)\n\t}\n\n\t// For now mandate that the driver implements RowsAffected. If we ever need to support\n\t// a driver that doesn't implement this, we can run this in a transaction with a get beforehand.\n\tn, err := result.RowsAffected()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"rows affected: %v\", err)\n\t}\n\tif n < 1 {\n\t\treturn storage.ErrNotFound\n\t}\n\treturn nil\n}\n\nfunc (c *conn) CreateDeviceRequest(ctx context.Context, d storage.DeviceRequest) error {\n\t_, err := c.Exec(`\n\t\tinsert into device_request (\n\t\t\tuser_code, device_code, client_id, client_secret, scopes, expiry\n\t\t)\n\t\tvalues (\n\t\t\t$1, $2, $3, $4, $5, $6\n\t\t);`,\n\t\td.UserCode, d.DeviceCode, d.ClientID, d.ClientSecret, encoder(d.Scopes), d.Expiry,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert device request: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) CreateDeviceToken(ctx context.Context, t storage.DeviceToken) error {\n\t_, err := c.Exec(`\n\t\tinsert into device_token (\n\t\t\tdevice_code, status, token, expiry, last_request, poll_interval, code_challenge, code_challenge_method\n\t\t)\n\t\tvalues (\n\t\t\t$1, $2, $3, $4, $5, $6, $7, $8\n\t\t);`,\n\t\tt.DeviceCode, t.Status, t.Token, t.Expiry, t.LastRequestTime, t.PollIntervalSeconds, t.PKCE.CodeChallenge, t.PKCE.CodeChallengeMethod,\n\t)\n\tif err != nil {\n\t\tif c.alreadyExistsCheck(err) {\n\t\t\treturn storage.ErrAlreadyExists\n\t\t}\n\t\treturn fmt.Errorf(\"insert device token: %v\", err)\n\t}\n\treturn nil\n}\n\nfunc (c *conn) GetDeviceRequest(ctx context.Context, userCode string) (storage.DeviceRequest, error) {\n\treturn getDeviceRequest(ctx, c, userCode)\n}\n\nfunc getDeviceRequest(ctx context.Context, q querier, userCode string) (d storage.DeviceRequest, err error) {\n\terr = q.QueryRow(`\n\t\tselect\n            device_code, client_id, client_secret, scopes, expiry\n\t\tfrom device_request where user_code = $1;\n\t`, userCode).Scan(\n\t\t&d.DeviceCode, &d.ClientID, &d.ClientSecret, decoder(&d.Scopes), &d.Expiry,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn d, storage.ErrNotFound\n\t\t}\n\t\treturn d, fmt.Errorf(\"select device token: %v\", err)\n\t}\n\td.UserCode = userCode\n\treturn d, nil\n}\n\nfunc (c *conn) GetDeviceToken(ctx context.Context, deviceCode string) (storage.DeviceToken, error) {\n\treturn getDeviceToken(ctx, c, deviceCode)\n}\n\nfunc getDeviceToken(ctx context.Context, q querier, deviceCode string) (a storage.DeviceToken, err error) {\n\terr = q.QueryRow(`\n\t\tselect\n            status, token, expiry, last_request, poll_interval, code_challenge, code_challenge_method\n\t\tfrom device_token where device_code = $1;\n\t`, deviceCode).Scan(\n\t\t&a.Status, &a.Token, &a.Expiry, &a.LastRequestTime, &a.PollIntervalSeconds, &a.PKCE.CodeChallenge, &a.PKCE.CodeChallengeMethod,\n\t)\n\tif err != nil {\n\t\tif err == sql.ErrNoRows {\n\t\t\treturn a, storage.ErrNotFound\n\t\t}\n\t\treturn a, fmt.Errorf(\"select device token: %v\", err)\n\t}\n\ta.DeviceCode = deviceCode\n\treturn a, nil\n}\n\nfunc (c *conn) UpdateDeviceToken(ctx context.Context, deviceCode string, updater func(old storage.DeviceToken) (storage.DeviceToken, error)) error {\n\treturn c.ExecTx(func(tx *trans) error {\n\t\tr, err := getDeviceToken(ctx, tx, deviceCode)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif r, err = updater(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t\t_, err = tx.Exec(`\n\t\t\tupdate device_token\n\t\t\tset\n\t\t\t\tstatus = $1,\n\t\t\t\ttoken = $2,\n\t\t\t\tlast_request = $3,\n\t\t\t\tpoll_interval = $4,\n\t\t\t\tcode_challenge = $5,\n\t\t\t\tcode_challenge_method = $6\n\t\t\twhere\n\t\t\t\tdevice_code = $7\n\t\t`,\n\t\t\tr.Status, r.Token, r.LastRequestTime, r.PollIntervalSeconds, r.PKCE.CodeChallenge, r.PKCE.CodeChallengeMethod, r.DeviceCode,\n\t\t)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"update device token: %v\", err)\n\t\t}\n\t\treturn nil\n\t})\n}\n"
  },
  {
    "path": "storage/sql/crud_test.go",
    "content": "//go:build cgo\n// +build cgo\n\npackage sql\n\nimport (\n\t\"database/sql\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestDecoder(t *testing.T) {\n\tdb, err := sql.Open(\"sqlite3\", \":memory:\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer db.Close()\n\n\tif _, err := db.Exec(`create table foo ( id integer primary key, bar blob );`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif _, err := db.Exec(`insert into foo ( id, bar ) values (1, ?);`, []byte(`[\"a\", \"b\"]`)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tvar got []string\n\tif err := db.QueryRow(`select bar from foo where id = 1;`).Scan(decoder(&got)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\twant := []string{\"a\", \"b\"}\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"wanted %q got %q\", want, got)\n\t}\n}\n\nfunc TestEncoder(t *testing.T) {\n\tdb, err := sql.Open(\"sqlite3\", \":memory:\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer db.Close()\n\n\tif _, err := db.Exec(`create table foo ( id integer primary key, bar blob );`); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tput := []string{\"a\", \"b\"}\n\tif _, err := db.Exec(`insert into foo ( id, bar ) values (1, ?)`, encoder(put)); err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tvar got []byte\n\tif err := db.QueryRow(`select bar from foo where id = 1;`).Scan(&got); err != nil {\n\t\tt.Fatal(err)\n\t}\n\twant := []byte(`[\"a\",\"b\"]`)\n\tif !reflect.DeepEqual(got, want) {\n\t\tt.Errorf(\"wanted %q got %q\", want, got)\n\t}\n}\n"
  },
  {
    "path": "storage/sql/migrate.go",
    "content": "package sql\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n)\n\nfunc (c *conn) migrate() (int, error) {\n\t_, err := c.Exec(`\n\t\tcreate table if not exists migrations (\n\t\t\tnum integer not null,\n\t\t\tat timestamptz not null\n\t\t);\n\t`)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"creating migration table: %v\", err)\n\t}\n\n\ti := 0\n\tdone := false\n\n\tvar flavorMigrations []migration\n\tfor _, m := range migrations {\n\t\tif m.flavor == nil || m.flavor == c.flavor {\n\t\t\tflavorMigrations = append(flavorMigrations, m)\n\t\t}\n\t}\n\n\tfor {\n\t\terr := c.ExecTx(func(tx *trans) error {\n\t\t\t// Within a transaction, perform a single migration.\n\t\t\tvar (\n\t\t\t\tnum sql.NullInt64\n\t\t\t\tn   int\n\t\t\t)\n\t\t\tif err := tx.QueryRow(`select max(num) from migrations;`).Scan(&num); err != nil {\n\t\t\t\treturn fmt.Errorf(\"select max migration: %v\", err)\n\t\t\t}\n\t\t\tif num.Valid {\n\t\t\t\tn = int(num.Int64)\n\t\t\t}\n\t\t\tif n >= len(flavorMigrations) {\n\t\t\t\tdone = true\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tmigrationNum := n + 1\n\t\t\tm := flavorMigrations[n]\n\t\t\tfor i := range m.stmts {\n\t\t\t\tif _, err := tx.Exec(m.stmts[i]); err != nil {\n\t\t\t\t\treturn fmt.Errorf(\"migration %d statement %d failed: %v\", migrationNum, i+1, err)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tq := `insert into migrations (num, at) values ($1, now());`\n\t\t\tif _, err := tx.Exec(q, migrationNum); err != nil {\n\t\t\t\treturn fmt.Errorf(\"update migration table: %v\", err)\n\t\t\t}\n\t\t\treturn nil\n\t\t})\n\t\tif err != nil {\n\t\t\treturn i, err\n\t\t}\n\t\tif done {\n\t\t\tbreak\n\t\t}\n\t\ti++\n\t}\n\n\treturn i, nil\n}\n\ntype migration struct {\n\tstmts []string\n\n\t// If flavor is nil the migration will take place for all database backend flavors.\n\t// If specified, only for that corresponding flavor, in that case stmts can be written\n\t// in the specific SQL dialect.\n\tflavor *flavor\n}\n\n// All SQL flavors share migration strategies.\nvar migrations = []migration{\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\tcreate table client (\n\t\t\t\tid text not null primary key,\n\t\t\t\tsecret text not null,\n\t\t\t\tredirect_uris bytea not null, -- JSON array of strings\n\t\t\t\ttrusted_peers bytea not null, -- JSON array of strings\n\t\t\t\tpublic boolean not null,\n\t\t\t\tname text not null,\n\t\t\t\tlogo_url text not null\n\t\t\t);`,\n\t\t\t`\n\t\t\tcreate table auth_request (\n\t\t\t\tid text not null primary key,\n\t\t\t\tclient_id text not null,\n\t\t\t\tresponse_types bytea not null, -- JSON array of strings\n\t\t\t\tscopes bytea not null,         -- JSON array of strings\n\t\t\t\tredirect_uri text not null,\n\t\t\t\tnonce text not null,\n\t\t\t\tstate text not null,\n\t\t\t\tforce_approval_prompt boolean not null,\n\n\t\t\t\tlogged_in boolean not null,\n\n\t\t\t\tclaims_user_id text not null,\n\t\t\t\tclaims_username text not null,\n\t\t\t\tclaims_email text not null,\n\t\t\t\tclaims_email_verified boolean not null,\n\t\t\t\tclaims_groups bytea not null, -- JSON array of strings\n\n\t\t\t\tconnector_id text not null,\n\t\t\t\tconnector_data bytea,\n\n\t\t\t\texpiry timestamptz not null\n\t\t\t);`,\n\t\t\t`\n\t\t\tcreate table auth_code (\n\t\t\t\tid text not null primary key,\n\t\t\t\tclient_id text not null,\n\t\t\t\tscopes bytea not null, -- JSON array of strings\n\t\t\t\tnonce text not null,\n\t\t\t\tredirect_uri text not null,\n\n\t\t\t\tclaims_user_id text not null,\n\t\t\t\tclaims_username text not null,\n\t\t\t\tclaims_email text not null,\n\t\t\t\tclaims_email_verified boolean not null,\n\t\t\t\tclaims_groups bytea not null, -- JSON array of strings\n\n\t\t\t\tconnector_id text not null,\n\t\t\t\tconnector_data bytea,\n\n\t\t\t\texpiry timestamptz not null\n\t\t\t);`,\n\t\t\t`\n\t\t\tcreate table refresh_token (\n\t\t\t\tid text not null primary key,\n\t\t\t\tclient_id text not null,\n\t\t\t\tscopes bytea not null, -- JSON array of strings\n\t\t\t\tnonce text not null,\n\n\t\t\t\tclaims_user_id text not null,\n\t\t\t\tclaims_username text not null,\n\t\t\t\tclaims_email text not null,\n\t\t\t\tclaims_email_verified boolean not null,\n\t\t\t\tclaims_groups bytea not null, -- JSON array of strings\n\n\t\t\t\tconnector_id text not null,\n\t\t\t\tconnector_data bytea\n\t\t\t);`,\n\t\t\t`\n\t\t\tcreate table password (\n\t\t\t\temail text not null primary key,\n\t\t\t\thash bytea not null,\n\t\t\t\tusername text not null,\n\t\t\t\tuser_id text not null\n\t\t\t);`,\n\t\t\t`\n\t\t\t-- keys is a weird table because we only ever expect there to be a single row\n\t\t\tcreate table keys (\n\t\t\t\tid text not null primary key,\n\t\t\t\tverification_keys bytea not null, -- JSON array\n\t\t\t\tsigning_key bytea not null,       -- JSON object\n\t\t\t\tsigning_key_pub bytea not null,   -- JSON object\n\t\t\t\tnext_rotation timestamptz not null\n\t\t\t);`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table refresh_token\n\t\t\t\tadd column token text not null default '';`,\n\t\t\t`\n\t\t\talter table refresh_token\n\t\t\t\tadd column created_at timestamptz not null default '0001-01-01 00:00:00 UTC';`,\n\t\t\t`\n\t\t\talter table refresh_token\n\t\t\t\tadd column last_used timestamptz not null default '0001-01-01 00:00:00 UTC';`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\tcreate table offline_session (\n\t\t\t\tuser_id text not null,\n\t\t\t\tconn_id text not null,\n\t\t\t\trefresh bytea not null,\n\t\t\t\tPRIMARY KEY (user_id, conn_id)\n\t\t\t);`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\tcreate table connector (\n\t\t\t\tid text not null primary key,\n\t\t\t\ttype text not null,\n\t\t\t\tname text not null,\n\t\t\t\tresource_version text not null,\n\t\t\t\tconfig bytea\n\t\t\t);`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table auth_code\n\t\t\t\tadd column claims_preferred_username text not null default '';`,\n\t\t\t`\n\t\t\talter table auth_request\n\t\t\t\tadd column claims_preferred_username text not null default '';`,\n\t\t\t`\n\t\t\talter table refresh_token\n\t\t\t\tadd column claims_preferred_username text not null default '';`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table offline_session\n\t\t\t\tadd column connector_data bytea;\n\t\t\t`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table auth_request\n\t\t\t\tmodify column state varchar(4096);\n\t\t\t`,\n\t\t},\n\t\tflavor: &flavorMySQL,\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\tcreate table device_request (\n\t\t\t\tuser_code text not null primary key,\n\t\t\t\tdevice_code text not null,\n\t\t\t\tclient_id text not null,\n\t\t\t\tclient_secret text ,\n\t\t\t\tscopes bytea not null, -- JSON array of strings\n\t\t\t\texpiry timestamptz not null\n\t\t\t);`,\n\t\t\t`\n\t\t\tcreate table device_token (\n\t\t\t\tdevice_code text not null primary key,\n\t\t\t\tstatus text not null,\n\t\t\t\ttoken bytea,\n\t\t\t\texpiry timestamptz not null,\n\t\t\t\tlast_request timestamptz not null,\n                poll_interval integer not null\n\t\t\t);`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table auth_request\n\t\t\t\tadd column code_challenge text not null default '';`,\n\t\t\t`\n\t\t\talter table auth_request\n\t\t\t\tadd column code_challenge_method text not null default '';`,\n\t\t\t`\n\t\t\talter table auth_code\n\t\t\t\tadd column code_challenge text not null default '';`,\n\t\t\t`\n\t\t\talter table auth_code\n\t\t\t\tadd column code_challenge_method text not null default '';`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table refresh_token\n\t\t\t\tadd column obsolete_token text default '';`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table device_token\n\t\t\t\tadd column code_challenge text not null default '';`,\n\t\t\t`\n\t\t\talter table device_token\n\t\t\t\tadd column code_challenge_method text not null default '';`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table auth_request\n\t\t\t\tadd column hmac_key bytea;`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column preferred_username text not null default '';`,\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column groups bytea not null default convert_to('[]', 'UTF8');`,\n\t\t},\n\t\tflavor: &flavorPostgres,\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column preferred_username text not null default '';`,\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column groups bytea not null default '[]';`,\n\t\t},\n\t\tflavor: &flavorSQLite3,\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column preferred_username text not null default '';`,\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column groups bytea;`,\n\t\t\t`\n\t\t\tupdate password\n\t\t\t\tset groups = '[]'\n\t\t\t\twhere groups is null;`,\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tmodify column groups bytea not null;`,\n\t\t},\n\t\tflavor: &flavorMySQL,\n\t},\n\t// Migration for adding name and email_verified to password table (Postgres)\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column name text not null default '';`,\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column email_verified boolean;`,\n\t\t},\n\t\tflavor: &flavorPostgres,\n\t},\n\t// Migration for adding name and email_verified to password table (SQLite3)\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column name text not null default '';`,\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column email_verified boolean;`,\n\t\t},\n\t\tflavor: &flavorSQLite3,\n\t},\n\t// Migration for adding name and email_verified to password table (MySQL)\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column name text not null default '';`,\n\t\t\t`\n\t\t\talter table password\n\t\t\t\tadd column email_verified boolean;`,\n\t\t},\n\t\tflavor: &flavorMySQL,\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table connector\n\t\t\t\tadd column grant_types bytea;`,\n\t\t},\n\t},\n\t// Migration for adding allowed_connectors to client table\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table client\n\t\t\t\tadd column allowed_connectors bytea;`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\tcreate table user_identity (\n\t\t\t\tuser_id text not null,\n\t\t\t\tconnector_id text not null,\n\t\t\t\tclaims_user_id text not null,\n\t\t\t\tclaims_username text not null,\n\t\t\t\tclaims_preferred_username text not null default '',\n\t\t\t\tclaims_email text not null,\n\t\t\t\tclaims_email_verified boolean not null,\n\t\t\t\tclaims_groups bytea not null,\n\t\t\t\tconsents bytea not null,\n\t\t\t\tcreated_at timestamptz not null,\n\t\t\t\tlast_login timestamptz not null,\n\t\t\t\tblocked_until timestamptz not null,\n\t\t\t\tPRIMARY KEY (user_id, connector_id)\n\t\t\t);`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\tcreate table auth_session (\n\t\t\t\tuser_id text not null,\n\t\t\t\tconnector_id text not null,\n\t\t\t\tnonce text not null default '',\n\t\t\t\tclient_states bytea not null,\n\t\t\t\tcreated_at timestamptz not null,\n\t\t\t\tlast_activity timestamptz not null,\n\t\t\t\tip_address text not null default '',\n\t\t\t\tuser_agent text not null default '',\n\t\t\t\tPRIMARY KEY (user_id, connector_id)\n\t\t\t);`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table auth_request\n\t\t\t\tadd column mfa_validated boolean not null default false;`,\n\t\t\t`\n\t\t\talter table user_identity\n\t\t\t\tadd column mfa_secrets bytea;`,\n\t\t\t`\n\t\t\talter table client\n\t\t\t\tadd column mfa_chain bytea;`,\n\t\t\t`alter table auth_request add column prompt text not null default '';`,\n\t\t\t`alter table auth_request add column max_age integer not null default -1;`,\n\t\t\t`alter table auth_request add column auth_time timestamptz not null default '1970-01-01 00:00:00';`,\n\t\t\t`alter table auth_code add column auth_time timestamptz not null default '1970-01-01 00:00:00';`,\n\t\t},\n\t},\n\t{\n\t\tstmts: []string{\n\t\t\t`\n\t\t\talter table auth_session\n\t\t\t\tadd column absolute_expiry timestamptz not null default '1970-01-01 00:00:00';`,\n\t\t\t`\n\t\t\talter table auth_session\n\t\t\t\tadd column idle_expiry timestamptz not null default '1970-01-01 00:00:00';`,\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "storage/sql/migrate_test.go",
    "content": "//go:build cgo\n// +build cgo\n\npackage sql\n\nimport (\n\t\"database/sql\"\n\t\"log/slog\"\n\t\"testing\"\n\n\tsqlite3 \"github.com/mattn/go-sqlite3\"\n)\n\nfunc TestMigrate(t *testing.T) {\n\tdb, err := sql.Open(\"sqlite3\", \":memory:\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer db.Close()\n\n\tlogger := slog.New(slog.DiscardHandler)\n\n\terrCheck := func(err error) bool {\n\t\tsqlErr, ok := err.(sqlite3.Error)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn sqlErr.ExtendedCode == sqlite3.ErrConstraintUnique\n\t}\n\n\tvar sqliteMigrations []migration\n\tfor _, m := range migrations {\n\t\tif m.flavor == nil || m.flavor == &flavorSQLite3 {\n\t\t\tsqliteMigrations = append(sqliteMigrations, m)\n\t\t}\n\t}\n\n\tc := &conn{db, &flavorSQLite3, logger, errCheck}\n\tfor _, want := range []int{len(sqliteMigrations), 0} {\n\t\tgot, err := c.migrate()\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tif got != want {\n\t\t\tt.Errorf(\"expected %d migrations, got %d\", want, got)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "storage/sql/postgres_test.go",
    "content": "//go:build go1.11\n// +build go1.11\n\npackage sql\n\nimport (\n\t\"log/slog\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nfunc TestPostgresTunables(t *testing.T) {\n\thost := os.Getenv(testPostgresEnv)\n\tif host == \"\" {\n\t\tt.Skipf(\"test environment variable %q not set, skipping\", testPostgresEnv)\n\t}\n\n\tport := uint64(5432)\n\tif rawPort := os.Getenv(\"DEX_POSTGRES_PORT\"); rawPort != \"\" {\n\t\tvar err error\n\n\t\tport, err = strconv.ParseUint(rawPort, 10, 32)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"invalid postgres port %q: %s\", rawPort, err)\n\t\t}\n\t}\n\n\tbaseCfg := &Postgres{\n\t\tNetworkDB: NetworkDB{\n\t\t\tDatabase: getenv(\"DEX_POSTGRES_DATABASE\", \"postgres\"),\n\t\t\tUser:     getenv(\"DEX_POSTGRES_USER\", \"postgres\"),\n\t\t\tPassword: getenv(\"DEX_POSTGRES_PASSWORD\", \"postgres\"),\n\t\t\tHost:     host,\n\t\t\tPort:     uint16(port),\n\t\t},\n\t\tSSL: SSL{\n\t\t\tMode: pgSSLDisable, // Postgres container doesn't support SSL.\n\t\t},\n\t}\n\n\tt.Run(\"with nothing set, uses defaults\", func(t *testing.T) {\n\t\tcfg := *baseCfg\n\t\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\t\tc, err := cfg.open(logger)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error opening connector: %s\", err.Error())\n\t\t}\n\t\tdefer c.db.Close()\n\t\tif m := c.db.Stats().MaxOpenConnections; m != 5 {\n\t\t\tt.Errorf(\"expected MaxOpenConnections to have its default (5), got %d\", m)\n\t\t}\n\t})\n\n\tt.Run(\"with something set, uses that\", func(t *testing.T) {\n\t\tcfg := *baseCfg\n\t\tcfg.MaxOpenConns = 101\n\t\tlogger := slog.New(slog.NewTextHandler(t.Output(), &slog.HandlerOptions{Level: slog.LevelDebug}))\n\t\tc, err := cfg.open(logger)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error opening connector: %s\", err.Error())\n\t\t}\n\t\tdefer c.db.Close()\n\t\tif m := c.db.Stats().MaxOpenConnections; m != 101 {\n\t\t\tt.Errorf(\"expected MaxOpenConnections to be set to 101, got %d\", m)\n\t\t}\n\t})\n}\n"
  },
  {
    "path": "storage/sql/sql.go",
    "content": "// Package sql provides SQL implementations of the storage interface.\npackage sql\n\nimport (\n\t\"database/sql\"\n\t\"log/slog\"\n\t\"regexp\"\n\t\"time\"\n\n\t// import third party drivers\n\t_ \"github.com/lib/pq\"\n\t_ \"github.com/mattn/go-sqlite3\"\n)\n\n// flavor represents a specific SQL implementation, and is used to translate query strings\n// between different drivers. Flavors shouldn't aim to translate all possible SQL statements,\n// only the specific queries used by the SQL storages.\ntype flavor struct {\n\tqueryReplacers []replacer\n\n\t// Optional function to create and finish a transaction.\n\texecuteTx func(db *sql.DB, fn func(*sql.Tx) error) error\n\n\t// Does the flavor support timezones?\n\tsupportsTimezones bool\n}\n\n// A regexp with a replacement string.\ntype replacer struct {\n\tre   *regexp.Regexp\n\twith string\n}\n\n// Match a postgres query binds. E.g. \"$1\", \"$12\", etc.\nvar bindRegexp = regexp.MustCompile(`\\$\\d+`)\n\nfunc matchLiteral(s string) *regexp.Regexp {\n\treturn regexp.MustCompile(`\\b` + regexp.QuoteMeta(s) + `\\b`)\n}\n\nvar (\n\t// The \"github.com/lib/pq\" driver is the default flavor. All others are\n\t// translations of this.\n\tflavorPostgres = flavor{\n\t\t// The default behavior for Postgres transactions is consistent reads, not consistent writes.\n\t\t// For each transaction opened, ensure it has the correct isolation level.\n\t\t//\n\t\t// See: https://www.postgresql.org/docs/9.3/static/sql-set-transaction.html\n\t\t//\n\t\t// NOTE(ericchiang): For some reason using `SET SESSION CHARACTERISTICS AS TRANSACTION` at a\n\t\t// session level didn't work for some edge cases. Might be something worth exploring.\n\t\texecuteTx: func(db *sql.DB, fn func(sqlTx *sql.Tx) error) error {\n\t\t\ttx, err := db.Begin()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer tx.Rollback()\n\n\t\t\tif _, err := tx.Exec(`SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;`); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := fn(tx); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn tx.Commit()\n\t\t},\n\n\t\tsupportsTimezones: true,\n\t}\n\n\tflavorSQLite3 = flavor{\n\t\tqueryReplacers: []replacer{\n\t\t\t{bindRegexp, \"?\"},\n\t\t\t// Translate for booleans to integers.\n\t\t\t{matchLiteral(\"true\"), \"1\"},\n\t\t\t{matchLiteral(\"false\"), \"0\"},\n\t\t\t{matchLiteral(\"boolean\"), \"integer\"},\n\t\t\t// Translate other types.\n\t\t\t{matchLiteral(\"bytea\"), \"blob\"},\n\t\t\t{matchLiteral(\"timestamptz\"), \"timestamp\"},\n\t\t\t// SQLite doesn't have a \"now()\" method, replace with \"date('now')\"\n\t\t\t{regexp.MustCompile(`\\bnow\\(\\)`), \"date('now')\"},\n\t\t},\n\t}\n\n\tflavorMySQL = flavor{\n\t\tqueryReplacers: []replacer{\n\t\t\t{bindRegexp, \"?\"},\n\t\t\t// Translate types.\n\t\t\t{matchLiteral(\"bytea\"), \"blob\"},\n\t\t\t{matchLiteral(\"timestamptz\"), \"datetime(3)\"},\n\t\t\t// MySQL doesn't support indices on text fields w/o\n\t\t\t// specifying key length. Use varchar instead (767 byte\n\t\t\t// is the max key length for InnoDB with 4k pages).\n\t\t\t// For compound indexes (with two keys) even less.\n\t\t\t{matchLiteral(\"text\"), \"varchar(384)\"},\n\t\t\t// Quote keywords and reserved words used as identifiers.\n\t\t\t{regexp.MustCompile(`\\b(keys|groups)\\b`), \"`$1`\"},\n\t\t\t// Change default timestamp to fit datetime.\n\t\t\t{regexp.MustCompile(`0001-01-01 00:00:00 UTC`), \"1000-01-01 00:00:00\"},\n\t\t},\n\t}\n)\n\nfunc (f flavor) translate(query string) string {\n\t// TODO(ericchiang): Heavy cashing.\n\tfor _, r := range f.queryReplacers {\n\t\tquery = r.re.ReplaceAllString(query, r.with)\n\t}\n\treturn query\n}\n\n// translateArgs translates query parameters that may be unique to\n// a specific SQL flavor. For example, standardizing \"time.Time\"\n// types to UTC for clients that don't provide timezone support.\nfunc (c *conn) translateArgs(args []interface{}) []interface{} {\n\tif c.flavor.supportsTimezones {\n\t\treturn args\n\t}\n\n\tfor i, arg := range args {\n\t\tif t, ok := arg.(time.Time); ok {\n\t\t\targs[i] = t.UTC()\n\t\t}\n\t}\n\treturn args\n}\n\n// conn is the main database connection.\ntype conn struct {\n\tdb                 *sql.DB\n\tflavor             *flavor\n\tlogger             *slog.Logger\n\talreadyExistsCheck func(err error) bool\n}\n\nfunc (c *conn) Close() error {\n\treturn c.db.Close()\n}\n\n// conn implements the same method signatures as encoding/sql.DB.\n\nfunc (c *conn) Exec(query string, args ...interface{}) (sql.Result, error) {\n\tquery = c.flavor.translate(query)\n\treturn c.db.Exec(query, c.translateArgs(args)...)\n}\n\nfunc (c *conn) Query(query string, args ...interface{}) (*sql.Rows, error) {\n\tquery = c.flavor.translate(query)\n\treturn c.db.Query(query, c.translateArgs(args)...)\n}\n\nfunc (c *conn) QueryRow(query string, args ...interface{}) *sql.Row {\n\tquery = c.flavor.translate(query)\n\treturn c.db.QueryRow(query, c.translateArgs(args)...)\n}\n\n// ExecTx runs a method which operates on a transaction.\nfunc (c *conn) ExecTx(fn func(tx *trans) error) error {\n\tif c.flavor.executeTx != nil {\n\t\treturn c.flavor.executeTx(c.db, func(sqlTx *sql.Tx) error {\n\t\t\treturn fn(&trans{sqlTx, c})\n\t\t})\n\t}\n\n\tsqlTx, err := c.db.Begin()\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := fn(&trans{sqlTx, c}); err != nil {\n\t\tsqlTx.Rollback()\n\t\treturn err\n\t}\n\treturn sqlTx.Commit()\n}\n\ntype trans struct {\n\ttx *sql.Tx\n\tc  *conn\n}\n\n// trans implements the same method signatures as encoding/sql.Tx.\n\nfunc (t *trans) Exec(query string, args ...interface{}) (sql.Result, error) {\n\tquery = t.c.flavor.translate(query)\n\treturn t.tx.Exec(query, t.c.translateArgs(args)...)\n}\n\nfunc (t *trans) Query(query string, args ...interface{}) (*sql.Rows, error) {\n\tquery = t.c.flavor.translate(query)\n\treturn t.tx.Query(query, t.c.translateArgs(args)...)\n}\n\nfunc (t *trans) QueryRow(query string, args ...interface{}) *sql.Row {\n\tquery = t.c.flavor.translate(query)\n\treturn t.tx.QueryRow(query, t.c.translateArgs(args)...)\n}\n"
  },
  {
    "path": "storage/sql/sql_test.go",
    "content": "package sql\n\nimport \"testing\"\n\nfunc TestTranslate(t *testing.T) {\n\ttests := []struct {\n\t\ttestCase string\n\t\tflavor   flavor\n\t\tquery    string\n\t\texp      string\n\t}{\n\t\t{\n\t\t\t\"sqlite3 query bind replacement\",\n\t\t\tflavorSQLite3,\n\t\t\t`select foo from bar where foo.zam = $1;`,\n\t\t\t`select foo from bar where foo.zam = ?;`,\n\t\t},\n\t\t{\n\t\t\t\"sqlite3 query bind replacement at newline\",\n\t\t\tflavorSQLite3,\n\t\t\t`select foo from bar where foo.zam = $1`,\n\t\t\t`select foo from bar where foo.zam = ?`,\n\t\t},\n\t\t{\n\t\t\t\"sqlite3 query true\",\n\t\t\tflavorSQLite3,\n\t\t\t`select foo from bar where foo.zam = true`,\n\t\t\t`select foo from bar where foo.zam = 1`,\n\t\t},\n\t\t{\n\t\t\t\"sqlite3 query false\",\n\t\t\tflavorSQLite3,\n\t\t\t`select foo from bar where foo.zam = false`,\n\t\t\t`select foo from bar where foo.zam = 0`,\n\t\t},\n\t\t{\n\t\t\t\"sqlite3 bytea\",\n\t\t\tflavorSQLite3,\n\t\t\t`\"connector_data\" bytea not null,`,\n\t\t\t`\"connector_data\" blob not null,`,\n\t\t},\n\t\t{\n\t\t\t\"sqlite3 now\",\n\t\t\tflavorSQLite3,\n\t\t\t`now(),`,\n\t\t\t`date('now'),`,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\t\tif got := tc.flavor.translate(tc.query); got != tc.exp {\n\t\t\tt.Errorf(\"%s: want=%q, got=%q\", tc.testCase, tc.exp, got)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "storage/sql/sqlite.go",
    "content": "//go:build cgo\n// +build cgo\n\npackage sql\n\nimport (\n\t\"database/sql\"\n\t\"fmt\"\n\t\"log/slog\"\n\n\tsqlite3 \"github.com/mattn/go-sqlite3\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\n// SQLite3 options for creating an SQL db.\ntype SQLite3 struct {\n\t// File to\n\tFile string `json:\"file\"`\n}\n\n// Open creates a new storage implementation backed by SQLite3\nfunc (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) {\n\tconn, err := s.open(logger)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn, nil\n}\n\nfunc (s *SQLite3) open(logger *slog.Logger) (*conn, error) {\n\tdb, err := sql.Open(\"sqlite3\", s.File)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// always allow only one connection to sqlite3, any other thread/go-routine\n\t// attempting concurrent access will have to wait\n\tdb.SetMaxOpenConns(1)\n\terrCheck := func(err error) bool {\n\t\tsqlErr, ok := err.(sqlite3.Error)\n\t\tif !ok {\n\t\t\treturn false\n\t\t}\n\t\treturn sqlErr.ExtendedCode == sqlite3.ErrConstraintPrimaryKey\n\t}\n\n\tc := &conn{db, &flavorSQLite3, logger, errCheck}\n\tif _, err := c.migrate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to perform migrations: %v\", err)\n\t}\n\treturn c, nil\n}\n"
  },
  {
    "path": "storage/sql/sqlite_no_cgo.go",
    "content": "//go:build !cgo\n// +build !cgo\n\n// This is a stub for the no CGO compilation (CGO_ENABLED=0)\n\npackage sql\n\nimport (\n\t\"fmt\"\n\t\"log/slog\"\n\n\t\"github.com/dexidp/dex/storage\"\n)\n\ntype SQLite3 struct{}\n\nfunc (s *SQLite3) Open(logger *slog.Logger) (storage.Storage, error) {\n\treturn nil, fmt.Errorf(\"SQLite storage is not available: binary compiled without CGO support. Recompile with CGO_ENABLED=1 or use a different storage backend.\")\n}\n"
  },
  {
    "path": "storage/sql/sqlite_test.go",
    "content": "//go:build cgo\n// +build cgo\n\npackage sql\n\nimport (\n\t\"testing\"\n)\n\nfunc TestSQLite3(t *testing.T) {\n\ttestDB(t, &SQLite3{\":memory:\"}, false, true)\n}\n"
  },
  {
    "path": "storage/static.go",
    "content": "package storage\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log/slog\"\n\t\"strings\"\n)\n\n// Tests for this code are in the \"memory\" package, since this package doesn't\n// define a concrete storage implementation.\n\n// staticClientsStorage is a storage that only allow read-only actions on clients.\n// All read actions return from the list of clients stored in memory, not the\n// underlying\ntype staticClientsStorage struct {\n\tStorage\n\n\t// A read-only set of clients.\n\tclients     []Client\n\tclientsByID map[string]Client\n}\n\n// WithStaticClients adds a read-only set of clients to the underlying storages.\nfunc WithStaticClients(s Storage, staticClients []Client) Storage {\n\tclientsByID := make(map[string]Client, len(staticClients))\n\tfor _, client := range staticClients {\n\t\tclientsByID[client.ID] = client\n\t}\n\n\treturn staticClientsStorage{s, staticClients, clientsByID}\n}\n\nfunc (s staticClientsStorage) GetClient(ctx context.Context, id string) (Client, error) {\n\tif client, ok := s.clientsByID[id]; ok {\n\t\treturn client, nil\n\t}\n\treturn s.Storage.GetClient(ctx, id)\n}\n\nfunc (s staticClientsStorage) isStatic(id string) bool {\n\t_, ok := s.clientsByID[id]\n\treturn ok\n}\n\nfunc (s staticClientsStorage) ListClients(ctx context.Context) ([]Client, error) {\n\tclients, err := s.Storage.ListClients(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tn := 0\n\tfor _, client := range clients {\n\t\t// If a client in the backing storage has the same ID as a static client\n\t\t// prefer the static client.\n\t\tif !s.isStatic(client.ID) {\n\t\t\tclients[n] = client\n\t\t\tn++\n\t\t}\n\t}\n\treturn append(clients[:n], s.clients...), nil\n}\n\nfunc (s staticClientsStorage) CreateClient(ctx context.Context, c Client) error {\n\tif s.isStatic(c.ID) {\n\t\treturn errors.New(\"static clients: read-only cannot create client\")\n\t}\n\treturn s.Storage.CreateClient(ctx, c)\n}\n\nfunc (s staticClientsStorage) DeleteClient(ctx context.Context, id string) error {\n\tif s.isStatic(id) {\n\t\treturn errors.New(\"static clients: read-only cannot delete client\")\n\t}\n\treturn s.Storage.DeleteClient(ctx, id)\n}\n\nfunc (s staticClientsStorage) UpdateClient(ctx context.Context, id string, updater func(old Client) (Client, error)) error {\n\tif s.isStatic(id) {\n\t\treturn errors.New(\"static clients: read-only cannot update client\")\n\t}\n\treturn s.Storage.UpdateClient(ctx, id, updater)\n}\n\ntype staticPasswordsStorage struct {\n\tStorage\n\n\t// A read-only set of passwords.\n\tpasswords []Password\n\t// A map of passwords that is indexed by lower-case email ids\n\tpasswordsByEmail map[string]Password\n\n\tlogger *slog.Logger\n}\n\n// WithStaticPasswords returns a storage with a read-only set of passwords.\nfunc WithStaticPasswords(s Storage, staticPasswords []Password, logger *slog.Logger) Storage {\n\tpasswordsByEmail := make(map[string]Password, len(staticPasswords))\n\tfor _, p := range staticPasswords {\n\t\t// Enable case insensitive email comparison.\n\t\tlowerEmail := strings.ToLower(p.Email)\n\t\tif _, ok := passwordsByEmail[lowerEmail]; ok {\n\t\t\tlogger.Error(\"attempting to create StaticPasswords with the same email id\", \"email\", p.Email)\n\t\t}\n\t\tpasswordsByEmail[lowerEmail] = p\n\t}\n\n\treturn staticPasswordsStorage{s, staticPasswords, passwordsByEmail, logger}\n}\n\nfunc (s staticPasswordsStorage) isStatic(email string) bool {\n\t_, ok := s.passwordsByEmail[strings.ToLower(email)]\n\treturn ok\n}\n\nfunc (s staticPasswordsStorage) GetPassword(ctx context.Context, email string) (Password, error) {\n\t// TODO(ericchiang): BLAH. We really need to figure out how to handle\n\t// lower cased emails better.\n\temail = strings.ToLower(email)\n\tif password, ok := s.passwordsByEmail[email]; ok {\n\t\treturn password, nil\n\t}\n\treturn s.Storage.GetPassword(ctx, email)\n}\n\nfunc (s staticPasswordsStorage) ListPasswords(ctx context.Context) ([]Password, error) {\n\tpasswords, err := s.Storage.ListPasswords(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tn := 0\n\tfor _, password := range passwords {\n\t\t// If an entry has the same email as those provided in the static\n\t\t// values, prefer the static value.\n\t\tif !s.isStatic(password.Email) {\n\t\t\tpasswords[n] = password\n\t\t\tn++\n\t\t}\n\t}\n\treturn append(passwords[:n], s.passwords...), nil\n}\n\nfunc (s staticPasswordsStorage) CreatePassword(ctx context.Context, p Password) error {\n\tif s.isStatic(p.Email) {\n\t\treturn errors.New(\"static passwords: read-only cannot create password\")\n\t}\n\treturn s.Storage.CreatePassword(ctx, p)\n}\n\nfunc (s staticPasswordsStorage) DeletePassword(ctx context.Context, email string) error {\n\tif s.isStatic(email) {\n\t\treturn errors.New(\"static passwords: read-only cannot delete password\")\n\t}\n\treturn s.Storage.DeletePassword(ctx, email)\n}\n\nfunc (s staticPasswordsStorage) UpdatePassword(ctx context.Context, email string, updater func(old Password) (Password, error)) error {\n\tif s.isStatic(email) {\n\t\treturn errors.New(\"static passwords: read-only cannot update password\")\n\t}\n\treturn s.Storage.UpdatePassword(ctx, email, updater)\n}\n\n// staticConnectorsStorage represents a storage with read-only set of connectors.\ntype staticConnectorsStorage struct {\n\tStorage\n\n\t// A read-only set of connectors.\n\tconnectors     []Connector\n\tconnectorsByID map[string]Connector\n}\n\n// WithStaticConnectors returns a storage with a read-only set of Connectors. Write actions,\n// such as updating existing Connectors, will fail.\nfunc WithStaticConnectors(s Storage, staticConnectors []Connector) Storage {\n\tconnectorsByID := make(map[string]Connector, len(staticConnectors))\n\tfor _, c := range staticConnectors {\n\t\tconnectorsByID[c.ID] = c\n\t}\n\treturn staticConnectorsStorage{s, staticConnectors, connectorsByID}\n}\n\nfunc (s staticConnectorsStorage) isStatic(id string) bool {\n\t_, ok := s.connectorsByID[id]\n\treturn ok\n}\n\nfunc (s staticConnectorsStorage) GetConnector(ctx context.Context, id string) (Connector, error) {\n\tif connector, ok := s.connectorsByID[id]; ok {\n\t\treturn connector, nil\n\t}\n\treturn s.Storage.GetConnector(ctx, id)\n}\n\nfunc (s staticConnectorsStorage) ListConnectors(ctx context.Context) ([]Connector, error) {\n\tconnectors, err := s.Storage.ListConnectors(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tn := 0\n\tfor _, connector := range connectors {\n\t\t// If an entry has the same id as those provided in the static\n\t\t// values, prefer the static value.\n\t\tif !s.isStatic(connector.ID) {\n\t\t\tconnectors[n] = connector\n\t\t\tn++\n\t\t}\n\t}\n\treturn append(connectors[:n], s.connectors...), nil\n}\n\nfunc (s staticConnectorsStorage) CreateConnector(ctx context.Context, c Connector) error {\n\tif s.isStatic(c.ID) {\n\t\treturn errors.New(\"static connectors: read-only cannot create connector\")\n\t}\n\treturn s.Storage.CreateConnector(ctx, c)\n}\n\nfunc (s staticConnectorsStorage) DeleteConnector(ctx context.Context, id string) error {\n\tif s.isStatic(id) {\n\t\treturn errors.New(\"static connectors: read-only cannot delete connector\")\n\t}\n\treturn s.Storage.DeleteConnector(ctx, id)\n}\n\nfunc (s staticConnectorsStorage) UpdateConnector(ctx context.Context, id string, updater func(old Connector) (Connector, error)) error {\n\tif s.isStatic(id) {\n\t\treturn errors.New(\"static connectors: read-only cannot update connector\")\n\t}\n\treturn s.Storage.UpdateConnector(ctx, id, updater)\n}\n"
  },
  {
    "path": "storage/storage.go",
    "content": "package storage\n\nimport (\n\t\"context\"\n\t\"crypto\"\n\t\"crypto/rand\"\n\t\"encoding/base32\"\n\t\"errors\"\n\t\"io\"\n\t\"math/big\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-jose/go-jose/v4\"\n)\n\nvar (\n\t// ErrNotFound is the error returned by storages if a resource cannot be found.\n\tErrNotFound = errors.New(\"not found\")\n\n\t// ErrAlreadyExists is the error returned by storages if a resource ID is taken during a create.\n\tErrAlreadyExists = errors.New(\"ID already exists\")\n)\n\n// Kubernetes only allows lower case letters for names.\n//\n// TODO(ericchiang): refactor ID creation onto the storage.\nvar encoding = base32.NewEncoding(\"abcdefghijklmnopqrstuvwxyz234567\")\n\n// Valid characters for user codes\nconst validUserCharacters = \"BCDFGHJKLMNPQRSTVWXZ\"\n\n// NewDeviceCode returns a 32 char alphanumeric cryptographically secure string\nfunc NewDeviceCode() string {\n\treturn newSecureID(32)\n}\n\n// NewID returns a random string which can be used as an ID for objects.\nfunc NewID() string {\n\treturn newSecureID(16)\n}\n\nfunc newSecureID(len int) string {\n\tbuff := make([]byte, len) // random ID.\n\tif _, err := io.ReadFull(rand.Reader, buff); err != nil {\n\t\tpanic(err)\n\t}\n\t// Avoid the identifier to begin with number and trim padding\n\treturn string(buff[0]%26+'a') + strings.TrimRight(encoding.EncodeToString(buff[1:]), \"=\")\n}\n\n// NewHMACKey returns a random key which can be used in the computation of an HMAC\nfunc NewHMACKey(h crypto.Hash) []byte {\n\treturn []byte(newSecureID(h.Size()))\n}\n\n// GCResult returns the number of objects deleted by garbage collection.\ntype GCResult struct {\n\tAuthRequests   int64\n\tAuthCodes      int64\n\tDeviceRequests int64\n\tDeviceTokens   int64\n\tAuthSessions   int64\n}\n\n// IsEmpty returns whether the garbage collection result is empty or not.\nfunc (g *GCResult) IsEmpty() bool {\n\treturn g.AuthRequests == 0 &&\n\t\tg.AuthCodes == 0 &&\n\t\tg.DeviceRequests == 0 &&\n\t\tg.DeviceTokens == 0 &&\n\t\tg.AuthSessions == 0\n}\n\n// Storage is the storage interface used by the server. Implementations are\n// required to be able to perform atomic compare-and-swap updates and either\n// support timezones or standardize on UTC.\ntype Storage interface {\n\tClose() error\n\n\t// TODO(ericchiang): Let the storages set the IDs of these objects.\n\tCreateAuthRequest(ctx context.Context, a AuthRequest) error\n\tCreateClient(ctx context.Context, c Client) error\n\tCreateAuthCode(ctx context.Context, c AuthCode) error\n\tCreateRefresh(ctx context.Context, r RefreshToken) error\n\tCreatePassword(ctx context.Context, p Password) error\n\tCreateOfflineSessions(ctx context.Context, s OfflineSessions) error\n\tCreateUserIdentity(ctx context.Context, u UserIdentity) error\n\tCreateAuthSession(ctx context.Context, s AuthSession) error\n\tCreateConnector(ctx context.Context, c Connector) error\n\tCreateDeviceRequest(ctx context.Context, d DeviceRequest) error\n\tCreateDeviceToken(ctx context.Context, d DeviceToken) error\n\n\t// TODO(ericchiang): return (T, bool, error) so we can indicate not found\n\t// requests that way instead of using ErrNotFound.\n\tGetAuthRequest(ctx context.Context, id string) (AuthRequest, error)\n\tGetAuthCode(ctx context.Context, id string) (AuthCode, error)\n\tGetClient(ctx context.Context, id string) (Client, error)\n\tGetKeys(ctx context.Context) (Keys, error)\n\tGetRefresh(ctx context.Context, id string) (RefreshToken, error)\n\tGetPassword(ctx context.Context, email string) (Password, error)\n\tGetOfflineSessions(ctx context.Context, userID string, connID string) (OfflineSessions, error)\n\tGetUserIdentity(ctx context.Context, userID, connectorID string) (UserIdentity, error)\n\tGetAuthSession(ctx context.Context, userID, connectorID string) (AuthSession, error)\n\tGetConnector(ctx context.Context, id string) (Connector, error)\n\tGetDeviceRequest(ctx context.Context, userCode string) (DeviceRequest, error)\n\tGetDeviceToken(ctx context.Context, deviceCode string) (DeviceToken, error)\n\n\tListClients(ctx context.Context) ([]Client, error)\n\tListRefreshTokens(ctx context.Context) ([]RefreshToken, error)\n\tListPasswords(ctx context.Context) ([]Password, error)\n\tListConnectors(ctx context.Context) ([]Connector, error)\n\tListUserIdentities(ctx context.Context) ([]UserIdentity, error)\n\tListAuthSessions(ctx context.Context) ([]AuthSession, error)\n\n\t// Delete methods MUST be atomic.\n\tDeleteAuthRequest(ctx context.Context, id string) error\n\tDeleteAuthCode(ctx context.Context, code string) error\n\tDeleteClient(ctx context.Context, id string) error\n\tDeleteRefresh(ctx context.Context, id string) error\n\tDeletePassword(ctx context.Context, email string) error\n\tDeleteOfflineSessions(ctx context.Context, userID string, connID string) error\n\tDeleteUserIdentity(ctx context.Context, userID, connectorID string) error\n\tDeleteAuthSession(ctx context.Context, userID, connectorID string) error\n\tDeleteConnector(ctx context.Context, id string) error\n\n\t// Update methods take a function for updating an object then performs that update within\n\t// a transaction. \"updater\" functions may be called multiple times by a single update call.\n\t//\n\t// Because new fields may be added to resources, updaters should only modify existing\n\t// fields on the old object rather then creating new structs. For example:\n\t//\n\t//\t\tupdater := func(old storage.Client) (storage.Client, error) {\n\t//\t\t\told.Secret = newSecret\n\t//\t\t\treturn old, nil\n\t//\t\t}\n\t//\t\tif err := s.UpdateClient(clientID, updater); err != nil {\n\t//\t\t\t// update failed, handle error\n\t//\t\t}\n\t//\n\tUpdateClient(ctx context.Context, id string, updater func(old Client) (Client, error)) error\n\tUpdateKeys(ctx context.Context, updater func(old Keys) (Keys, error)) error\n\tUpdateAuthRequest(ctx context.Context, id string, updater func(a AuthRequest) (AuthRequest, error)) error\n\tUpdateRefreshToken(ctx context.Context, id string, updater func(r RefreshToken) (RefreshToken, error)) error\n\tUpdatePassword(ctx context.Context, email string, updater func(p Password) (Password, error)) error\n\tUpdateOfflineSessions(ctx context.Context, userID string, connID string, updater func(s OfflineSessions) (OfflineSessions, error)) error\n\tUpdateUserIdentity(ctx context.Context, userID, connectorID string, updater func(u UserIdentity) (UserIdentity, error)) error\n\tUpdateAuthSession(ctx context.Context, userID, connectorID string, updater func(s AuthSession) (AuthSession, error)) error\n\tUpdateConnector(ctx context.Context, id string, updater func(c Connector) (Connector, error)) error\n\tUpdateDeviceToken(ctx context.Context, deviceCode string, updater func(t DeviceToken) (DeviceToken, error)) error\n\n\t// GarbageCollect deletes all expired AuthCodes,\n\t// AuthRequests, DeviceRequests, and DeviceTokens.\n\tGarbageCollect(ctx context.Context, now time.Time) (GCResult, error)\n}\n\n// Client represents an OAuth2 client.\n//\n// For further reading see:\n//   - Trusted peers: https://developers.google.com/identity/protocols/CrossClientAuth\n//   - Public clients: https://developers.google.com/api-client-library/python/auth/installed-app\ntype Client struct {\n\t// Client ID and secret used to identify the client.\n\tID        string `json:\"id\"`\n\tIDEnv     string `json:\"idEnv\"`\n\tSecret    string `json:\"secret\"`\n\tSecretEnv string `json:\"secretEnv\"`\n\n\t// A registered set of redirect URIs. When redirecting from dex to the client, the URI\n\t// requested to redirect to MUST match one of these values, unless the client is \"public\".\n\tRedirectURIs []string `json:\"redirectURIs\"`\n\n\t// TrustedPeers are a list of peers which can issue tokens on this client's behalf using\n\t// the dynamic \"oauth2:server:client_id:(client_id)\" scope. If a peer makes such a request,\n\t// this client's ID will appear as the ID Token's audience.\n\t//\n\t// Clients inherently trust themselves.\n\tTrustedPeers []string `json:\"trustedPeers\"`\n\n\t// Public clients must use either use a redirectURL 127.0.0.1:X or \"urn:ietf:wg:oauth:2.0:oob\"\n\tPublic bool `json:\"public\"`\n\n\t// Name and LogoURL used when displaying this client to the end user.\n\tName    string `json:\"name\"`\n\tLogoURL string `json:\"logoURL\"`\n\n\t// AllowedConnectors is a list of connector IDs that the client is allowed to use for authentication.\n\t// If empty, all connectors are allowed.\n\tAllowedConnectors []string `json:\"allowedConnectors\"`\n\n\t// MFAChain is an ordered list of MFA authenticator IDs that a user must complete\n\t// during login. Empty means no MFA required.\n\tMFAChain []string `json:\"mfaChain\"`\n}\n\n// Claims represents the ID Token claims supported by the server.\ntype Claims struct {\n\tUserID            string\n\tUsername          string\n\tPreferredUsername string\n\tEmail             string\n\tEmailVerified     bool\n\n\tGroups []string\n}\n\n// PKCE is a container for the data needed to perform Proof Key for Code Exchange (RFC 7636) auth flow\ntype PKCE struct {\n\tCodeChallenge       string\n\tCodeChallengeMethod string\n}\n\n// AuthRequest represents a OAuth2 client authorization request. It holds the state\n// of a single auth flow up to the point that the user authorizes the client.\ntype AuthRequest struct {\n\t// ID used to identify the authorization request.\n\tID string\n\n\t// ID of the client requesting authorization from a user.\n\tClientID string\n\n\t// Values parsed from the initial request. These describe the resources the client is\n\t// requesting as well as values describing the form of the response.\n\tResponseTypes []string\n\tScopes        []string\n\tRedirectURI   string\n\tNonce         string\n\tState         string\n\n\t// The client has indicated that the end user must be shown an approval prompt\n\t// on all requests. The server cannot cache their initial action for subsequent\n\t// attempts.\n\tForceApprovalPrompt bool\n\n\t// OIDC prompt parameter. Controls authentication and consent UI behavior.\n\t// Values: \"none\", \"login\", \"consent\", \"select_account\".\n\tPrompt string\n\n\t// MaxAge is the OIDC max_age parameter — maximum allowable elapsed time\n\t// in seconds since the user last actively authenticated.\n\t// -1 means not specified.\n\tMaxAge int\n\n\t// AuthTime is when the user last actively authenticated (entered credentials).\n\t// Set during finalizeLogin (= now) or trySessionLogin (= UserIdentity.LastLogin).\n\t// Used in ID token as \"auth_time\" claim.\n\tAuthTime time.Time\n\n\tExpiry time.Time\n\n\t// Has the user proved their identity through a backing identity provider?\n\t//\n\t// If false, the following fields are invalid.\n\tLoggedIn bool\n\n\t// The identity of the end user. Generally nil until the user authenticates\n\t// with a backend.\n\tClaims Claims\n\n\t// The connector used to login the user and any data the connector wishes to persists.\n\t// Set when the user authenticates.\n\tConnectorID   string\n\tConnectorData []byte\n\n\t// PKCE CodeChallenge and CodeChallengeMethod\n\tPKCE PKCE\n\n\t// HMACKey is used when generating an AuthRequest-specific HMAC\n\tHMACKey []byte\n\n\t// MFAValidated is set to true if the user has completed multi-factor authentication.\n\tMFAValidated bool\n}\n\n// AuthCode represents a code which can be exchanged for an OAuth2 token response.\n//\n// This value is created once an end user has authorized a client, the server has\n// redirect the end user back to the client, but the client hasn't exchanged the\n// code for an access_token and id_token.\ntype AuthCode struct {\n\t// Actual string returned as the \"code\" value.\n\tID string\n\n\t// The client this code value is valid for. When exchanging the code for a\n\t// token response, the client must use its client_secret to authenticate.\n\tClientID string\n\n\t// As part of the OAuth2 spec when a client makes a token request it MUST\n\t// present the same redirect_uri as the initial redirect. This values is saved\n\t// to make this check.\n\t//\n\t// https://tools.ietf.org/html/rfc6749#section-4.1.3\n\tRedirectURI string\n\n\t// If provided by the client in the initial request, the provider MUST create\n\t// a ID Token with this nonce in the JWT payload.\n\tNonce string\n\n\t// Scopes authorized by the end user for the client.\n\tScopes []string\n\n\t// Authentication data provided by an upstream source.\n\tConnectorID   string\n\tConnectorData []byte\n\tClaims        Claims\n\n\tExpiry time.Time\n\n\t// AuthTime is when the user last actively authenticated.\n\t// Carried over from AuthRequest to include in ID tokens.\n\tAuthTime time.Time\n\n\t// PKCE CodeChallenge and CodeChallengeMethod\n\tPKCE PKCE\n}\n\n// RefreshToken is an OAuth2 refresh token which allows a client to request new\n// tokens on the end user's behalf.\ntype RefreshToken struct {\n\tID string\n\n\t// A single token that's rotated every time the refresh token is refreshed.\n\t//\n\t// May be empty.\n\tToken         string\n\tObsoleteToken string\n\n\tCreatedAt time.Time\n\tLastUsed  time.Time\n\n\t// Client this refresh token is valid for.\n\tClientID string\n\n\t// Authentication data provided by an upstream source.\n\tConnectorID   string\n\tConnectorData []byte\n\tClaims        Claims\n\n\t// Scopes present in the initial request. Refresh requests may specify a set\n\t// of scopes different from the initial request when refreshing a token,\n\t// however those scopes must be encompassed by this set.\n\tScopes []string\n\n\t// Nonce value supplied during the initial redirect. This is required to be part\n\t// of the claims of any future id_token generated by the client.\n\tNonce string\n}\n\n// RefreshTokenRef is a reference object that contains metadata about refresh tokens.\ntype RefreshTokenRef struct {\n\tID string\n\n\t// Client the refresh token is valid for.\n\tClientID string\n\n\tCreatedAt time.Time\n\tLastUsed  time.Time\n}\n\n// MFASecret stores the enrollment state and secret for an MFA authenticator.\n// Note: Secret is stored without encryption. Encrypting secrets at rest is the\n// responsibility of the storage backend (e.g., encrypted etcd, disk encryption).\ntype MFASecret struct {\n\tAuthenticatorID string    `json:\"authenticatorID\"`\n\tType            string    `json:\"type\"`\n\tSecret          string    `json:\"secret\"`\n\tConfirmed       bool      `json:\"confirmed\"`\n\tCreatedAt       time.Time `json:\"createdAt\"`\n}\n\n// UserIdentity represents persistent per-user identity data.\ntype UserIdentity struct {\n\tUserID       string\n\tConnectorID  string\n\tClaims       Claims\n\tConsents     map[string][]string   // clientID -> approved scopes\n\tMFASecrets   map[string]*MFASecret // authenticatorID -> secret\n\tCreatedAt    time.Time\n\tLastLogin    time.Time\n\tBlockedUntil time.Time\n}\n\n// ClientAuthState represents authentication state for a specific client within an auth session.\ntype ClientAuthState struct {\n\tActive            bool\n\tExpiresAt         time.Time\n\tLastActivity      time.Time\n\tLastTokenIssuedAt time.Time\n}\n\n// AuthSession represents a user's authentication session from a specific connector.\n// Keyed by composite (UserID, ConnectorID), similar to OfflineSessions.\n// The Nonce field is a random value included in the session cookie to prevent forgery.\n//\n// TODO(nabokihms): support multiple sessions in one browser by storing multiple\n// session references in the cookie (e.g. \"ref1|ref2\") so that different users\n// can maintain independent sessions in the same browser.\ntype AuthSession struct {\n\tUserID       string\n\tConnectorID  string\n\tNonce        string                      // random, included in cookie for verification\n\tClientStates map[string]*ClientAuthState // clientID -> auth state\n\tCreatedAt    time.Time\n\tLastActivity time.Time\n\tIPAddress    string\n\tUserAgent    string\n\n\t// AbsoluteExpiry is CreatedAt + AbsoluteLifetime, set once at creation.\n\tAbsoluteExpiry time.Time\n\t// IdleExpiry is LastActivity + ValidIfNotUsedFor, updated on every activity.\n\tIdleExpiry time.Time\n}\n\n// OfflineSessions objects are sessions pertaining to users with refresh tokens.\ntype OfflineSessions struct {\n\t// UserID of an end user who has logged into the server.\n\tUserID string\n\n\t// The ID of the connector used to login the user.\n\tConnID string\n\n\t// Refresh is a hash table of refresh token reference objects\n\t// indexed by the ClientID of the refresh token.\n\tRefresh map[string]*RefreshTokenRef\n\n\t// Authentication data provided by an upstream source.\n\tConnectorData []byte\n}\n\n// Password is an email to password mapping managed by the storage.\ntype Password struct {\n\t// Email and identifying name of the password. Emails are assumed to be valid and\n\t// determining that an end-user controls the address is left to an outside application.\n\t//\n\t// Emails are case insensitive and should be standardized by the storage.\n\t//\n\t// Storages that don't support an extended character set for IDs, such as '.' and '@'\n\t// (cough cough, kubernetes), must map this value appropriately.\n\tEmail string `json:\"email\"`\n\n\t// Bcrypt encoded hash of the password. This package enforces a min cost value of 10\n\tHash []byte `json:\"hash\"`\n\n\t// Bcrypt encoded hash of the password set in environment variable of this name.\n\tHashFromEnv string `json:\"hashFromEnv\"`\n\n\t// Optional username to display. NOT used during login.\n\tUsername string `json:\"username\"`\n\n\t// Optional full name for OIDC \"name\" claim.\n\t// Defaults to Username when empty.\n\tName string `json:\"name\"`\n\n\t// Optional preferred username for OIDC \"preferred_username\" claim.\n\tPreferredUsername string `json:\"preferredUsername\"`\n\n\t// Optional value for OIDC \"email_verified\" claim.\n\t// Defaults to true for backwards compatibility when nil.\n\tEmailVerified *bool `json:\"emailVerified,omitempty\"`\n\n\t// Randomly generated user ID. This is NOT the primary ID of the Password object.\n\tUserID string `json:\"userID\"`\n\n\t// Groups assigned to the user\n\tGroups []string `json:\"groups\"`\n}\n\n// Connector is an object that contains the metadata about connectors used to login to Dex.\ntype Connector struct {\n\t// ID that will uniquely identify the connector object.\n\tID string `json:\"id\"`\n\t// The Type of the connector. E.g. 'oidc' or 'ldap'\n\tType string `json:\"type\"`\n\t// The Name of the connector that is used when displaying it to the end user.\n\tName string `json:\"name\"`\n\t// ResourceVersion is the static versioning used to keep track of dynamic configuration\n\t// changes to the connector object made by the API calls.\n\tResourceVersion string `json:\"resourceVersion\"`\n\t// Config holds all the configuration information specific to the connector type. Since there\n\t// no generic struct we can use for this purpose, it is stored as a byte stream.\n\t//\n\t// NOTE: This is a bug. The JSON tag should be `config`.\n\t// However, fixing this requires migrating Kubernetes objects for all previously created connectors,\n\t// or making Dex reading both tags and act accordingly.\n\tConfig []byte `json:\"email\"`\n\n\t// GrantTypes is a list of grant types that this connector is allowed to be used with.\n\t// If empty, all grant types are allowed.\n\tGrantTypes []string `json:\"grantTypes,omitempty\"`\n}\n\n// VerificationKey is a rotated signing key which can still be used to verify\n// signatures.\ntype VerificationKey struct {\n\tPublicKey *jose.JSONWebKey `json:\"publicKey\"`\n\tExpiry    time.Time        `json:\"expiry\"`\n}\n\n// Keys hold encryption and signing keys.\ntype Keys struct {\n\t// Key for creating and verifying signatures. These may be nil.\n\tSigningKey    *jose.JSONWebKey\n\tSigningKeyPub *jose.JSONWebKey\n\n\t// Old signing keys which have been rotated but can still be used to validate\n\t// existing signatures.\n\tVerificationKeys []VerificationKey\n\n\t// The next time the signing key will rotate.\n\t//\n\t// For caching purposes, implementations MUST NOT update keys before this time.\n\tNextRotation time.Time\n}\n\n// NewUserCode returns a randomized 8 character user code for the device flow.\n// No vowels are included to prevent accidental generation of words\nfunc NewUserCode() string {\n\tcode := randomString(8)\n\treturn code[:4] + \"-\" + code[4:]\n}\n\nfunc randomString(n int) string {\n\tv := big.NewInt(int64(len(validUserCharacters)))\n\tbytes := make([]byte, n)\n\tfor i := 0; i < n; i++ {\n\t\tc, _ := rand.Int(rand.Reader, v)\n\t\tbytes[i] = validUserCharacters[c.Int64()]\n\t}\n\treturn string(bytes)\n}\n\n// DeviceRequest represents an OIDC device authorization request. It holds the state of a device request until the user\n// authenticates using their user code or the expiry time passes.\ntype DeviceRequest struct {\n\t// The code the user will enter in a browser\n\tUserCode string\n\t// The unique device code for device authentication\n\tDeviceCode string\n\t// The client ID the code is for\n\tClientID string\n\t// The Client Secret\n\tClientSecret string\n\t// The scopes the device requests\n\tScopes []string\n\t// The expire time\n\tExpiry time.Time\n}\n\n// DeviceToken is a structure which represents the actual token of an authorized device and its rotation parameters\ntype DeviceToken struct {\n\tDeviceCode          string\n\tStatus              string\n\tToken               string\n\tExpiry              time.Time\n\tLastRequestTime     time.Time\n\tPollIntervalSeconds int\n\tPKCE                PKCE\n}\n"
  },
  {
    "path": "web/robots.txt",
    "content": "User-agent: *\nDisallow: /\n"
  },
  {
    "path": "web/static/main.css",
    "content": "* {\n  box-sizing: border-box;\n}\n\nbody {\n  margin: 0;\n}\n\n.dex-container {\n  color: #333;\n  margin: 60px auto;\n  max-width: 480px;\n  min-width: 320px;\n  padding: 0 16px;\n  text-align: center;\n}\n\n.dex-btn {\n  border-radius: 8px;\n  border: 0;\n  cursor: pointer;\n  font-size: 15px;\n  padding: 0;\n  transition: background-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease;\n}\n\n.dex-btn:focus-visible {\n  outline: 2px solid #4A90D9;\n  outline-offset: 2px;\n}\n\n.dex-btn:active {\n  transform: scale(0.98);\n}\n\n.dex-btn:disabled {\n  cursor: not-allowed;\n  opacity: 0.5;\n}\n\n.dex-btn-icon {\n  background-position: center;\n  background-repeat: no-repeat;\n  background-size: 20px;\n  border-radius: 8px 0 0 8px;\n  float: left;\n  height: 40px;\n  margin-right: 4px;\n  width: 40px;\n}\n\n.dex-btn-icon--google {\n  background-color: #FFFFFF;\n  background-image: url(../static/img/google-icon.svg);\n}\n\n.dex-btn-icon--local {\n  background-color: #84B6EF;\n  background-image: url(../static/img/email-icon.svg);\n}\n\n.dex-btn-icon--gitea {\n  background-color: #F5F5F5;\n  background-image: url(../static/img/gitea-icon.svg);\n}\n\n.dex-btn-icon--github {\n  background-color: #F5F5F5;\n  background-image: url(../static/img/github-icon.svg);\n}\n\n.dex-btn-icon--gitlab {\n  background-color: #F5F5F5;\n  background-image: url(../static/img/gitlab-icon.svg);\n  background-size: contain;\n}\n\n.dex-btn-icon--keystone {\n  background-color: #F5F5F5;\n  background-image: url(../static/img/keystone-icon.svg);\n  background-size: contain;\n}\n\n.dex-btn-icon--oidc {\n  background-color: #EBEBEE;\n  background-image: url(../static/img/oidc-icon.svg);\n  background-size: contain;\n}\n\n.dex-btn-icon--bitbucket-cloud {\n  background-color: #205081;\n  background-image: url(../static/img/bitbucket-icon.svg);\n}\n\n.dex-btn-icon--atlassian-crowd {\n  background-color: #CFDCEA;\n  background-image: url(../static/img/atlassian-crowd-icon.svg);\n}\n\n.dex-btn-icon--ldap {\n  background-color: #84B6EF;\n  background-image: url(../static/img/ldap-icon.svg);\n}\n\n.dex-btn-icon--saml {\n  background-color: #84B6EF;\n  background-image: url(../static/img/saml-icon.svg);\n}\n\n.dex-btn-icon--linkedin {\n  background-image: url(../static/img/linkedin-icon.svg);\n  background-size: contain;\n}\n\n.dex-btn-icon--microsoft {\n  background-image: url(../static/img/microsoft-icon.svg);\n}\n\n.dex-btn-icon--mockCallback,\n.dex-btn-icon--mockPassword {\n  background-color: #6c5ce7;\n  background-image: url(../static/img/mock-icon.svg);\n}\n\n.dex-btn-text {\n  font-weight: 600;\n  line-height: 40px;\n  padding: 6px 14px;\n  text-align: center;\n}\n\n.dex-subtle-text {\n  color: #888;\n  font-size: 13px;\n}\n\n.dex-separator {\n  color: #aaa;\n}\n\n.dex-list {\n  color: #888;\n  display: inline-block;\n  font-size: 13px;\n  list-style: circle;\n  text-align: left;\n}\n\n.dex-error-box {\n  background-color: #e5383b;\n  border-radius: 6px;\n  color: #fff;\n  font-size: 14px;\n  font-weight: normal;\n  margin: 20px auto;\n  max-width: 320px;\n  padding: 8px 12px;\n}\n"
  },
  {
    "path": "web/templates/approval.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">Grant Access</h2>\n\n  <hr class=\"dex-separator\">\n  <div>\n    {{ if .Scopes }}\n    <div class=\"dex-subtle-text\">{{ .Client }} would like to:</div>\n    <ul class=\"dex-list\">\n      {{ range $scope := .Scopes }}\n      <li>{{ $scope }}</li>\n      {{ end }}\n    </ul>\n    {{ else }}\n    <div class=\"dex-subtle-text\">{{ .Client }} has not requested any personal information</div>\n    {{ end }}\n  </div>\n  <hr class=\"dex-separator\">\n\n  <div>\n    <div class=\"theme-form-row\">\n      <form method=\"post\">\n        <input type=\"hidden\" name=\"req\" value=\"{{ .AuthReqID }}\"/>\n        <input type=\"hidden\" name=\"approval\" value=\"approve\">\n        <button type=\"submit\" class=\"dex-btn theme-btn--success\">\n            <span class=\"dex-btn-text\">Grant Access</span>\n        </button>\n      </form>\n    </div>\n    <div class=\"theme-form-row\">\n      <form method=\"post\">\n        <input type=\"hidden\" name=\"req\" value=\"{{ .AuthReqID }}\"/>\n        <input type=\"hidden\" name=\"approval\" value=\"rejected\">\n        <button type=\"submit\" class=\"dex-btn theme-btn-provider\">\n            <span class=\"dex-btn-text\">Cancel</span>\n        </button>\n      </form>\n    </div>\n  </div>\n\n</div>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/templates/device.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">Enter User Code</h2>\n  <form method=\"post\" action=\"{{ .PostURL }}\">\n    <div class=\"theme-form-row\">\n      {{ if( .UserCode  )}}\n      <input tabindex=\"2\" required id=\"user_code\" name=\"user_code\" type=\"text\" class=\"theme-form-input\" autocomplete=\"off\" value=\"{{.UserCode}}\" {{ if .Invalid }} autofocus {{ end }}/>\n      {{ else }}\n      <input tabindex=\"2\" required id=\"user_code\" name=\"user_code\" type=\"text\" class=\"theme-form-input\" placeholder=\"XXXX-XXXX\" autocomplete=\"off\"  {{ if .Invalid }} autofocus {{ end }}/>\n      {{ end }}\n    </div>\n\n    {{ if .Invalid }}\n    <div id=\"login-error\" class=\"dex-error-box\">\n      Invalid or Expired User Code\n    </div>\n    {{ end }}\n    <button tabindex=\"3\" id=\"submit-login\" type=\"submit\" class=\"dex-btn theme-btn--primary\">Submit</button>\n  </form>\n</div>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/templates/device_success.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">Login Successful for {{ .ClientName }}</h2>\n  <p>Return to your device to continue</p>\n</div>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/templates/error.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">{{ .ErrType }}</h2>\n  <p>{{ .ErrMsg }}</p>\n</div>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/templates/footer.html",
    "content": "    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "web/templates/header.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n    <title>{{ issuer }}</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <link href=\"{{ url .ReqPath \"static/main.css\" }}\" rel=\"stylesheet\">\n    <link href=\"{{ url .ReqPath \"theme/styles.css\" }}\" rel=\"stylesheet\">\n    <link rel=\"icon\" href=\"{{ url .ReqPath \"theme/favicon.png\" }}\">\n  </head>\n\n  <body class=\"theme-body\">\n    <div class=\"theme-navbar\">\n      <div class=\"theme-navbar__logo-wrap\">\n        <img class=\"theme-navbar__logo\" src=\"{{ url .ReqPath logo }}\">\n      </div>\n    </div>\n\n    <div class=\"dex-container\">\n"
  },
  {
    "path": "web/templates/login.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">Log in to {{ issuer }} </h2>\n  <div>\n    {{ range $c := .Connectors }}\n      <div class=\"theme-form-row\">\n        <a href=\"{{ $c.URL }}\" target=\"_self\">\n          <button class=\"dex-btn theme-btn-provider\">\n            <span class=\"dex-btn-icon dex-btn-icon--{{ $c.Type }}\"></span>\n            <span class=\"dex-btn-text\">Log in with {{ $c.Name }}</span>\n          </button>\n        </a>\n      </div>\n    {{ end }}\n  </div>\n</div>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/templates/oob.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">Login Successful</h2>\n  <p>Please copy this code, switch to your application and paste it there:</p>\n  <input type=\"text\" class=\"theme-form-input\" value=\"{{ .Code }}\" />\n</div>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/templates/password.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">Log in to Your Account</h2>\n  <form method=\"post\" action=\"{{ .PostURL }}\">\n    <div class=\"theme-form-row\">\n      <div class=\"theme-form-label\">\n        <label for=\"login\">{{ .UsernamePrompt }}</label>\n      </div>\n\t  <input tabindex=\"1\" required id=\"login\" name=\"login\" type=\"text\" class=\"theme-form-input\" placeholder=\"{{ .UsernamePrompt | lower }}\" {{ if .Username }} value=\"{{ .Username }}\" {{ else }} autofocus {{ end }}/>\n    </div>\n    <div class=\"theme-form-row\">\n      <div class=\"theme-form-label\">\n        <label for=\"password\">Password</label>\n      </div>\n\t  <input tabindex=\"2\" required id=\"password\" name=\"password\" type=\"password\" class=\"theme-form-input\" placeholder=\"password\" {{ if .Invalid }} autofocus {{ end }}/>\n    </div>\n\n    {{ if .Invalid }}\n      <div id=\"login-error\" class=\"dex-error-box\">\n        Invalid {{ .UsernamePrompt }} and password.\n      </div>\n    {{ end }}\n\n    {{ if .ShowRememberMe }}\n    <div class=\"theme-form-row\">\n      <label class=\"theme-remember-me\">\n        <input tabindex=\"3\" type=\"checkbox\" name=\"remember_me\" {{ if .RememberMeChecked }}checked{{ end }}/>\n        Remember me\n      </label>\n    </div>\n    {{ end }}\n\n    <button tabindex=\"4\" id=\"submit-login\" type=\"submit\" class=\"dex-btn theme-btn--primary\">Login</button>\n\n  </form>\n  {{ if .BackLink }}\n  <div class=\"theme-link-back\">\n    <a class=\"dex-subtle-text\" href=\"{{ .BackLink }}\">Select another login method.</a>\n  </div>\n  {{ end }}\n</div>\n\n\n<script type=\"text/javascript\">\n  document.querySelector('form').onsubmit = function(e) {\n    var el = document.querySelector('#submit-login');\n    el.setAttribute('disabled', 'disabled');\n  };\n</script>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/templates/totp_verify.html",
    "content": "{{ template \"header.html\" . }}\n\n<div class=\"theme-panel\">\n  <h2 class=\"theme-heading\">Two-factor authentication</h2>\n  {{ if not (eq .QRCode \"\") }}\n  <p>Scan the QR code below using your authenticator app, then enter the code.</p>\n  <div style=\"text-align: center; margin: 1em 0;\">\n    <img src=\"data:image/png;base64,{{ .QRCode }}\" alt=\"QR code\" width=\"200\" height=\"200\"/>\n  </div>\n  {{ else }}\n  <p>Enter the code from your authenticator app.</p>\n  <p class=\"theme-heading\">{{ .Issuer }}</p>\n  {{ end }}\n  <form method=\"post\" action=\"{{ .PostURL }}\">\n    <div class=\"theme-form-row\">\n      <div class=\"theme-form-label\">\n        <label for=\"totp\">One-time code</label>\n      </div>\n      <input tabindex=\"1\" required id=\"totp\" name=\"totp\" type=\"text\"\n             inputmode=\"numeric\" pattern=\"[0-9]*\" maxlength=\"6\"\n             autocomplete=\"one-time-code\"\n             class=\"theme-form-input\" placeholder=\"000000\"\n             autofocus/>\n    </div>\n\n    {{ if .Invalid }}\n    <div id=\"login-error\" class=\"dex-error-box\">\n      Invalid code. Please try again.\n    </div>\n    {{ end }}\n\n    <button tabindex=\"2\" id=\"submit-login\" type=\"submit\" class=\"dex-btn theme-btn--primary\">Verify</button>\n  </form>\n</div>\n\n{{ template \"footer.html\" . }}\n"
  },
  {
    "path": "web/themes/dark/styles.css",
    "content": ".theme-body {\n  background-color: #131519;\n  color: #b8bcc4;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n}\n\n.theme-navbar {\n  background-color: #1a1d23;\n  border-bottom: 1px solid #2a2d35;\n  color: #b8bcc4;\n  font-size: 13px;\n  font-weight: 400;\n  height: 52px;\n  overflow: hidden;\n  padding: 0 16px;\n}\n\n.theme-navbar__logo-wrap {\n  display: inline-block;\n  height: 100%;\n  overflow: hidden;\n  padding: 12px 15px;\n  width: 300px;\n}\n\n.theme-navbar__logo {\n  height: 100%;\n  max-height: 26px;\n}\n\n.theme-heading {\n  color: #dcdfe5;\n  font-size: 20px;\n  font-weight: 600;\n  margin-bottom: 16px;\n  margin-top: 0;\n}\n\n.theme-panel {\n  background-color: #1a1d23;\n  border: 1px solid #2a2d35;\n  border-radius: 12px;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);\n  padding: 32px;\n}\n\n.theme-btn-provider {\n  background-color: #22252c;\n  border: 1px solid #33363e;\n  color: #b8bcc4;\n  min-width: 260px;\n}\n\n.theme-btn-provider:hover {\n  background-color: #2a2d35;\n  border-color: #3e414a;\n  color: #dcdfe5;\n}\n\n.theme-btn--primary {\n  background-color: #3d3f47;\n  border: none;\n  color: #e8eaed;\n  min-width: 200px;\n  padding: 8px 16px;\n}\n\n.theme-btn--primary:hover {\n  background-color: #4a4c55;\n  color: #fff;\n}\n\n.theme-btn--success {\n  background-color: #2d7d9a;\n  color: #e8eaed;\n  width: 260px;\n}\n\n.theme-btn--success:hover {\n  background-color: #358fae;\n}\n\n.theme-form-row {\n  display: block;\n  margin: 16px auto;\n}\n\n.theme-form-input {\n  background-color: #131519;\n  border: 1px solid #33363e;\n  border-radius: 8px;\n  color: #b8bcc4;\n  display: block;\n  font-size: 14px;\n  height: 40px;\n  line-height: 1.5;\n  margin: auto;\n  padding: 8px 12px;\n  transition: border-color 0.15s ease, box-shadow 0.15s ease;\n  width: 260px;\n}\n\n.theme-form-input:focus,\n.theme-form-input:active {\n  border-color: #5a9bb5;\n  box-shadow: 0 0 0 3px rgba(90, 155, 181, 0.15);\n  color: #dcdfe5;\n  outline: none;\n}\n\n.theme-form-label {\n  color: #b8bcc4;\n  font-size: 14px;\n  font-weight: 500;\n  margin: 4px auto;\n  position: relative;\n  text-align: left;\n  width: 260px;\n}\n\n.theme-link-back {\n  margin-top: 8px;\n}\n\n.theme-remember-me {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    cursor: pointer;\n    font-size: 14px;\n    width: 260px;\n    margin: 4px auto;\n}\n\n.dex-container {\n  color: #b8bcc4;\n}\n"
  },
  {
    "path": "web/themes/light/styles.css",
    "content": ".theme-body {\n  background-color: #f4f5f7;\n  color: #1a1a1a;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;\n}\n\n.theme-navbar {\n  background-color: #fff;\n  border-bottom: 1px solid #e1e4e8;\n  color: #1a1a1a;\n  font-size: 13px;\n  font-weight: 400;\n  height: 52px;\n  overflow: hidden;\n  padding: 0 16px;\n}\n\n.theme-navbar__logo-wrap {\n  display: inline-block;\n  height: 100%;\n  overflow: hidden;\n  padding: 12px 15px;\n  width: 300px;\n}\n\n.theme-navbar__logo {\n  height: 100%;\n  max-height: 26px;\n}\n\n.theme-heading {\n  font-size: 20px;\n  font-weight: 600;\n  margin-bottom: 16px;\n  margin-top: 0;\n}\n\n.theme-panel {\n  background-color: #fff;\n  border: 1px solid #e1e4e8;\n  border-radius: 12px;\n  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.06);\n  padding: 32px;\n}\n\n.theme-btn-provider {\n  background-color: #fff;\n  border: 1px solid #d0d5dd;\n  color: #1a1a1a;\n  min-width: 260px;\n}\n\n.theme-btn-provider:hover {\n  background-color: #f9fafb;\n  border-color: #b0b5bd;\n  color: #1a1a1a;\n}\n\n.theme-btn--primary {\n  background-color: #1a1a1a;\n  border: none;\n  color: #fff;\n  min-width: 200px;\n  padding: 8px 16px;\n}\n\n.theme-btn--primary:hover {\n  background-color: #333;\n  color: #fff;\n}\n\n.theme-btn--success {\n  background-color: #16a34a;\n  color: #fff;\n  width: 260px;\n}\n\n.theme-btn--success:hover {\n  background-color: #15803d;\n}\n\n.theme-form-row {\n  display: block;\n  margin: 16px auto;\n}\n\n.theme-form-input {\n  border-radius: 8px;\n  border: 1px solid #d0d5dd;\n  color: #1a1a1a;\n  display: block;\n  font-size: 14px;\n  height: 40px;\n  line-height: 1.5;\n  margin: auto;\n  padding: 8px 12px;\n  transition: border-color 0.15s ease, box-shadow 0.15s ease;\n  width: 260px;\n}\n\n.theme-form-input:focus,\n.theme-form-input:active {\n  border-color: #4A90D9;\n  box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.15);\n  outline: none;\n}\n\n.theme-form-label {\n  font-size: 14px;\n  font-weight: 500;\n  margin: 4px auto;\n  position: relative;\n  text-align: left;\n  width: 260px;\n}\n\n.theme-link-back {\n  margin-top: 8px;\n}\n\n.theme-remember-me {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n  cursor: pointer;\n  font-size: 14px;\n  width: 260px;\n  margin: 4px auto;\n}\n"
  },
  {
    "path": "web/web.go",
    "content": "package web\n\nimport (\n\t\"embed\"\n\t\"io/fs\"\n)\n\n//go:embed static/* templates/* themes/* robots.txt\nvar files embed.FS\n\n// FS returns a filesystem with the default web assets.\nfunc FS() fs.FS {\n\treturn files\n}\n"
  }
]