[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    reviewers:\n      - \"mattermost/cloud-sre\"\n    open-pull-requests-limit: 5\n    groups:\n      Github Actions updates:\n        applies-to: version-updates\n        dependency-type: production\n    schedule:\n      # Check for updates to GitHub Actions every week\n      day: \"monday\"\n      time: \"10:00\"\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Build and Test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\npermissions:\n  contents: read\n  id-token: write  # Required for OIDC authentication with Chainguard identity\n\nenv:\n  GO_VERSION: 1.24.6\n  FIPS_ENABLED: true\n\njobs:\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n      - name: Lint\n        run: make lint\n\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n      - name: Test\n        run: make test\n\n  package:\n    name: Package\n    runs-on: ubuntu-latest\n    needs: [lint, test]\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n      - name: build-package/package\n        run: make package\n\n  build-amd64:\n    name: Build AMD64\n    runs-on: ubuntu-latest\n    needs: [lint, test]\n    if: github.actor != 'dependabot[bot]'\n    timeout-minutes: 30\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n      - name: Build Docker Image for AMD64\n        run: make build-image-amd64-with-tags\n\n  build-arm64:\n    name: Build ARM64\n    runs-on: ubuntu-24.04-arm\n    needs: [lint, test]\n    if: github.actor != 'dependabot[bot]'\n    timeout-minutes: 30\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n      - name: Build Docker Image for ARM64\n        run: make build-image-arm64-with-tags\n\n  fips-build-amd64:\n    name: FIPS Build AMD64\n    runs-on: ubuntu-latest\n    needs: [lint, test]\n    if: github.actor != 'dependabot[bot]'\n    timeout-minutes: 30\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Chainguard\n        uses: chainguard-dev/setup-chainctl@c125f765e82b09a42af3185f3214465314d75c5d # v0.5.0\n        with:\n          identity: ${{ secrets.CHAINGUARD_IDENTITY }}\n\n      - name: Build FIPS Docker Image for AMD64\n        run: make build-image-fips-amd64-with-tags\n\n  fips-build-arm64:\n    name: FIPS Build ARM64\n    runs-on: ubuntu-24.04-arm\n    needs: [lint, test]\n    if: github.actor != 'dependabot[bot]'\n    timeout-minutes: 30\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Chainguard\n        uses: chainguard-dev/setup-chainctl@c125f765e82b09a42af3185f3214465314d75c5d # v0.5.0\n        with:\n          identity: ${{ secrets.CHAINGUARD_IDENTITY }}\n\n      - name: Build FIPS Docker Image for ARM64\n        run: make build-image-fips-arm64-with-tags\n\n  fips-security-scan:\n    name: FIPS Security Scan\n    runs-on: ubuntu-latest\n    needs: [lint, test]\n    if: github.actor != 'dependabot[bot]'\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Chainguard\n        uses: chainguard-dev/setup-chainctl@c125f765e82b09a42af3185f3214465314d75c5d # v0.5.0\n        with:\n          identity: ${{ secrets.CHAINGUARD_IDENTITY }}\n\n      - name: Build FIPS Docker image for scanning\n        run: make build-image-fips-amd64-with-tags\n\n      - name: Run Grype vulnerability scanner\n        uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2\n        with:\n          image: \"mattermost/mattermost-push-proxy-fips:${{ github.ref == 'refs/heads/master' && 'master' || format('dev-{0}', github.sha) }}\"\n          output-format: table\n          fail-build: false\n\n  security-scan:\n    name: Security Scan\n    runs-on: ubuntu-latest\n    needs: [lint, test]\n    if: github.actor != 'dependabot[bot]'\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Build Docker image for scanning\n        run: make build-image-amd64-with-tags\n\n      - name: Run Grype vulnerability scanner\n        uses: anchore/scan-action@7037fa011853d5a11690026fb85feee79f4c946c # v7.3.2\n        with:\n          image: \"mattermost-push-proxy:${{ github.ref == 'refs/heads/master' && 'master' || format('dev-{0}', github.sha) }}\"\n          output-format: table\n          fail-build: false\n\n  pr-deploy-amd64:\n    name: PR Deploy AMD64\n    runs-on: ubuntu-latest\n    needs: [security-scan, build-amd64]\n    if: github.event_name == 'pull_request' && github.repository_owner == 'mattermost' && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push AMD64 PR images\n        run: |\n          # Build with default APP_NAME (avoids filesystem issues)\n          make build-image-amd64-with-tags\n\n          # Retag with correct namespace for pushing\n          docker tag mattermost-push-proxy:dev-${{ github.sha }}-amd64 mattermost/mattermost-push-proxy:dev-${{ github.sha }}-amd64\n          docker tag mattermost-push-proxy:dev-${{ github.sha }} mattermost/mattermost-push-proxy:dev-${{ github.sha }}\n\n          # Push to correct namespace\n          docker push mattermost/mattermost-push-proxy:dev-${{ github.sha }}-amd64\n          echo \"✅ AMD64 image pushed: mattermost/mattermost-push-proxy:dev-${{ github.sha }}-amd64\"\n\n  pr-deploy-arm64:\n    name: PR Deploy ARM64\n    runs-on: ubuntu-24.04-arm\n    needs: [security-scan, build-arm64]\n    if: github.event_name == 'pull_request' && github.repository_owner == 'mattermost' && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push ARM64 PR images\n        run: |\n          # Build with default APP_NAME (avoids filesystem issues)\n          make build-image-arm64-with-tags\n\n          # Retag with correct namespace for pushing\n          docker tag mattermost-push-proxy:dev-${{ github.sha }}-arm64 mattermost/mattermost-push-proxy:dev-${{ github.sha }}-arm64\n          docker tag mattermost-push-proxy:dev-${{ github.sha }} mattermost/mattermost-push-proxy:dev-${{ github.sha }}\n\n          # Push to correct namespace\n          docker push mattermost/mattermost-push-proxy:dev-${{ github.sha }}-arm64\n          echo \"✅ ARM64 image pushed: mattermost/mattermost-push-proxy:dev-${{ github.sha }}-arm64\"\n\n  pr-deploy-manifest:\n    name: PR Deploy Manifest\n    runs-on: ubuntu-latest\n    needs: [pr-deploy-amd64, pr-deploy-arm64]\n    if: github.event_name == 'pull_request' && github.repository_owner == 'mattermost' && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Create and push multi-arch manifest\n        run: |\n          # Create multi-platform manifest for PR testing\n          docker manifest create mattermost/mattermost-push-proxy:dev-${{ github.sha }} \\\n            --amend mattermost/mattermost-push-proxy:dev-${{ github.sha }}-amd64 \\\n            --amend mattermost/mattermost-push-proxy:dev-${{ github.sha }}-arm64\n          docker manifest push mattermost/mattermost-push-proxy:dev-${{ github.sha }}\n\n          # Clean up intermediate architecture-specific tags (like production)\n          echo \"Cleaning up intermediate architecture-specific tags...\"\n          docker rmi mattermost/mattermost-push-proxy:dev-${{ github.sha }}-amd64 2>/dev/null || true\n          docker rmi mattermost/mattermost-push-proxy:dev-${{ github.sha }}-arm64 2>/dev/null || true\n          echo \"✅ Multi-arch PR image available (arch-specific tags removed):\"\n          echo \"  docker pull mattermost/mattermost-push-proxy:dev-${{ github.sha }}\"\n\n  pr-deploy-fips-amd64:\n    name: PR Deploy FIPS AMD64\n    runs-on: ubuntu-latest\n    needs: [fips-security-scan, fips-build-amd64]\n    if: github.event_name == 'pull_request' && github.repository_owner == 'mattermost' && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Chainguard Identity\n        uses: chainguard-dev/setup-chainctl@c125f765e82b09a42af3185f3214465314d75c5d # v0.5.0\n        with:\n          identity: ${{ secrets.CHAINGUARD_IDENTITY }}\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push FIPS AMD64 PR images\n        run: |\n          # Build with default APP_NAME (avoids filesystem issues)\n          make build-image-fips-amd64-with-tags\n\n          # Retag with correct namespace for pushing\n          docker tag mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-amd64 mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-amd64\n          docker tag mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }} mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}\n\n          # Push to correct namespace\n          docker push mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-amd64\n          echo \"✅ FIPS AMD64 image pushed: mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-amd64\"\n\n  pr-deploy-fips-arm64:\n    name: PR Deploy FIPS ARM64\n    runs-on: ubuntu-24.04-arm\n    needs: [fips-security-scan, fips-build-arm64]\n    if: github.event_name == 'pull_request' && github.repository_owner == 'mattermost' && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Chainguard Identity\n        uses: chainguard-dev/setup-chainctl@c125f765e82b09a42af3185f3214465314d75c5d # v0.5.0\n        with:\n          identity: ${{ secrets.CHAINGUARD_IDENTITY }}\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push FIPS ARM64 PR images\n        run: |\n          # Build with default APP_NAME (avoids filesystem issues)\n          make build-image-fips-arm64-with-tags\n\n          # Retag with correct namespace for pushing\n          docker tag mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-arm64 mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-arm64\n          docker tag mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }} mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}\n\n          # Push to correct namespace\n          docker push mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-arm64\n          echo \"✅ FIPS ARM64 image pushed: mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-arm64\"\n\n  pr-deploy-fips-manifest:\n    name: PR Deploy FIPS Manifest\n    runs-on: ubuntu-latest\n    needs: [pr-deploy-fips-amd64, pr-deploy-fips-arm64]\n    if: github.event_name == 'pull_request' && github.repository_owner == 'mattermost' && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: read\n      id-token: write\n    steps:\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Create and push FIPS multi-arch manifest\n        run: |\n          # Create multi-platform FIPS manifest for PR testing\n          docker manifest create mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }} \\\n            --amend mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-amd64 \\\n            --amend mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-arm64\n          docker manifest push mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}\n\n          # Clean up intermediate FIPS architecture-specific tags (like production)\n          echo \"Cleaning up intermediate FIPS architecture-specific tags...\"\n          docker rmi mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-amd64 2>/dev/null || true\n          docker rmi mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}-arm64 2>/dev/null || true\n          echo \"✅ Multi-arch FIPS PR image available (arch-specific tags removed):\"\n          echo \"  docker pull mattermost/mattermost-push-proxy-fips:dev-${{ github.sha }}\"\n\n\n\n  deploy-amd64:\n    name: Deploy AMD64\n    runs-on: ubuntu-latest\n    needs: [security-scan, build-amd64]\n    if: github.repository_owner == 'mattermost' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push AMD64 image\n        run: |\n          # Build AMD64 image with temp tag for cleanup\n          SHORT_SHA=${GITHUB_SHA:0:7}\n          make build-image-amd64-with-tags\n          # Retag with temp namespace for later cleanup\n          docker tag mattermost-push-proxy:master-amd64 mattermost/mattermost-push-proxy:temp-${GITHUB_SHA}-amd64\n          # Push temp AMD64 image\n          docker push mattermost/mattermost-push-proxy:temp-${GITHUB_SHA}-amd64\n\n  deploy-arm64:\n    name: Deploy ARM64\n    runs-on: ubuntu-24.04-arm\n    needs: [security-scan, build-arm64]\n    if: github.repository_owner == 'mattermost' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push ARM64 image\n        run: |\n          # Build ARM64 image with temp tag for cleanup\n          SHORT_SHA=${GITHUB_SHA:0:7}\n          make build-image-arm64-with-tags\n          # Retag with temp namespace for later cleanup\n          docker tag mattermost-push-proxy:master-arm64 mattermost/mattermost-push-proxy:temp-${GITHUB_SHA}-arm64\n          # Push temp ARM64 image\n          docker push mattermost/mattermost-push-proxy:temp-${GITHUB_SHA}-arm64\n\n  deploy:\n    name: Deploy\n    runs-on: ubuntu-latest\n    needs: [deploy-amd64, deploy-arm64]\n    if: github.repository_owner == 'mattermost' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Create and push multi-arch manifest\n        run: |\n          # Create multi-platform manifest using commit SHA\n          SHORT_SHA=${GITHUB_SHA:0:7}\n          docker manifest create mattermost/mattermost-push-proxy:${SHORT_SHA} \\\n            --amend mattermost/mattermost-push-proxy:temp-${GITHUB_SHA}-amd64 \\\n            --amend mattermost/mattermost-push-proxy:temp-${GITHUB_SHA}-arm64\n          docker manifest push mattermost/mattermost-push-proxy:${SHORT_SHA}\n\n          echo \"✅ Clean unified multi-arch tag: mattermost/mattermost-push-proxy:${SHORT_SHA}\"\n\n          # Cleanup temp tags using Docker Hub API with org-level cleanup token\n          echo \"🗑️  Cleaning up temp tags from Docker Hub...\"\n\n          # Delete temp tags using Docker Hub API\n          TEMP_AMD64_TAG=\"temp-${GITHUB_SHA}-amd64\"\n          TEMP_ARM64_TAG=\"temp-${GITHUB_SHA}-arm64\"\n\n          # Get Docker Hub API token using org-level cleanup token\n          DOCKER_HUB_TOKEN=$(curl -s -X POST \\\n            -H \"Content-Type: application/json\" \\\n            -d '{\"username\": \"matterbuild\", \"password\": \"${{ secrets.DOCKERHUB_CLEANUP_TOKEN }}\"}' \\\n            https://hub.docker.com/v2/users/login/ | jq -r .token)\n\n          # Delete AMD64 temp tag\n          curl -X DELETE \\\n            -H \"Authorization: JWT ${DOCKER_HUB_TOKEN}\" \\\n            \"https://hub.docker.com/v2/repositories/mattermost/mattermost-push-proxy/tags/${TEMP_AMD64_TAG}/\" \\\n            && echo \"✅ Deleted AMD64 temp tag\" || echo \"⚠️  AMD64 temp tag not found or already deleted\"\n\n          # Delete ARM64 temp tag\n          curl -X DELETE \\\n            -H \"Authorization: JWT ${DOCKER_HUB_TOKEN}\" \\\n            \"https://hub.docker.com/v2/repositories/mattermost/mattermost-push-proxy/tags/${TEMP_ARM64_TAG}/\" \\\n            && echo \"✅ Deleted ARM64 temp tag\" || echo \"⚠️  ARM64 temp tag not found or already deleted\"\n\n          echo \"✅ Temp tags cleaned up from Docker Hub\"\n\n      - name: Create release\n        if: startsWith(github.ref, 'refs/tags/v')\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: make github-release\n\n  fips-deploy-amd64:\n    name: FIPS Deploy AMD64\n    runs-on: ubuntu-latest\n    needs: [fips-security-scan, fips-build-amd64]\n    if: github.repository_owner == 'mattermost' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Chainguard Identity\n        uses: chainguard-dev/setup-chainctl@c125f765e82b09a42af3185f3214465314d75c5d # v0.5.0\n        with:\n          identity: ${{ secrets.CHAINGUARD_IDENTITY }}\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push FIPS AMD64 image\n        run: |\n          # Build FIPS AMD64 image with temp tag for cleanup\n          SHORT_SHA=${GITHUB_SHA:0:7}\n          make build-image-fips-amd64-with-tags\n          # Retag with temp namespace for later cleanup\n          docker tag mattermost/mattermost-push-proxy-fips:master-amd64 mattermost/mattermost-push-proxy-fips:temp-${GITHUB_SHA}-amd64\n          # Push temp FIPS AMD64 image\n          docker push mattermost/mattermost-push-proxy-fips:temp-${GITHUB_SHA}-amd64\n\n  fips-deploy-arm64:\n    name: FIPS Deploy ARM64\n    runs-on: ubuntu-24.04-arm\n    needs: [fips-security-scan, fips-build-arm64]\n    if: github.repository_owner == 'mattermost' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Chainguard Identity\n        uses: chainguard-dev/setup-chainctl@c125f765e82b09a42af3185f3214465314d75c5d # v0.5.0\n        with:\n          identity: ${{ secrets.CHAINGUARD_IDENTITY }}\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push FIPS ARM64 image\n        run: |\n          # Build FIPS ARM64 image with temp tag for cleanup\n          SHORT_SHA=${GITHUB_SHA:0:7}\n          make build-image-fips-arm64-with-tags\n          # Retag with temp namespace for later cleanup\n          docker tag mattermost/mattermost-push-proxy-fips:master-arm64 mattermost/mattermost-push-proxy-fips:temp-${GITHUB_SHA}-arm64\n          # Push temp FIPS ARM64 image\n          docker push mattermost/mattermost-push-proxy-fips:temp-${GITHUB_SHA}-arm64\n\n  fips-deploy:\n    name: FIPS Deploy\n    runs-on: ubuntu-latest\n    needs: [fips-deploy-amd64, fips-deploy-arm64]\n    if: github.repository_owner == 'mattermost' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) && github.actor != 'dependabot[bot]'\n    permissions:\n      contents: write\n      id-token: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0\n        with:\n          username: matterbuild\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Create and push FIPS multi-arch manifest\n        run: |\n          # Create multi-platform manifest for FIPS using commit SHA\n          SHORT_SHA=${GITHUB_SHA:0:7}\n          docker manifest create mattermost/mattermost-push-proxy-fips:${SHORT_SHA} \\\n            --amend mattermost/mattermost-push-proxy-fips:temp-${GITHUB_SHA}-amd64 \\\n            --amend mattermost/mattermost-push-proxy-fips:temp-${GITHUB_SHA}-arm64\n          docker manifest push mattermost/mattermost-push-proxy-fips:${SHORT_SHA}\n\n          echo \"✅ Clean unified FIPS multi-arch tag: mattermost/mattermost-push-proxy-fips:${SHORT_SHA}\"\n\n          # Cleanup temp FIPS tags using Docker Hub API\n          echo \"🗑️  Cleaning up temp FIPS tags from Docker Hub...\"\n\n          # Delete temp FIPS tags using Docker Hub API\n          TEMP_AMD64_TAG=\"temp-${GITHUB_SHA}-amd64\"\n          TEMP_ARM64_TAG=\"temp-${GITHUB_SHA}-arm64\"\n\n          # Get Docker Hub API token using org-level cleanup token\n          DOCKER_HUB_TOKEN=$(curl -s -X POST \\\n            -H \"Content-Type: application/json\" \\\n            -d '{\"username\": \"matterbuild\", \"password\": \"${{ secrets.DOCKERHUB_CLEANUP_TOKEN }}\"}' \\\n            https://hub.docker.com/v2/users/login/ | jq -r .token)\n\n          # Delete FIPS AMD64 temp tag\n          curl -X DELETE \\\n            -H \"Authorization: JWT ${DOCKER_HUB_TOKEN}\" \\\n            \"https://hub.docker.com/v2/repositories/mattermost/mattermost-push-proxy-fips/tags/${TEMP_AMD64_TAG}/\" \\\n            && echo \"✅ Deleted FIPS AMD64 temp tag\" || echo \"⚠️  FIPS AMD64 temp tag not found or already deleted\"\n\n          # Delete FIPS ARM64 temp tag\n          curl -X DELETE \\\n            -H \"Authorization: JWT ${DOCKER_HUB_TOKEN}\" \\\n            \"https://hub.docker.com/v2/repositories/mattermost/mattermost-push-proxy-fips/tags/${TEMP_ARM64_TAG}/\" \\\n            && echo \"✅ Deleted FIPS ARM64 temp tag\" || echo \"⚠️  FIPS ARM64 temp tag not found or already deleted\"\n\n          echo \"✅ Temp FIPS tags cleaned up from Docker Hub\"\n\n      - name: Create FIPS release\n        if: startsWith(github.ref, 'refs/tags/v')\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: make github-release-fips\n\n      - name: Cleanup\n        run: make clean\n"
  },
  {
    "path": ".gitignore",
    "content": "\nlogs\n.DS_Store\ndist\n\n# Build Targets\n.prebuild\n.prebuild-go\n.prebuild-jsx\n\n# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n.idea\n\n# Architecture specific extensions/prefixes\n[568vq].out\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n\n# Log files\n*mattermost.log\n\n# Vim temporary files\n*.swp\n\n# Default local file storage\ndata/*\napi/data/*\n\n.agignore\n.ctags\ntags\n\n# Certificates\n*.pem\n*.cer\n*.p8\n*.p12\n*.key\ncerts\ncerts.bak\n*.env\n\n# Ignore binary\n/mattermost-push-proxy\n\nnode_modules\n# Swagger output file\nindex.html\n\nconfig/mattermost-push-proxy.json\n# Ignore config.json\ncmd/renew_apple_cert/convert_cert/config/**\ncmd/renew_apple_cert/convert_cert/config.json\ncmd/renew_apple_cert/create_csr/config/**\ncmd/renew_apple_cert/create_csr/config.json\n"
  },
  {
    "path": ".golangci.yml",
    "content": "# options for analysis running\nrun:\n  # timeout for analysis, e.g. 30s, 5m, default is 1m\n  timeout: 5m\n\nlinters-settings:\n  gofmt:\n    simplify: true\n  govet:\n    enable-all: true\n    disable:\n      - fieldalignment\n\nlinters:\n  disable-all: true\n  enable:\n    - gofmt\n    - gosimple\n    - govet\n    - ineffassign\n    - copyloopvar\n    - staticcheck\n    - unconvert\n    - unused\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Mattermost Push Proxy Changelog\n\n\n## 5.8.1 Release\n- Release Date: March 28, 2019\n- Compatible with all versions of Mattermost server and mobile\n\n### Compatibility\n - As of April 10, 2018, Google has deprecated the Google Cloud Messaging (GCM) service. The GCM server and client APIs are deprecated and will be removed as of April 11, 2019. You must [migrate GCM apps to Firebase Cloud Messaging (FCM)](https://developers.mattermost.com/contribute/mobile/push-notifications/migrate-gcm-fcm/), which inherits the reliable and scalable GCM infrastructure, plus many new features.\n \n### Bug Fixes\n - FCM messages are sent as high priority to wake Android devices in doze mode\n\n________________\n\n## 5.8 Release\n- Release Date: March 20, 2019\n- Compatible with all versions of Mattermost server and mobile\n\n### Combatibility\n - As of April 10, 2018, Google has deprecated the Google Cloud Messaging (GCM) service. The GCM server and client APIs are deprecated and will be removed as of April 11, 2019. You must [migrate GCM apps to Firebase Cloud Messaging (FCM)](https://developers.mattermost.com/contribute/mobile/push-notifications/migrate-gcm-fcm/), which inherits the reliable and scalable GCM infrastructure, plus many new features.\n \n### Highlights\n - Adds support for Firebase Cloud Messaging\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Please see: https://github.com/mattermost/mattermost-server/blob/master/CONTRIBUTING.md\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2015 Mattermost, Inc. All rights reserved.\n\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": "Makefile",
    "content": "# ====================================================================================\n# Variables\n\n## General Variables\n\n# Branch Variables\nPROTECTED_BRANCH := master\nCURRENT_BRANCH   := $(shell git rev-parse --abbrev-ref HEAD)\n# Use repository name as application name\nAPP_NAME    := $(shell basename -s .git `git config --get remote.origin.url`)\nAPP_COMMIT  := $(shell git rev-parse HEAD)\n# Check if we are in protected branch, if yes use `protected_branch_name-sha` as app version.\n# Else check if we are in a release tag, if yes use the tag as app version, else use `dev-sha` as app version.\nAPP_VERSION ?= $(shell if [ $(PROTECTED_BRANCH) = $(CURRENT_BRANCH) ]; then echo $(PROTECTED_BRANCH); else (git describe --abbrev=0 --exact-match --tags 2>/dev/null || echo dev-$(APP_COMMIT)) ; fi)\nAPP_VERSION_NO_V := $(patsubst v%,%,$(APP_VERSION))\nGIT_VERSION ?= $(shell git describe --tags --always --dirty)\nGIT_TREESTATE = clean\nDIFF = $(shell git diff --quiet >/dev/null 2>&1; if [ $$? -eq 1 ]; then echo \"1\"; fi)\nifeq ($(DIFF), 1)\n    GIT_TREESTATE = dirty\nendif\n\nGO_INSTALL = ./scripts/go_install.sh\nTOOLS_BIN_DIR := $(abspath bin)\n\n# Get current date and format like: 2022-04-27 11:32\nBUILD_DATE  := $(shell date +%Y-%m-%d\\ %H:%M)\n\n# Get version information for plugins that depend on a semver version\nBUILD_HASH = $(shell git rev-parse --short HEAD)\nBUILD_TAG_LATEST = $(shell git describe --tags --match 'v*' --abbrev=0)\nBUILD_TAG_CURRENT = $(shell git tag --points-at HEAD)\n\n## General Configuration Variables\n# We don't need make's built-in rules.\nMAKEFLAGS     += --no-builtin-rules\n# Be pedantic about undefined variables.\nMAKEFLAGS     += --warn-undefined-variables\n# Set help as default target\n.DEFAULT_GOAL := help\n\n# App Code location\nCONFIG_APP_CODE         += ./\n\n## Docker Variables\n# Docker executable\nDOCKER                  := $(shell which docker)\n# Dockerfile's location\nDOCKER_FILE             ?= ./docker/Dockerfile\n# Docker options to inherit for all docker run commands\nDOCKER_OPTS             += --rm --platform \"linux/amd64\"\n# Registry to upload images\nDOCKER_REGISTRY         ?= docker.io\nDOCKER_REGISTRY_REPO    ?= mattermost/${APP_NAME}-daily\n# Registry credentials\nDOCKER_USER             ?= user\nDOCKER_PASSWORD         ?= password\n## Latest Docker tags\n# if we are on a latest semver APP_VERSION tag, also push latest\nifneq ($(shell echo $(APP_VERSION) | egrep '^v([0-9]+\\.){0,2}(\\*|[0-9]+)'),)\n  ifeq ($(shell git tag -l --sort=v:refname | tail -n1),$(APP_VERSION))\n\t\tLATEST_DOCKER_TAG = -t $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:latest\n  endif\nendif\n\n## Docker Images\nDOCKER_IMAGE_GO         ?= \"golang:${GO_VERSION}\"\nDOCKER_IMAGE_GOLINT     ?= \"golangci/golangci-lint:v1.64.4@sha256:e83b903d722c12402c9d88948a6cac42ea0e34bf336fc6a170ade9adeecb2d0e\"\nDOCKER_IMAGE_DOCKERLINT ?= \"hadolint/hadolint:v2.12.0\"\nDOCKER_IMAGE_COSIGN     ?= \"bitnami/cosign:1.8.0@sha256:8c2c61c546258fffff18b47bb82a65af6142007306b737129a7bd5429d53629a\"\nDOCKER_IMAGE_GH_CLI     ?= \"ghcr.io/supportpal/github-gh-cli:2.31.0@sha256:71371e36e62bd24ddd42d9e4c720a7e9954cb599475e24d1407af7190e2a5685\"\n\n# To build FIPS-compliant push-proxy: make build-fips\n# Requires Docker to be installed and running\nFIPS_ENABLED ?= false\nBUILD_IMAGE_FIPS ?= cgr.dev/mattermost.com/go-msft-fips:1.24.6\nBASE_IMAGE_FIPS ?= cgr.dev/mattermost.com/glibc-openssl-fips:15.1\n\n## Cosign Variables\n# The public key\nCOSIGN_PUBLIC_KEY       ?= akey\n# The private key\nCOSIGN_KEY              ?= akey\n# The passphrase used to decrypt the private key\nCOSIGN_PASSWORD         ?= password\n\n## Go Variables\n# Go executable\nGO                           := $(shell which go)\n# Extract GO version from go.mod file\nGO_VERSION                   ?= $(shell grep -E '^go' go.mod | awk {'print $$2'})\n# LDFLAGS\nGO_LDFLAGS                   += -X \"github.com/mattermost/${APP_NAME}/internal/version.gitVersion=$(GIT_VERSION)\"\nGO_LDFLAGS                   += -X \"github.com/mattermost/${APP_NAME}/internal/version.buildHash=$(BUILD_HASH)\"\nGO_LDFLAGS                   += -X \"github.com/mattermost/${APP_NAME}/internal/version.buildTagLatest=$(BUILD_TAG_LATEST)\"\nGO_LDFLAGS                   += -X \"github.com/mattermost/${APP_NAME}/internal/version.buildTagCurrent=$(BUILD_TAG_CURRENT)\"\nGO_LDFLAGS                   += -X \"github.com/mattermost/${APP_NAME}/internal/version.gitTreeState=$(GIT_TREESTATE)\"\nGO_LDFLAGS                   += -X \"github.com/mattermost/${APP_NAME}/internal/version.buildDate=$(BUILD_DATE)\"\n\n# Architectures to build for\nGO_BUILD_PLATFORMS           ?= linux-amd64 linux-arm64 freebsd-amd64\nGO_BUILD_PLATFORMS_ARTIFACTS = $(foreach cmd,$(addprefix go-build/,${APP_NAME}),$(addprefix $(cmd)-,$(GO_BUILD_PLATFORMS)))\n\n# Build options\nGO_BUILD_OPTS                += -trimpath\nGO_TEST_OPTS                 += -v -timeout=180s\n# Temporary folder to output compiled binaries artifacts\nGO_OUT_BIN_DIR               := ./dist\n\n## Github Variables\n# A github access token that provides access to upload artifacts under releases\nGITHUB_TOKEN                 ?= a_token\n# Github organization\nGITHUB_ORG                   := mattermost\n# Most probably the name of the repo\nGITHUB_REPO                  := ${APP_NAME}\n\n## FIPS Docker Variables\nAPP_NAME_FIPS ?= mattermost/mattermost-push-proxy-fips\nAPP_VERSION_FIPS ?= $(APP_VERSION)-fips\nAPP_VERSION_NO_V_FIPS ?= $(APP_VERSION_NO_V)-fips\n\n## Architecture Variables (default to ARM64 for local builds)\nTARGET_OS ?= linux\nTARGET_ARCH ?= arm64\n\nOUTDATED_VER := master\nOUTDATED_BIN := go-mod-outdated\nOUTDATED_GEN := $(TOOLS_BIN_DIR)/$(OUTDATED_BIN)\n\n# ====================================================================================\n# Colors\n\nBLUE   := $(shell printf \"\\033[34m\")\nYELLOW := $(shell printf \"\\033[33m\")\nRED    := $(shell printf \"\\033[31m\")\nGREEN  := $(shell printf \"\\033[32m\")\nCYAN   := $(shell printf \"\\033[36m\")\nCNone  := $(shell printf \"\\033[0m\")\n\n# ====================================================================================\n# Logger\n\nTIME_LONG\t= `date +%Y-%m-%d' '%H:%M:%S`\nTIME_SHORT\t= `date +%H:%M:%S`\nTIME\t\t= $(TIME_SHORT)\n\nINFO = echo ${TIME} ${BLUE}[ .. ]${CNone}\nWARN = echo ${TIME} ${YELLOW}[WARN]${CNone}\nERR  = echo ${TIME} ${RED}[FAIL]${CNone}\nOK   = echo ${TIME} ${GREEN}[ OK ]${CNone}\nFAIL = (echo ${TIME} ${RED}[FAIL]${CNone} && false)\n\n# ====================================================================================\n# Verbosity control hack\n\nVERBOSE ?= 0\nAT_0 := @\nAT_1 :=\nAT = $(AT_$(VERBOSE))\n\n# ====================================================================================\n# Used for semver bumping\nCURRENT_VERSION := $(shell git describe --abbrev=0 --tags)\nVERSION_PARTS := $(subst ., ,$(subst v,,$(CURRENT_VERSION)))\nMAJOR := $(word 1,$(VERSION_PARTS))\nMINOR := $(word 2,$(VERSION_PARTS))\nPATCH := $(word 3,$(VERSION_PARTS))\n\n# Check if current branch is protected\ndefine check_protected_branch\n\t@current_branch=$$(git rev-parse --abbrev-ref HEAD); \\\n\tif ! echo \"$(PROTECTED_BRANCH)\" | grep -wq \"$$current_branch\"; then \\\n\t\techo \"Error: Tagging is only allowed from $(PROTECTED_BRANCH) branch. You are on $$current_branch branch.\"; \\\n\t\texit 1; \\\n\tfi\nendef\n# Check if there are pending pulls\ndefine check_pending_pulls\n\t@git fetch; \\\n\tcurrent_branch=$$(git rev-parse --abbrev-ref HEAD); \\\n\tif [ \"$$(git rev-parse HEAD)\" != \"$$(git rev-parse origin/$$current_branch)\" ]; then \\\n\t\techo \"Error: Your branch is not up to date with upstream. Please pull the latest changes before performing a release\"; \\\n\t\texit 1; \\\n\tfi\nendef\n# ====================================================================================\n# Targets\n\nhelp: ## to get help\n\t@echo \"Usage:\"\n\t@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) |\\\n\tawk 'BEGIN {FS = \":.*?## \"}; {printf \"make ${CYAN}%-30s${CNone} %s\\n\", $$1, $$2}'\n\n.PHONY: build\nbuild: go-build-docker ## to build\n\n.PHONY: build-all\nbuild-all: build build-fips ## to build both normal and FIPS versions\n\n.PHONY: release\nrelease: build github-release ## to build and release artifacts\n\n.PHONY: release-fips\nrelease-fips: build-fips ## to build and release FIPS artifacts\n\n.PHONY: release-all\nrelease-all: build-all github-release-all ## to build and release both versions\n\n.PHONY: package\npackage: go-build package-software ## to build, package\n\n.PHONY: package-fips\npackage-fips: build-fips ## to build FIPS version\n\n.PHONY: package-all\npackage-all: package package-fips ## to build, package both versions\n\n.PHONY: sign\nsign: docker-sign docker-verify ## to sign the artifact and perform verification\n\n.PHONY: lint\nlint: go-lint docker-lint ## to lint\n\n.PHONY: test\ntest: go-test ## to test\n\n\n.PHONY: patch minor major\n\npatch: ## to bump patch version (semver)\n\t$(call check_protected_branch)\n\t$(call check_pending_pulls)\n\t@$(eval PATCH := $(shell echo $$(($(PATCH)+1))))\n\t@$(INFO) Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)\n\tgit tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m \"Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)\"\n\tgit push origin v$(MAJOR).$(MINOR).$(PATCH)\n\t@$(OK) Bumping $(APP_NAME) to Patch version $(MAJOR).$(MINOR).$(PATCH)\n\nminor: ## to bump minor version (semver)\n\t$(call check_protected_branch)\n\t$(call check_pending_pulls)\n\t@$(eval MINOR := $(shell echo $$(($(MINOR)+1))))\n\t@$(eval PATCH := 0)\n\t@$(INFO) Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)\n\tgit tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m \"Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)\"\n\tgit push origin v$(MAJOR).$(MINOR).$(PATCH)\n\t@$(OK) Bumping $(APP_NAME) to Minor version $(MAJOR).$(MINOR).$(PATCH)\n\nmajor: ## to bump major version (semver)\n\t$(call check_protected_branch)\n\t$(call check_pending_pulls)\n\t$(eval MAJOR := $(shell echo $$(($(MAJOR)+1))))\n\t$(eval MINOR := 0)\n\t$(eval PATCH := 0)\n\t@$(INFO) Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)\n\tgit tag -s -a v$(MAJOR).$(MINOR).$(PATCH) -m \"Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)\"\n\tgit push origin v$(MAJOR).$(MINOR).$(PATCH)\n\t@$(OK) Bumping $(APP_NAME) to Major version $(MAJOR).$(MINOR).$(PATCH)\n\npackage-software:  ## to package the binary\n\t@$(INFO) Packaging\n\t$(AT) for file in $(GO_OUT_BIN_DIR)/mattermost-push-proxy-*; do \\\n\t\t[[ \"$$file\" == *.tar.gz ]] && continue; \\\n\t\ttarget=$$(basename $$file); \\\n\t\tmkdir -p $(GO_OUT_BIN_DIR)/$${target}_temp/bin; \\\n\t\tcp -RL config $(GO_OUT_BIN_DIR)/$${target}_temp/config; \\\n\t\techo $(APP_VERSION) > $(GO_OUT_BIN_DIR)/$${target}_temp/config/build.txt; \\\n\t\tcp LICENSE.txt NOTICE.txt README.md $(GO_OUT_BIN_DIR)/$${target}_temp; \\\n\t\tmkdir $(GO_OUT_BIN_DIR)/$${target}_temp/logs; \\\n\t\tmv $$file $(GO_OUT_BIN_DIR)/$${target}_temp/bin/mattermost-push-proxy; \\\n\t\tmv $(GO_OUT_BIN_DIR)/$${target}_temp $(GO_OUT_BIN_DIR)/$${target}; \\\n\t\ttar -czf $(GO_OUT_BIN_DIR)/$${target}.tar.gz -C $(GO_OUT_BIN_DIR) $${target}; \\\n\t\trm -r $(GO_OUT_BIN_DIR)/$${target}; \\\n\tdone\n\t@$(OK) Packaging\n\n.PHONY: package-software-fips\npackage-software-fips:  ## to package the FIPS binary\n\t@$(INFO) Packaging FIPS\n\t$(AT) for file in $(GO_OUT_BIN_DIR)/mattermost-push-proxy-*-fips; do \\\n\t\t[[ \"$$file\" == *.tar.gz ]] && continue; \\\n\t\ttarget=$$(basename $$file); \\\n\t\tmkdir -p $(GO_OUT_BIN_DIR)/$${target}_temp/bin; \\\n\t\tcp -RL config $(GO_OUT_BIN_DIR)/$${target}_temp/config; \\\n\t\techo $(APP_VERSION)-fips > $(GO_OUT_BIN_DIR)/$${target}_temp/config/build.txt; \\\n\t\tcp LICENSE.txt NOTICE.txt README.md $(GO_OUT_BIN_DIR)/$${target}_temp; \\\n\t\tmkdir $(GO_OUT_BIN_DIR)/$${target}_temp/logs; \\\n\t\tmv $$file $(GO_OUT_BIN_DIR)/$${target}_temp/bin/mattermost-push-proxy; \\\n\t\tmv $(GO_OUT_BIN_DIR)/$${target}_temp $(GO_OUT_BIN_DIR)/$${target}; \\\n\t\ttar -czf $(GO_OUT_BIN_DIR)/$${target}.tar.gz -C $(GO_OUT_BIN_DIR) $${target}; \\\n\t\trm -r $(GO_OUT_BIN_DIR)/$${target}; \\\n\tdone\n\t@$(OK) Packaging FIPS\n\n.PHONY: docker-build\ndocker-build: ## to build the docker image\n\t@$(INFO) Performing Docker build ${APP_NAME}:${APP_VERSION_NO_V}\n\t$(AT)$(DOCKER) buildx build \\\n\t--no-cache --pull --platform linux/amd64,linux/arm64 \\\n\t-f ${DOCKER_FILE} . \\\n\t-t ${APP_NAME}:${APP_VERSION_NO_V} || ${FAIL}\n\t@$(OK) Performing Docker build ${APP_NAME}:${APP_VERSION_NO_V}\n\n## --------------------------------------\n## Regular Multi-Architecture Build Targets\n## --------------------------------------\n\n.PHONY: build-image-amd64-with-tags\nbuild-image-amd64-with-tags: go-build-amd64 package-software ## Build Docker image for AMD64 with tags\n\t@echo \"Building mattermost-push-proxy Docker Image for AMD64\"\n\tdocker build --no-cache --pull \\\n\t\t--build-arg TARGETOS=linux \\\n\t\t--build-arg TARGETARCH=amd64 \\\n\t\t-f ${DOCKER_FILE} \\\n\t\t-t ${APP_NAME}:${APP_VERSION_NO_V}-amd64 \\\n\t\t-t ${APP_NAME}:${APP_VERSION_NO_V} \\\n\t\t.\n\n.PHONY: build-image-arm64-with-tags\nbuild-image-arm64-with-tags: go-build-arm64 package-software ## Build Docker image for ARM64 with tags\n\t@echo \"Building mattermost-push-proxy Docker Image for ARM64\"\n\tdocker build --no-cache --pull \\\n\t\t--build-arg TARGETOS=linux \\\n\t\t--build-arg TARGETARCH=arm64 \\\n\t\t-f ${DOCKER_FILE} \\\n\t\t-t ${APP_NAME}:${APP_VERSION_NO_V}-arm64 \\\n\t\t-t ${APP_NAME}:${APP_VERSION_NO_V} \\\n\t\t.\n\n.PHONY: docker-build-parallel-with-tags\ndocker-build-parallel-with-tags: ## Build Docker images for both architectures in parallel\n\t@echo \"Building mattermost-push-proxy Docker Images for both platforms in parallel\"\n\t$(MAKE) build-image-amd64-with-tags &\n\t$(MAKE) build-image-arm64-with-tags &\n\twait\n\t@echo \"Creating multi-platform manifests with clean tags\"\n\tdocker manifest create ${APP_NAME}:${APP_VERSION_NO_V} \\\n\t\t--amend ${APP_NAME}:${APP_VERSION_NO_V}-amd64 \\\n\t\t--amend ${APP_NAME}:${APP_VERSION_NO_V}-arm64\n\t@echo \"✅ Multi-platform manifests created\"\n\n.PHONY: docker-push-with-tags\ndocker-push-with-tags: ## Push Docker images with unified tags\n\t@echo \"Pushing Docker images to registry\"\n\tdocker push ${APP_NAME}:${APP_VERSION_NO_V}-amd64\n\tdocker push ${APP_NAME}:${APP_VERSION_NO_V}-arm64\n\tdocker manifest push ${APP_NAME}:${APP_VERSION_NO_V}\n\t@echo \"Cleaning up intermediate architecture-specific tags from registry\"\n\tdocker rmi ${APP_NAME}:${APP_VERSION_NO_V}-amd64\n\tdocker rmi ${APP_NAME}:${APP_VERSION_NO_V}-arm64\n\t@echo \"✅ Multi-platform images pushed with unified tags\"\n\t@echo \"✅ Intermediate architecture-specific tags cleaned up from registry\"\n\n.PHONY: cleanup-tags\ncleanup-tags: ## Clean up intermediate architecture-specific tags from registry\n\t@echo \"Cleaning up intermediate architecture-specific tags from registry\"\n\t@echo \"Removing AMD64 tag: ${APP_NAME}:${APP_VERSION_NO_V}-amd64\"\n\tdocker rmi ${APP_NAME}:${APP_VERSION_NO_V}-amd64 2>/dev/null || true\n\t@echo \"Removing ARM64 tag: ${APP_NAME}:${APP_VERSION_NO_V}-arm64\"\n\tdocker rmi ${APP_NAME}:${APP_VERSION_NO_V}-arm64 2>/dev/null || true\n\t@echo \"✅ Intermediate architecture-specific tags cleaned up from registry\"\n\n.PHONY: build-image-fips\nbuild-image-fips: ## Build the FIPS docker image for mattermost-push-proxy\n\t@echo \"Building mattermost-push-proxy FIPS Docker Image for $(TARGET_ARCH)\"\n\tdocker build --no-cache --pull \\\n\t\t--build-arg BUILD_IMAGE=$(BUILD_IMAGE_FIPS) \\\n\t\t--build-arg BASE_IMAGE=$(BASE_IMAGE_FIPS) \\\n\t\t--build-arg TARGETOS=$(TARGET_OS) \\\n\t\t--build-arg TARGETARCH=$(TARGET_ARCH) \\\n\t\t-f docker/Dockerfile.fips \\\n\t\t-t $(APP_NAME_FIPS):$(APP_VERSION_NO_V) .\n\n.PHONY: buildx-image-fips\nbuildx-image-fips: ## Builds and pushes the FIPS docker image for mattermost-push-proxy\n\t@echo \"Building mattermost-push-proxy FIPS Docker Image with buildx\"\n\tdocker buildx build --no-cache --pull \\\n\t\t--platform linux/amd64,linux/arm64 \\\n\t\t--build-arg BUILD_IMAGE=$(BUILD_IMAGE_FIPS) \\\n\t\t--build-arg BASE_IMAGE=$(BASE_IMAGE_FIPS) \\\n\t\t--build-arg TARGETOS=linux \\\n\t\t--build-arg TARGETARCH=amd64 \\\n\t\t-f docker/Dockerfile.fips \\\n\t\t-t $(APP_NAME_FIPS):$(APP_VERSION_NO_V) \\\n\t\t--push .\n\n## --------------------------------------\n## FIPS Multi-Architecture Build Targets (Fast, Parallel)\n## --------------------------------------\n\n.PHONY: build-image-fips-amd64-with-tags\nbuild-image-fips-amd64-with-tags: ## Build FIPS Docker image for AMD64 with tags\n\t@echo \"Building mattermost-push-proxy FIPS Docker Image for AMD64\"\n\tdocker build --no-cache --pull \\\n\t\t--build-arg BUILD_IMAGE=$(BUILD_IMAGE_FIPS) \\\n\t\t--build-arg BASE_IMAGE=$(BASE_IMAGE_FIPS) \\\n\t\t--build-arg TARGETOS=linux \\\n\t\t--build-arg TARGETARCH=amd64 \\\n\t\t-f docker/Dockerfile.fips \\\n\t\t-t $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-amd64 \\\n\t\t-t $(APP_NAME_FIPS):$(APP_VERSION_NO_V) \\\n\t\t.\n\n.PHONY: build-image-fips-arm64-with-tags\nbuild-image-fips-arm64-with-tags: ## Build FIPS Docker image for ARM64 with tags\n\t@echo \"Building mattermost-push-proxy FIPS Docker Image for ARM64\"\n\tdocker build --no-cache --pull \\\n\t\t--build-arg BUILD_IMAGE=$(BUILD_IMAGE_FIPS) \\\n\t\t--build-arg BASE_IMAGE=$(BASE_IMAGE_FIPS) \\\n\t\t--build-arg TARGETOS=linux \\\n\t\t--build-arg TARGETARCH=arm64 \\\n\t\t-f docker/Dockerfile.fips \\\n\t\t-t $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-arm64 \\\n\t\t-t $(APP_NAME_FIPS):$(APP_VERSION_NO_V) \\\n\t\t.\n\n.PHONY: docker-build-fips-parallel-with-tags\ndocker-build-fips-parallel-with-tags: ## Build FIPS Docker images for both architectures in parallel (FAST)\n\t@echo \"Building mattermost-push-proxy FIPS Docker Images for both platforms in parallel\"\n\t$(MAKE) build-image-fips-amd64-with-tags &\n\t$(MAKE) build-image-fips-arm64-with-tags &\n\twait\n\t@echo \"Creating multi-platform manifests with clean tags\"\n\tdocker manifest create $(APP_NAME_FIPS):$(APP_VERSION_NO_V) \\\n\t\t--amend $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-amd64 \\\n\t\t--amend $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-arm64\n\t@echo \"✅ Multi-platform manifests created\"\n\n.PHONY: docker-push-fips-with-tags\ndocker-push-fips-with-tags: ## Push FIPS Docker images with unified tags\n\t@echo \"Pushing FIPS Docker images to registry\"\n\tdocker push $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-amd64\n\tdocker push $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-arm64\n\tdocker manifest push $(APP_NAME_FIPS):$(APP_VERSION_NO_V)\n\t@echo \"Cleaning up intermediate architecture-specific tags from registry\"\n\tdocker rmi $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-amd64\n\tdocker rmi $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-arm64\n\t@echo \"✅ FIPS multi-platform images pushed with unified tags\"\n\t@echo \"✅ Intermediate architecture-specific tags cleaned up from registry\"\n\n.PHONY: github-release-fips\ngithub-release-fips: ## Create GitHub release for FIPS version\n\t@echo \"Creating GitHub release for FIPS version\"\n\tgh release create v$(APP_VERSION_FIPS) \\\n\t\t--title \"Release v$(APP_VERSION_FIPS) (FIPS)\" \\\n\t\t--notes \"FIPS-compliant release of mattermost-push-proxy\" \\\n\t\t--target main\n\t@echo \"✅ GitHub release created for FIPS version\"\n\n.PHONY: cleanup-fips-tags\ncleanup-fips-tags: ## Clean up intermediate FIPS architecture-specific tags from registry\n\t@echo \"Cleaning up intermediate FIPS architecture-specific tags from registry\"\n\t@echo \"Removing AMD64 tag: $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-amd64\"\n\tdocker rmi $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-amd64 2>/dev/null || true\n\t@echo \"Removing ARM64 tag: $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-arm64\"\n\tdocker rmi $(APP_NAME_FIPS):$(APP_VERSION_NO_V)-arm64 2>/dev/null || true\n\t@echo \"✅ Intermediate FIPS architecture-specific tags cleaned up from registry\"\n\n.PHONY: docker-push\ndocker-push: ## to push the docker image\n\t@$(INFO) Pushing to registry...\n\t$(AT)$(DOCKER) buildx build \\\n\t--no-cache --pull --platform linux/amd64,linux/arm64 \\\n\t-f ${DOCKER_FILE} . \\\n\t-t $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V} $(LATEST_DOCKER_TAG) --push || ${FAIL}\n\t@$(OK) Pushing to registry $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}\n\n.PHONY: docker-push-fips\ndocker-push-fips: ## to push the FIPS docker image (builds for default TARGET_ARCH: $(TARGET_ARCH))\n\t@$(INFO) Pushing FIPS to registry...\n\t$(AT)$(DOCKER) build \\\n\t--no-cache --pull \\\n\t--platform $(TARGET_OS)/$(TARGET_ARCH) \\\n\t-f ${DOCKER_FILE}.fips . \\\n\t-t $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips || ${FAIL}\n\t$(AT)$(DOCKER) push $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips || ${FAIL}\n\t@$(OK) Pushing FIPS to registry $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips\n\n.PHONY: docker-push-fips-linux-amd64\ndocker-push-fips-linux-amd64: ## to push the FIPS docker image for Linux AMD64\n\t@$(INFO) Pushing FIPS Linux AMD64 to registry...\n\t$(AT)$(DOCKER) build \\\n\t--no-cache --pull \\\n\t--platform linux/amd64 \\\n\t-f ${DOCKER_FILE}.fips . \\\n\t-t $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips-linux-amd64 || ${FAIL}\n\t$(AT)$(DOCKER) push $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips-linux-amd64 || ${FAIL}\n\t@$(OK) Pushing FIPS Linux AMD64 to registry $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips-linux-amd64\n\n.PHONY: docker-push-fips-linux-arm64\ndocker-push-fips-linux-arm64: ## to push the FIPS docker image for Linux ARM64\n\t@$(INFO) Pushing FIPS Linux ARM64 to registry...\n\t$(AT)$(DOCKER) build \\\n\t--no-cache --pull \\\n\t--platform linux/arm64 \\\n\t-f ${DOCKER_FILE}.fips . \\\n\t-t $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips-linux-arm64 || ${FAIL}\n\t$(AT)$(DOCKER) push $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips-linux-arm64 || ${FAIL}\n\t@$(OK) Pushing FIPS Linux ARM64 to registry $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION_NO_V}-fips-linux-arm64\n\n.PHONY: docker-push-fips-parallel\ndocker-push-fips-parallel: ## to push FIPS docker images for both architectures in parallel\n\t@$(INFO) Pushing FIPS Docker images for both architectures in parallel\n\t$(AT)$(MAKE) docker-push-fips-linux-amd64 &\n\t$(AT)$(MAKE) docker-push-fips-linux-arm64 &\n\twait\n\t@$(OK) FIPS Docker images pushed for both architectures\n\n\n\n.PHONY: docker-sign\ndocker-sign: ## to sign the docker image\n\t@$(INFO) Signing the docker image...\n\t$(AT)echo \"$${COSIGN_KEY}\" > cosign.key && \\\n\t$(DOCKER) run ${DOCKER_OPTS} \\\n\t--entrypoint '/bin/sh' \\\n        -v $(PWD):/app -w /app \\\n\t-e COSIGN_PASSWORD=${COSIGN_PASSWORD} \\\n\t-e HOME=\"/tmp\" \\\n    ${DOCKER_IMAGE_COSIGN} \\\n\t-c \\\n\t\"echo Signing... && \\\n\tcosign login $(DOCKER_REGISTRY) -u ${DOCKER_USER} -p ${DOCKER_PASSWORD} && \\\n\tcosign sign --key cosign.key $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION}\" || ${FAIL}\n# if we are on a latest semver APP_VERSION tag, also sign latest tag\nifneq ($(shell echo $(APP_VERSION) | egrep '^v([0-9]+\\.){0,2}(\\*|[0-9]+)'),)\n  ifeq ($(shell git tag -l --sort=v:refname | tail -n1),$(APP_VERSION))\n\t$(DOCKER) run ${DOCKER_OPTS} \\\n\t--entrypoint '/bin/sh' \\\n        -v $(PWD):/app -w /app \\\n\t-e COSIGN_PASSWORD=${COSIGN_PASSWORD} \\\n\t-e HOME=\"/tmp\" \\\n\t${DOCKER_IMAGE_COSIGN} \\\n\t-c \\\n\t\"echo Signing... && \\\n\tcosign login $(DOCKER_REGISTRY) -u ${DOCKER_USER} -p ${DOCKER_PASSWORD} && \\\n\tcosign sign --key cosign.key $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:latest\" || ${FAIL}\n  endif\nendif\n\t$(AT)rm -f cosign.key || ${FAIL}\n\t@$(OK) Signing the docker image: $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION}\n\n.PHONY: docker-verify\ndocker-verify: ## to verify the docker image\n\t@$(INFO) Verifying the published docker image...\n\t$(AT)echo \"$${COSIGN_PUBLIC_KEY}\" > cosign_public.key && \\\n\t$(DOCKER) run ${DOCKER_OPTS} \\\n\t--entrypoint '/bin/sh' \\\n\t-v $(PWD):/app -w /app \\\n\t${DOCKER_IMAGE_COSIGN} \\\n\t-c \\\n\t\"echo Verifying... && \\\n\tcosign verify --key cosign_public.key $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION}\" || ${FAIL}\n# if we are on a latest semver APP_VERSION tag, also verify latest tag\nifneq ($(shell echo $(APP_VERSION) | egrep '^v([0-9]+\\.){0,2}(\\*|[0-9]+)'),)\n  ifeq ($(shell git tag -l --sort=v:refname | tail -n1),$(APP_VERSION))\n\t$(DOCKER) run ${DOCKER_OPTS} \\\n\t--entrypoint '/bin/sh' \\\n\t-v $(PWD):/app -w /app \\\n\t${DOCKER_IMAGE_COSIGN} \\\n\t-c \\\n\t\"echo Verifying... && \\\n\tcosign verify --key cosign_public.key $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:latest\" || ${FAIL}\n  endif\nendif\n\t$(AT)rm -f cosign_public.key || ${FAIL}\n\t@$(OK) Verifying the published docker image: $(DOCKER_REGISTRY)/${DOCKER_REGISTRY_REPO}:${APP_VERSION}\n\n.PHONY: docker-sbom\ndocker-sbom: ## to print a sbom report\n\t@$(INFO) Performing Docker sbom report...\n\t$(AT)$(DOCKER) sbom ${APP_NAME}:${APP_VERSION} || ${FAIL}\n\t@$(OK) Performing Docker sbom report\n\n.PHONY: docker-scan\ndocker-scan: ## to print a vulnerability report\n\t@$(INFO) Performing Docker scan report...\n\t$(AT)$(DOCKER) scan ${APP_NAME}:${APP_VERSION} || ${FAIL}\n\t@$(OK) Performing Docker scan report\n\n.PHONY: docker-scout\n\t@$(INFO) Performing Docker scout report...\n\t$(AT)$(DOCKER) scout cves ${APP_NAME}:${APP_VERSION} || ${FAIL}\n\t@$(OK) Performing Docker scout report\n\n.PHONY: docker-lint\ndocker-lint: ## to lint the Dockerfile\n\t@$(INFO) Dockerfile linting...\n\t$(AT)$(DOCKER) run -i ${DOCKER_OPTS} \\\n\t${DOCKER_IMAGE_DOCKERLINT} \\\n\t< ${DOCKER_FILE} || ${FAIL}\n\t@$(OK) Dockerfile linting\n\n.PHONY: docker-login\ndocker-login: ## to login to a container registry\n\t@$(INFO) Dockerd login to container registry ${DOCKER_REGISTRY}...\n\t$(AT) echo \"${DOCKER_PASSWORD}\" | $(DOCKER) login --password-stdin -u ${DOCKER_USER} $(DOCKER_REGISTRY) || ${FAIL}\n\t@$(OK) Dockerd login to container registry ${DOCKER_REGISTRY}...\n\ngo-build: $(GO_BUILD_PLATFORMS_ARTIFACTS) ## to build binaries\n\n.PHONY: go-build-amd64\ngo-build-amd64: go-build/$(APP_NAME)-linux-amd64 ## Build AMD64 binary only\n\n.PHONY: go-build-arm64\ngo-build-arm64: go-build/$(APP_NAME)-linux-arm64 ## Build ARM64 binary only\n\n.PHONY: go-build\ngo-build/%:\n\t@$(INFO) go build $*...\n\t$(AT)target=\"$*\"; \\\n\tcommand=\"${APP_NAME}\"; \\\n\tplatform_ext=\"$${target#$$command-*}\"; \\\n\tplatform=\"$${platform_ext%.*}\"; \\\n\texport GOOS=\"$${platform%%-*}\"; \\\n\texport GOARCH=\"$${platform#*-}\"; \\\n\techo export GOOS=$${GOOS}; \\\n\techo export GOARCH=$${GOARCH}; \\\n\tCGO_ENABLED=0 \\\n\t$(GO) build ${GO_BUILD_OPTS} \\\n\t-ldflags '${GO_LDFLAGS}' \\\n\t-o ${GO_OUT_BIN_DIR}/$* \\\n\t${CONFIG_APP_CODE} || ${FAIL}\n\t@$(OK) go build $*\n\n.PHONY: go-build-docker\ngo-build-docker: # to build binaries under a controlled docker dedicated go container using DOCKER_IMAGE_GO\n\t@$(INFO) go build docker\n\t$(AT)$(DOCKER) run  \\\n\t-v $(PWD):/app -w /app \\\n\t$(DOCKER_IMAGE_GO) \\\n\t/bin/sh -c \\\n\t\"cd /app && \\\n\tmake go-build\"  || ${FAIL}\n\t@$(OK) go build docker\n\n## --------------------------------------\n## FIPS Build Targets\n## --------------------------------------\n\n_build-fips-internal: ## Internal FIPS build target (used by Dockerfile.fips and build-fips)\n\t@echo \"Building mattermost-push-proxy (FIPS)\"\n\t@mkdir -p $(GO_OUT_BIN_DIR)\n\tGO111MODULE=on GOOS=$(TARGET_OS) GOARCH=$(TARGET_ARCH) CGO_ENABLED=1 go build -tags=fips,goexperiment.opensslcrypto -trimpath -o $(GO_OUT_BIN_DIR)/mattermost-push-proxy ./main.go\n\n.PHONY: build-fips\nbuild-fips: ## Build the mattermost-push-proxy with FIPS-compliant settings using containerized build\n\t@echo \"Building mattermost-push-proxy (FIPS - $(TARGET_ARCH))\"\n\tdocker run --rm -v $(shell pwd):/app -w /app \\\n\t\t--entrypoint=\"\" \\\n\t\t-e TARGET_OS=$(TARGET_OS) \\\n\t\t-e TARGET_ARCH=$(TARGET_ARCH) \\\n\t\t-e CGO_ENABLED=1 \\\n\t\t-e GOFIPS=1 \\\n\t\t-e GOEXPERIMENT=systemcrypto \\\n\t\t-e HOST_UID=$(shell id -u) \\\n\t\t-e HOST_GID=$(shell id -g) \\\n\t\t$(BUILD_IMAGE_FIPS) \\\n\t\tsh -c \"cd /app && make _build-fips-internal TARGET_OS=\\$$TARGET_OS TARGET_ARCH=\\$$TARGET_ARCH && mv $(GO_OUT_BIN_DIR)/mattermost-push-proxy $(GO_OUT_BIN_DIR)/mattermost-push-proxy-fips-$(TARGET_ARCH) && chown \\$$HOST_UID:\\$$HOST_GID $(GO_OUT_BIN_DIR)/mattermost-push-proxy-fips-$(TARGET_ARCH)\"\n\n.PHONY: build-fips-amd64\nbuild-fips-amd64: ## Build the mattermost-push-proxy with FIPS-compliant settings for AMD64\n\t$(MAKE) build-fips TARGET_ARCH=amd64\n\n.PHONY: build-fips-arm64\nbuild-fips-arm64: ## Build the mattermost-push-proxy with FIPS-compliant settings for ARM64\n\t$(MAKE) build-fips TARGET_ARCH=arm64\n\n.PHONY: build-fips-all\nbuild-fips-all: build-fips-amd64 build-fips-arm64 ## Build FIPS binaries for both architectures\n\n.PHONY: go-run\ngo-run: ## to run locally for development\n\t@$(INFO) running locally...\n\t$(AT)$(GO) run ${GO_BUILD_OPTS} ${CONFIG_APP_CODE} || ${FAIL}\n\t@$(OK) running locally\n\n.PHONY: go-test\ngo-test: ## to run tests\n\t@$(INFO) testing...\n\t$(AT)$(DOCKER) run ${DOCKER_OPTS} \\\n\t-v $(PWD):/app -w /app \\\n\t$(DOCKER_IMAGE_GO) \\\n\t/bin/sh -c \\\n\t\"cd /app && \\\n\tgo test ${GO_TEST_OPTS} ./... \" || ${FAIL}\n\t@$(OK) testing\n\n.PHONY: go-mod-check\ngo-mod-check: ## to check go mod files consistency\n\t@$(INFO) Checking go mod files consistency...\n\t$(AT)$(GO) mod tidy\n\t$(AT)git --no-pager diff --exit-code go.mod go.sum || \\\n\t(${WARN} Please run \"go mod tidy\" and commit the changes in go.mod and go.sum. && ${FAIL} ; exit 128 )\n\t@$(OK) Checking go mod files consistency\n\n.PHONY: go-lint\ngo-lint: ## to lint go code\n\t@$(INFO) App linting...\n\t$(AT)$(DOCKER) run ${DOCKER_OPTS} \\\n\t-v $(PWD):/app -w /app \\\n\t${DOCKER_IMAGE_GOLINT} \\\n\tgolangci-lint run ./... || ${FAIL}\n\t@$(OK) App linting\n\n.PHONY: go-doc\ngo-doc: ## to generate documentation\n\t@$(INFO) Generating Documentation...\n\t$(AT)$(GO) run ./scripts/env_config.go ./docs/env_config.md || ${FAIL}\n\t@$(OK) Generating Documentation\n\n.PHONY: check-modules\ncheck-modules: $(OUTDATED_GEN) ## Check outdated modules\n\t@echo Checking outdated modules\n\t$(GO) list -mod=mod -u -m -json all | $(OUTDATED_GEN) -update -direct\n\n.PHONY: update-modules\nupdate-modules: ## Update all modules to latest versions\n\t@echo Updating modules\n\t$(GO) get -u ./...\n\t$(GO) mod tidy\n\n.PHONY: scan\nscan: ## Scan Docker image for vulnerabilities using Docker Scout\n\t@echo Running Docker Scout vulnerability scan\n\t@if ! docker images -q ${APP_NAME}:${APP_VERSION_NO_V} | grep -q .; then \\\n\t\techo \"❌ Image ${APP_NAME}:${APP_VERSION_NO_V} not found locally. Please build it first with:\"; \\\n\t\techo \"   make build-image-amd64-with-tags (or build-image-arm64-with-tags)\"; \\\n\t\texit 1; \\\n\tfi\n\tdocker scout cves ${APP_NAME}:${APP_VERSION_NO_V}\n\n.PHONY: scan-fips\nscan-fips: ## Scan FIPS Docker image for vulnerabilities using Docker Scout\n\t@echo Running Docker Scout vulnerability scan for FIPS image\n\t@if ! docker images -q $(APP_NAME_FIPS):$(APP_VERSION_NO_V) | grep -q .; then \\\n\t\techo \"❌ Image $(APP_NAME_FIPS):$(APP_VERSION_NO_V) not found locally. Please build it first with:\"; \\\n\t\techo \"   make build-image-fips-amd64-with-tags (or build-image-fips-arm64-with-tags)\"; \\\n\t\texit 1; \\\n\tfi\n\tdocker scout cves $(APP_NAME_FIPS):$(APP_VERSION_NO_V)\n\n.PHONY: grype\ngrype: ## Scan Docker image for vulnerabilities using Grype\n\t@echo Running Grype vulnerability scan\n\t@if ! docker images -q ${APP_NAME}:${APP_VERSION_NO_V} | grep -q .; then \\\n\t\techo \"❌ Image ${APP_NAME}:${APP_VERSION_NO_V} not found locally. Please build it first with:\"; \\\n\t\techo \"   make build-image-amd64-with-tags (or build-image-arm64-with-tags)\"; \\\n\t\texit 1; \\\n\tfi\n\tgrype docker:${APP_NAME}:${APP_VERSION_NO_V} -o table --only-fixed\n\n.PHONY: grype-fips\ngrype-fips: ## Scan FIPS Docker image for vulnerabilities using Grype\n\t@echo Running Grype vulnerability scan for FIPS image\n\t@if ! docker images -q $(APP_NAME_FIPS):$(APP_VERSION_NO_V) | grep -q .; then \\\n\t\techo \"❌ Image $(APP_NAME_FIPS):$(APP_VERSION_NO_V) not found locally. Please build it first with:\"; \\\n\t\techo \"   make build-image-fips-amd64-with-tags (or build-image-fips-arm64-with-tags)\"; \\\n\t\texit 1; \\\n\tfi\n\tgrype docker:$(APP_NAME_FIPS):$(APP_VERSION_NO_V) -o table --only-fixed\n\n.PHONY: security-all\nsecurity-all: ## Run all vulnerability scans (Docker Scout and Grype) for both regular and FIPS images\n\t@echo \"🔍 Running comprehensive security scans for all images...\"\n\t@echo \"\"\n\t@echo \"==========================================\"\n\t@echo \"📊 Docker Scout - Regular Image\"\n\t@echo \"==========================================\"\n\t$(MAKE) scan\n\t@echo \"\"\n\t@echo \"==========================================\"\n\t@echo \"📊 Docker Scout - FIPS Image\"\n\t@echo \"==========================================\"\n\t$(MAKE) scan-fips\n\t@echo \"\"\n\t@echo \"==========================================\"\n\t@echo \"🛡️  Grype - Regular Image\"\n\t@echo \"==========================================\"\n\t$(MAKE) grype\n\t@echo \"\"\n\t@echo \"==========================================\"\n\t@echo \"🛡️  Grype - FIPS Image\"\n\t@echo \"==========================================\"\n\t$(MAKE) grype-fips\n\t@echo \"\"\n\t@echo \"✅ All security scans completed!\"\n\n.PHONY: security-build-and-scan\nsecurity-build-and-scan: ## Build images and run comprehensive security scans\n\t@echo \"🚀 Building images and running comprehensive security scans...\"\n\t@echo \"\"\n\t@echo \"==========================================\"\n\t@echo \"🔨 Building Regular ARM64 Image\"\n\t@echo \"==========================================\"\n\t$(MAKE) build-image-arm64-with-tags\n\t@echo \"\"\n\t@echo \"==========================================\"\n\t@echo \"🔨 Building FIPS ARM64 Image\"\n\t@echo \"==========================================\"\n\t$(MAKE) build-image-fips-arm64-with-tags\n\t@echo \"\"\n\t@echo \"🔍 Starting security scans...\"\n\t$(MAKE) security-all\n\n.PHONY: github-release\ngithub-release: ## to publish a release and relevant artifacts to GitHub\n\t@$(INFO) Generating github-release http://github.com/$(GITHUB_ORG)/$(GITHUB_REPO)/releases/tag/$(APP_VERSION) ...\nifeq ($(shell echo $(APP_VERSION) | egrep '^v([0-9]+\\.){0,2}(\\*|[0-9]+)'),)\n\t$(error \"We only support releases from semver tags\")\nelse\n\t$(AT)$(DOCKER) run \\\n\t-v $(PWD):/app -w /app \\\n\t-e GITHUB_TOKEN=${GITHUB_TOKEN} \\\n\t$(DOCKER_IMAGE_GH_CLI) \\\n\t/bin/sh -c \\\n\t\"git config --global --add safe.directory /app && cd /app && \\\n\tgh release create $(APP_VERSION) --generate-notes $(GO_OUT_BIN_DIR)/*\" || ${FAIL}\nendif\n\t@$(OK) Generating github-release http://github.com/$(GITHUB_ORG)/$(GITHUB_REPO)/releases/tag/$(APP_VERSION) ...\n\n\n\n.PHONY: github-release-all\ngithub-release-all: ## to publish both normal and FIPS releases to GitHub\n\t@$(INFO) Generating both releases...\n\t$(AT)$(MAKE) github-release\n\t$(AT)$(MAKE) github-release-fips\n\t@$(OK) Generating both releases\n\n.PHONY: clean\nclean: ## to clean-up\n\t@$(INFO) cleaning /${GO_OUT_BIN_DIR} folder...\n\t$(AT)rm -rf ${GO_OUT_BIN_DIR} || ${FAIL}\n\t@$(OK) cleaning /${GO_OUT_BIN_DIR} folder\n\n\n## --------------------------------------\n## Tooling Binaries\n## --------------------------------------\n$(OUTDATED_GEN): ## Build go-mod-outdated.\n\tGOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) github.com/psampaz/go-mod-outdated $(OUTDATED_BIN) $(OUTDATED_VER)\n"
  },
  {
    "path": "NOTICE.txt",
    "content": "Mattermost Platform \n© 2015 Mattermost, Inc.  All Rights Reserved.  See LICENSE.txt for license information.\n\nNOTICES: \n--------\n"
  },
  {
    "path": "README.md",
    "content": "# Mattermost Push Proxy ![CircleCI branch](https://img.shields.io/circleci/project/github/mattermost/mattermost-push-proxy/master.svg)\n\nSee https://developers.mattermost.com/contribute/mobile/push-notifications/service/\n\n\n# How to Release\n\nTo trigger a release of Mattermost Push-Proxy, follow these steps:\n\n1. **For Patch Release:** Run the following command:\n    ```\n    make patch\n    ```\n   This will release a patch change.\n\n2. **For Minor Release:** Run the following command:\n    ```\n    make minor\n    ```\n   This will release a minor change.\n\n3. **For Major Release:** Run the following command:\n    ```\n    make major\n    ```\n   This will release a major change.\n"
  },
  {
    "path": "bin/.gitignore",
    "content": "# Ignore all files in this directory\n*\n# Except this one\n!.gitignore\n"
  },
  {
    "path": "build/setup.txt",
    "content": "#!/bin/bash\n\n# copy following lines to /etc/init/matter-push-proxy.conf\n\n# start on runlevel [2345]\n# stop on runlevel [016]\n# respawn\n# chdir /home/ubuntu/matter-push-proxy\n# setuid ubuntu\n# console output\n# exec bin/push-proxy | logger\n\nsudo stop matter-push-proxy\n\n\nrm -f matter-push-proxy.tar.gz\nrm -rf ~/matter-push-proxy\n\nwget https://github.com/mattermost/push-proxy/releases/download/v0.1.0/matter-push-proxy.tar.gz\n\nmkdir -p ~/matter-push-proxy\n\ntar -C ~/ -xzf matter-push-proxy.tar.gz\n\ncp config-push-proxy.json ~/matter-push-proxy/config/config-push-proxy.json\n\nsudo start matter-push-proxy\n\nsleep 10\n\ncurl localhost:8066"
  },
  {
    "path": "config/mattermost-push-proxy.sample.json",
    "content": "{\n    \"ListenAddress\":\":8066\",\n    \"ThrottlePerSec\":300,\n    \"ThrottleMemoryStoreSize\":50000,\n    \"ThrottleVaryByHeader\":\"X-Forwarded-For\",\n    \"EnableMetrics\": false,\n    \"SendTimeoutSec\": 30,\n    \"RetryTimeoutSec\": 8,\n    \"ApplePushSettings\":[\n        {\n            \"Type\":\"apple\",\n            \"ApplePushUseDevelopment\":false,\n            \"ApplePushCertPrivate\":\"\",\n            \"ApplePushCertPassword\":\"\",\n            \"ApplePushTopic\":\"com.mattermost.Mattermost\",\n            \"AppleAuthKeyFile\": \"\",\n            \"AppleAuthKeyID\": \"\",\n            \"AppleTeamID\": \"\"\n        },\n        {\n            \"Type\":\"apple_rn\",\n            \"ApplePushUseDevelopment\":false,\n            \"ApplePushCertPrivate\":\"\",\n            \"ApplePushCertPassword\":\"\",\n            \"ApplePushTopic\":\"com.mattermost.react.native\",\n            \"AppleAuthKeyFile\": \"\",\n            \"AppleAuthKeyID\": \"\",\n            \"AppleTeamID\": \"\"\n        }\n    ],\n    \"AndroidPushSettings\": [\n        {\n            \"Type\":\"android\",\n            \"ServiceFileLocation\":\"\"\n        },\n        {\n            \"Type\":\"android_rn\",\n            \"ServiceFileLocation\":\"\"\n        }\n    ],\n    \"EnableConsoleLog\": true,\n    \"EnableFileLog\": false,\n    \"LogFileLocation\": \"\",\n    \"LogFormat\": \"plain\"\n}\n"
  },
  {
    "path": "docker/Dockerfile",
    "content": "FROM --platform=${TARGETPLATFORM} ubuntu:noble-20250529 AS builder\nARG TARGETARCH\n\n# Setting bash as our shell, and enabling pipefail option\nSHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\n\n# Copying tarball\nWORKDIR /mattermost-push-proxy\nCOPY dist/mattermost-push-proxy-linux-${TARGETARCH}.tar.gz /mattermost-push-proxy-linux.tar.gz\nRUN tar -xf /mattermost-push-proxy-linux.tar.gz --strip-components=1  -C /mattermost-push-proxy\n\nFROM --platform=${TARGETPLATFORM} ubuntu:noble-20250529\n\n# Install needed packages and indirect dependencies\n# hadolint ignore=DL3008\nRUN apt-get update \\\n  && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \\\n  ca-certificates \\\n  libffi-dev \\\n  netcat-openbsd \\\n  tzdata \\\n  && rm -rf /var/lib/apt/lists/*\n\n# Coyping needed files from previous stage\nCOPY --from=builder /mattermost-push-proxy/ /mattermost-push-proxy/\nCOPY docker/entrypoint /usr/local/bin/\n\nRUN chown -R nobody:nogroup /mattermost-push-proxy\n\nUSER nobody\nWORKDIR /mattermost-push-proxy\nENV PUSH_PROXY=/mattermost-push-proxy/bin/mattermost-push-proxy\n\nEXPOSE 8066\nVOLUME [\"/mattermost-push-proxy/config\", \"/mattermost-push-proxy/certs\"]\n\nENTRYPOINT [\"/usr/local/bin/entrypoint\"]\n"
  },
  {
    "path": "docker/Dockerfile.fips",
    "content": "# Build the mattermost-push-proxy (FIPS version)\nARG BUILD_IMAGE=cgr.dev/mattermost.com/go-msft-fips:1.24.6\nARG BASE_IMAGE=cgr.dev/mattermost.com/glibc-openssl-fips:15.2\n\nFROM ${BUILD_IMAGE} AS builder\n\nARG TARGETARCH\nARG TARGETOS\n\nWORKDIR /workspace\nCOPY . .\n\n# Copy config file to workspace for later use\nCOPY config/mattermost-push-proxy.sample.json /workspace/mattermost-push-proxy.json\n\n# Build with FIPS-compliant settings\nENV CGO_ENABLED=1\nENV GOFIPS=1\nENV GOEXPERIMENT=systemcrypto\nRUN make _build-fips-internal TARGET_OS=$TARGETOS TARGET_ARCH=$TARGETARCH\n\nFROM ${BASE_IMAGE}\n\nLABEL name=\"Mattermost Push Proxy (FIPS)\" \\\n  maintainer=\"sre@mattermost.com\" \\\n  vendor=\"Mattermost\" \\\n  distribution-scope=\"public\" \\\n  url=\"https://mattermost.dev\" \\\n  io.k8s.description=\"Mattermost Push Proxy service for FIPS-compliant environments\" \\\n  io.k8s.display-name=\"Mattermost Push Proxy (FIPS)\" \\\n  io.openshift.tags=\"mattermost,push-proxy,fips\" \\\n  summary=\"FIPS-compliant Mattermost Push Proxy service\" \\\n  description=\"Mattermost Push Proxy service for FIPS-compliant environments. This is a FIPS-compliant version.\" \\\n  com.mattermost.fips=\"true\" \\\n  com.mattermost.fips.version=\"140-2\"\n\nWORKDIR /mattermost-push-proxy\n\n    # Copy files - directories will be created automatically\n    COPY --from=builder /workspace/dist/mattermost-push-proxy /mattermost-push-proxy/bin/mattermost-push-proxy\n    COPY --from=builder /workspace/mattermost-push-proxy.json /mattermost-push-proxy/config/mattermost-push-proxy.json\n\nUSER 65532\n\nEXPOSE 8066\nVOLUME [\"/mattermost-push-proxy/config\", \"/mattermost-push-proxy/certs\"]\n\n# Use the binary from bin directory\nENTRYPOINT [\"/mattermost-push-proxy/bin/mattermost-push-proxy\"]\n"
  },
  {
    "path": "docker/entrypoint",
    "content": "#!/bin/sh -e\n\nexec ${PUSH_PROXY} $@\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/mattermost/mattermost-push-proxy\n\ngo 1.24.6\n\nrequire (\n\tfirebase.google.com/go/v4 v4.18.0\n\tgithub.com/gorilla/handlers v1.5.2\n\tgithub.com/gorilla/mux v1.8.1\n\tgithub.com/kyokomi/emoji v2.2.4+incompatible\n\tgithub.com/mattermost/mattermost/server/public v0.1.16\n\tgithub.com/prometheus/client_golang v1.23.0\n\tgithub.com/prometheus/common v0.65.0\n\tgithub.com/sideshow/apns2 v0.25.0\n\tgithub.com/stretchr/testify v1.10.0\n\tgolang.org/x/net v0.43.0\n\tgolang.org/x/oauth2 v0.30.0\n\tgoogle.golang.org/api v0.248.0\n\tgopkg.in/throttled/throttled.v1 v1.0.0\n)\n\nrequire (\n\tcel.dev/expr v0.24.0 // indirect\n\tcloud.google.com/go v0.121.6 // indirect\n\tcloud.google.com/go/auth v0.16.5 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.8.0 // indirect\n\tcloud.google.com/go/firestore v1.18.0 // indirect\n\tcloud.google.com/go/iam v1.5.2 // indirect\n\tcloud.google.com/go/longrunning v0.6.7 // indirect\n\tcloud.google.com/go/monitoring v1.24.2 // indirect\n\tcloud.google.com/go/storage v1.56.1 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect\n\tgithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect\n\tgithub.com/MicahParks/keyfunc v1.9.0 // indirect\n\tgithub.com/PuerkitoBio/boom v0.0.0-20140219125548-fecdef1c97ca // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect\n\tgithub.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/francoispqt/gojay v1.2.13 // indirect\n\tgithub.com/garyburd/redigo v1.6.4 // indirect\n\tgithub.com/go-jose/go-jose/v4 v4.1.2 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgithub.com/mattermost/logr/v2 v2.0.22 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // 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/procfs v0.17.0 // indirect\n\tgithub.com/rakyll/pb v0.0.0-20160123035540-8d46b8b097ef // indirect\n\tgithub.com/spiffe/go-spiffe/v2 v2.5.0 // indirect\n\tgithub.com/wiggin77/merror v1.0.5 // indirect\n\tgithub.com/wiggin77/srslog v1.0.1 // indirect\n\tgithub.com/zeebo/errs v1.4.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/detectors/gcp v1.37.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect\n\tgo.opentelemetry.io/otel v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.37.0 // indirect\n\tgolang.org/x/crypto v0.41.0 // indirect\n\tgolang.org/x/sync v0.16.0 // indirect\n\tgolang.org/x/sys v0.35.0 // indirect\n\tgolang.org/x/text v0.28.0 // indirect\n\tgolang.org/x/time v0.12.0 // indirect\n\tgoogle.golang.org/appengine/v2 v2.0.6 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20250818200422-3122310a409c // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect\n\tgoogle.golang.org/grpc v1.75.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.7 // indirect\n\tgopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=\ncel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo=\ncloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=\ncloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=\ncloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=\ncloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=\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.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA=\ncloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw=\ncloud.google.com/go/firestore v1.18.0 h1:cuydCaLS7Vl2SatAeivXyhbhDEIR8BDmtn4egDhIn2s=\ncloud.google.com/go/firestore v1.18.0/go.mod h1:5ye0v48PhseZBdcl0qbl3uttu7FIEwEYVaWm0UIEOEU=\ncloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=\ncloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=\ncloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=\ncloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=\ncloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=\ncloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=\ncloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=\ncloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=\ncloud.google.com/go/storage v1.56.1 h1:n6gy+yLnHn0hTwBFzNn8zJ1kqWfR91wzdM8hjRF4wP0=\ncloud.google.com/go/storage v1.56.1/go.mod h1:C9xuCZgFl3buo2HZU/1FncgvvOgTAs/rnh4gF4lMg0s=\ncloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=\ncloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=\ndmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU=\ndmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU=\ndmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4=\ndmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=\nfirebase.google.com/go/v4 v4.18.0 h1:S+g0P72oDGqOaG4wlLErX3zQmU9plVdu7j+Bc3R1qFw=\nfirebase.google.com/go/v4 v4.18.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs=\ngit.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ=\ngithub.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo=\ngithub.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=\ngithub.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=\ngithub.com/PuerkitoBio/boom v0.0.0-20140219125548-fecdef1c97ca h1:jv7AlMqwTYg92zzES80+2pXD0bPY5kGT3AhFKdXLLdI=\ngithub.com/PuerkitoBio/boom v0.0.0-20140219125548-fecdef1c97ca/go.mod h1:BUNf81ELJpN4dRN2LS5r1fkTSLkczJubIXjJv04ib70=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=\ngithub.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=\ngithub.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=\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/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=\ngithub.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=\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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=\ngithub.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=\ngithub.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=\ngithub.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=\ngithub.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=\ngithub.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=\ngithub.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=\ngithub.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=\ngithub.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=\ngithub.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/garyburd/redigo v1.6.4 h1:LFu2R3+ZOPgSMWMOL+saa/zXRjw0ID2G8FepO53BGlg=\ngithub.com/garyburd/redigo v1.6.4/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=\ngithub.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=\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/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\ngithub.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\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/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=\ngithub.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=\ngithub.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=\ngithub.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=\ngithub.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=\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.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=\ngithub.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\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/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=\ngithub.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=\ngithub.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=\ngithub.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\ngithub.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=\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/pty v1.1.3/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/kyokomi/emoji v2.2.4+incompatible h1:np0woGKwx9LiHAQmwZx79Oc0rHpNw3o+3evou4BEPv4=\ngithub.com/kyokomi/emoji v2.2.4+incompatible/go.mod h1:mZ6aGCD7yk8j6QY6KICwnZ2pxoszVseX1DNoGtU2tBA=\ngithub.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=\ngithub.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mattermost/logr/v2 v2.0.22 h1:npFkXlkAWR9J8payh8ftPcCZvLbHSI125mAM5/r/lP4=\ngithub.com/mattermost/logr/v2 v2.0.22/go.mod h1:0sUKpO+XNMZApeumaid7PYaUZPBIydfuWZ0dqixXo+s=\ngithub.com/mattermost/mattermost/server/public v0.1.16 h1:CtTzNnuFCNndUaNfQse5NnvzfBZt+8P+wOU7OMn07zg=\ngithub.com/mattermost/mattermost/server/public v0.1.16/go.mod h1:hvxMXqfao9JDHM3auk8MLl7DD6jAuG0q27Kf9y6z1r0=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=\ngithub.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\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/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=\ngithub.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=\ngithub.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0 h1:LiZB1h0GIcudcDci2bxbqI6DXV8bF8POAnArqvRrIyw=\ngithub.com/olekukonko/ts v0.0.0-20171002115256-78ecb04241c0/go.mod h1:F/7q8/HZz+TXjlsoZQQKVYvXTZaFH4QRa3y+j1p7MS0=\ngithub.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=\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/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\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/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=\ngithub.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=\ngithub.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=\ngithub.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=\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.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=\ngithub.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=\ngithub.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=\ngithub.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=\ngithub.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=\ngithub.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=\ngithub.com/rakyll/pb v0.0.0-20160123035540-8d46b8b097ef h1:EzrUZp6kh2we8o30Qhx5SJxg7jaiVwlq3WaecTc4BOo=\ngithub.com/rakyll/pb v0.0.0-20160123035540-8d46b8b097ef/go.mod h1:6Wyuoi/kwXz73J3mwjVSCvIr9QprJ7Q9GrogPi1QZ+8=\ngithub.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=\ngithub.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=\ngithub.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=\ngithub.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=\ngithub.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=\ngithub.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM=\ngithub.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=\ngithub.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=\ngithub.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=\ngithub.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw=\ngithub.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI=\ngithub.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=\ngithub.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=\ngithub.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg=\ngithub.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw=\ngithub.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y=\ngithub.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ=\ngithub.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I=\ngithub.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0=\ngithub.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=\ngithub.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk=\ngithub.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4=\ngithub.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=\ngithub.com/sideshow/apns2 v0.25.0 h1:XOzanncO9MQxkb03T/2uU2KcdVjYiIf0TMLzec0FTW4=\ngithub.com/sideshow/apns2 v0.25.0/go.mod h1:7Fceu+sL0XscxrfLSkAoH6UtvKefq3Kq1n4W3ayQZqE=\ngithub.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=\ngithub.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=\ngithub.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=\ngithub.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=\ngithub.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=\ngithub.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=\ngithub.com/wiggin77/merror v1.0.5 h1:P+lzicsn4vPMycAf2mFf7Zk6G9eco5N+jB1qJ2XW3ME=\ngithub.com/wiggin77/merror v1.0.5/go.mod h1:H2ETSu7/bPE0Ymf4bEwdUoo73OOEkdClnoRisfw0Nm0=\ngithub.com/wiggin77/srslog v1.0.1 h1:gA2XjSMy3DrRdX9UqLuDtuVAAshb8bE1NhX1YK0Qe+8=\ngithub.com/wiggin77/srslog v1.0.1/go.mod h1:fehkyYDq1QfuYn60TDPu9YdY2bB85VUW2mvN1WynEls=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngithub.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=\ngithub.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=\ngo.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9plR/HNjgGBrfcOlweA=\ngo.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=\ngo.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=\ngo.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=\ngo.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=\ngo.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=\ngo.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=\ngo.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=\ngo.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=\ngo.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=\ngo.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=\ngo.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=\ngo.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=\ngolang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=\ngolang.org/x/crypto v0.0.0-20170512130425-ab89591268e0/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=\ngolang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190313220215-9f648a60d977/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=\ngolang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\ngolang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=\ngolang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=\ngolang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=\ngolang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=\ngolang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=\ngolang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=\ngolang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=\ngoogle.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y=\ngoogle.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y=\ngoogle.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=\ngoogle.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg=\ngoogle.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=\ngoogle.golang.org/genproto v0.0.0-20250818200422-3122310a409c h1:ZERoum3uuqL0PRSc6SXielu26FN96T4BUGaaW0oL+c8=\ngoogle.golang.org/genproto v0.0.0-20250818200422-3122310a409c/go.mod h1:Q8kep885BJnK3Jt6QZXIFeLHSzoAQtlI1CCloQigiyU=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=\ngoogle.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=\ngoogle.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=\ngoogle.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4=\ngoogle.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngoogle.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=\ngoogle.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=\ngopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=\ngopkg.in/throttled/throttled.v1 v1.0.0 h1:HW4VuZPcA2x88dJSf3T7GLTOwYCrdQcYDEC65ZEX2mQ=\ngopkg.in/throttled/throttled.v1 v1.0.0/go.mod h1:UIVpydfpoUKqbHErIHUoEnuOj9KVPAmS88/iAIqBScE=\ngopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\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=\ngrpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o=\nhonnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nsourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=\nsourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=\n"
  },
  {
    "path": "internal/version/version.go",
    "content": "package version\n\nimport (\n\t\"fmt\"\n\t\"runtime\"\n\t\"strings\"\n\t\"text/tabwriter\"\n)\n\n// Base version information.\n//\n// This is the fallback data used when version information from git is not\n// provided via go ldflags (e.g. via Makefile).\nvar (\n\t// Output of \"git describe\". The prerequisite is that the branch should be\n\t// tagged using the correct versioning strategy.\n\tgitVersion string = \"devel\"\n\t// short SHA1 from git, output of $(git rev-parse --short HEAD)\n\tbuildHash = \"unknown\"\n\t// the most recent v* tag in the current branch (or its ancestors)\n\tbuildTagLatest = \"unknown\"\n\t// the current commit's v* tag\n\tbuildTagCurrent = \"unknown\"\n\t// State of git tree, either \"clean\" or \"dirty\"\n\tgitTreeState = \"unknown\"\n\t// Build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ')\n\tbuildDate = \"unknown\"\n)\n\nfunc GetVersion() error {\n\tv := VersionInfo()\n\tres := v.String()\n\n\tfmt.Println(res)\n\treturn nil\n}\n\ntype Info struct {\n\tGitVersion   string `json:\"-\"`\n\tBuildHash    string `json:\"hash\"`\n\tBuildVersion string `json:\"version\"`\n\tGitTreeState string `json:\"-\"`\n\tBuildDate    string `json:\"-\"`\n\tGoVersion    string `json:\"-\"`\n\tCompiler     string `json:\"-\"`\n\tPlatform     string `json:\"-\"`\n}\n\nfunc VersionInfo() Info {\n\t// These variables typically come from -ldflags settings and in\n\t// their absence fallback to the global defaults set above.\n\n\t// Create the semver version based on the state of the current commit or its branch.\n\t// Use the first version we find.\n\tvar version string\n\ttags := strings.Fields(buildTagCurrent)\n\tfor _, t := range tags {\n\t\tif strings.HasPrefix(t, \"v\") {\n\t\t\tversion = t\n\t\t\tbreak\n\t\t}\n\t}\n\tif version == \"\" {\n\t\tversion = buildTagLatest + \"+\" + buildHash\n\t}\n\tversion = strings.TrimPrefix(version, \"v\")\n\n\treturn Info{\n\t\tGitVersion:   gitVersion,\n\t\tBuildHash:    buildHash,\n\t\tBuildVersion: version,\n\t\tGitTreeState: gitTreeState,\n\t\tBuildDate:    buildDate,\n\t\tGoVersion:    runtime.Version(),\n\t\tCompiler:     runtime.Compiler,\n\t\tPlatform:     fmt.Sprintf(\"%s/%s\", runtime.GOOS, runtime.GOARCH),\n\t}\n}\n\n// String returns the string representation of the version info\nfunc (i *Info) String() string {\n\tb := strings.Builder{}\n\tw := tabwriter.NewWriter(&b, 0, 0, 2, ' ', 0)\n\n\tfmt.Fprintf(w, \"GitVersion:\\t%s\\n\", i.GitVersion)\n\tfmt.Fprintf(w, \"BuildHash:\\t%s\\n\", i.BuildHash)\n\tfmt.Fprintf(w, \"BuildVersion:\\t%s\\n\", i.BuildVersion)\n\tfmt.Fprintf(w, \"GitTreeState:\\t%s\\n\", i.GitTreeState)\n\tfmt.Fprintf(w, \"BuildDate:\\t%s\\n\", i.BuildDate)\n\tfmt.Fprintf(w, \"GoVersion:\\t%s\\n\", i.GoVersion)\n\tfmt.Fprintf(w, \"Compiler:\\t%s\\n\", i.Compiler)\n\tfmt.Fprintf(w, \"Platform:\\t%s\\n\", i.Platform)\n\n\tw.Flush() // #nosec\n\treturn b.String()\n}\n"
  },
  {
    "path": "main.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\n\t\"github.com/mattermost/mattermost-push-proxy/internal/version\"\n\t\"github.com/mattermost/mattermost-push-proxy/server\"\n)\n\nvar (\n\tflagConfigFile string\n\tfalgVersion    bool\n)\n\nfunc main() {\n\tflag.StringVar(&flagConfigFile, \"config\", \"mattermost-push-proxy.json\", \"\")\n\tflag.BoolVar(&falgVersion, \"version\", false, \"\")\n\tflag.Parse()\n\n\tif falgVersion {\n\t\tif err := version.GetVersion(); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\n\t\tos.Exit(0)\n\t}\n\n\tfileName := server.FindConfigFile(flagConfigFile)\n\tcfg, err := server.LoadConfig(fileName)\n\tif err != nil {\n\t\t// We just do a hard exit, because the app won't be able to start without a config.\n\t\tlog.Fatal(err)\n\t}\n\n\tlogger, err := server.NewLogger(cfg)\n\tdefer func() {\n\t\tif logger != nil {\n\t\t\t_ = logger.Shutdown()\n\t\t}\n\t}()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tlogger.Info(\"Loading \" + fileName)\n\n\tsrv := server.New(cfg, logger)\n\tsrv.Start()\n\n\t// wait for kill signal before attempting to gracefully shutdown\n\t// the running service\n\tstopChan := make(chan os.Signal, 1)\n\tsignal.Notify(stopChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)\n\t<-stopChan\n\n\tsrv.Stop()\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mattermost-push-proxy\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Repository for mattermost push proxy\",\n  \"main\": \"\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\",\n    \"validate\": \"swagger-cli validate ./swagger/spec.yaml\",\n    \"build\": \"redoc-cli bundle -t ./swagger/template.hbs ./swagger/spec.yaml -o index.html --options.suppressWarnings\",\n    \"serve\": \"redoc-cli serve -t ./swagger/template.hbs ./swagger/spec.yaml -o index.html --options.suppressWarnings --ssr --watch\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mattermost/mattermost-push-proxy.git\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mattermost/mattermost-push-proxy/issues\"\n  },\n  \"homepage\": \"https://github.com/mattermost/mattermost-push-proxy#readme\",\n  \"dependencies\": {\n    \"redoc-cli\": \"^0.9.9\",\n    \"swagger-cli\": \"^4.0.4\"\n  }\n}\n"
  },
  {
    "path": "scripts/go_install.sh",
    "content": "#!/usr/bin/env bash\n\n# Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.\n# See LICENSE.txt for license information.\n\nset -o errexit\nset -o nounset\nset -o pipefail\n\nif [ -z \"${1}\" ]; then\n  echo \"must provide module as first parameter\"\n  exit 1\nfi\n\nif [ -z \"${2}\" ]; then\n  echo \"must provide binary name as second parameter\"\n  exit 1\nfi\n\nif [ -z \"${3}\" ]; then\n  echo \"must provide version as third parameter\"\n  exit 1\nfi\n\nif [ -z \"${GOBIN}\" ]; then\n  echo \"GOBIN is not set. Must set GOBIN to install the bin in a specified directory.\"\n  exit 1\nfi\n\ntmp_dir=$(mktemp -d -t goinstall_XXXXXXXXXX)\nfunction clean {\n  rm -rf \"${tmp_dir}\"\n}\ntrap clean EXIT\n\nrm \"${GOBIN}/${2}\"* || true\n\ncd \"${tmp_dir}\"\n\n# create a new module in the tmp directory\ngo mod init fake/mod\n\n# install the golang module specified as the first argument\ngo install -tags tools \"${1}@${3}\"\nmv \"${GOBIN}/${2}\" \"${GOBIN}/${2}-${3}\"\nln -sf \"${GOBIN}/${2}-${3}\" \"${GOBIN}/${2}\"\n"
  },
  {
    "path": "server/android_notification_server.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"time\"\n\n\tfirebase \"firebase.google.com/go/v4\"\n\t\"firebase.google.com/go/v4/messaging\"\n\t\"github.com/kyokomi/emoji\"\n\t\"golang.org/x/oauth2/google\"\n\t\"google.golang.org/api/option\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tapnsAuthError       = \"APNS_AUTH_ERROR\"\n\tinternalError       = \"INTERNAL\"\n\tthirdPartyAuthError = \"THIRD_PARTY_AUTH_ERROR\"\n\tinvalidArgument     = \"INVALID_ARGUMENT\"\n\tquotaExceeded       = \"QUOTA_EXCEEDED\"\n\tunregistered        = \"UNREGISTERED\"\n\tunavailable         = \"UNAVAILABLE\"\n\ttokenSourceError    = \"TOKEN_SOURCE_ERROR\"\n)\n\nconst (\n\tscope = \"https://www.googleapis.com/auth/firebase.messaging\"\n)\n\ntype AndroidNotificationServer struct {\n\tmetrics             *metrics\n\tlogger              *mlog.Logger\n\tAndroidPushSettings AndroidPushSettings\n\tclient              *messaging.Client\n\tsendTimeout         time.Duration\n\tretryTimeout        time.Duration\n}\n\n// serviceAccount contains a subset of the fields in service-account.json.\n// It is mainly used to extract the projectID and client email for authentication.\ntype serviceAccount struct {\n\tType        string `json:\"type\"`\n\tProjectID   string `json:\"project_id\"`\n\tClientEmail string `json:\"client_email\"`\n\tClientID    string `json:\"client_id\"`\n\tAuthURI     string `json:\"auth_uri\"`\n\tTokenURI    string `json:\"token_uri\"`\n}\n\nfunc NewAndroidNotificationServer(settings AndroidPushSettings, logger *mlog.Logger, metrics *metrics, sendTimeoutSecs int, retryTimeoutSecs int) *AndroidNotificationServer {\n\treturn &AndroidNotificationServer{\n\t\tAndroidPushSettings: settings,\n\t\tmetrics:             metrics,\n\t\tlogger:              logger,\n\t\tsendTimeout:         time.Duration(sendTimeoutSecs) * time.Second,\n\t\tretryTimeout:        time.Duration(retryTimeoutSecs) * time.Second,\n\t}\n}\n\nfunc (me *AndroidNotificationServer) Initialize() error {\n\tme.logger.Info(\"Initializing Android notification server\", mlog.String(\"type\", me.AndroidPushSettings.Type))\n\n\tif me.AndroidPushSettings.AndroidAPIKey != \"\" {\n\t\tme.logger.Info(\"AndroidPushSettings.AndroidAPIKey is no longer used. Please remove this config value.\")\n\t}\n\n\tif me.AndroidPushSettings.ServiceFileLocation == \"\" {\n\t\treturn errors.New(\"Android push notifications not configured.  Missing ServiceFileLocation.\")\n\t}\n\n\tjsonKey, err := os.ReadFile(me.AndroidPushSettings.ServiceFileLocation)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error reading service file: %v\", err)\n\t}\n\n\tcfg, err := google.JWTConfigFromJSON(jsonKey, scope)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting JWT config: %v\", err)\n\t}\n\n\tvar serviceAcc serviceAccount\n\terr = json.Unmarshal(jsonKey, &serviceAcc)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error parsing service account JSON: %v\", err)\n\t}\n\n\topt := option.WithTokenSource(cfg.TokenSource(context.Background()))\n\tconf := &firebase.Config{\n\t\tProjectID:        serviceAcc.ProjectID,\n\t\tServiceAccountID: serviceAcc.ClientEmail,\n\t}\n\tapp, err := firebase.NewApp(context.Background(), conf, opt)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error initializing app: %v\", err)\n\t}\n\n\tclient, err := app.Messaging(context.Background())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error initializing client: %v\", err)\n\t}\n\tme.client = client\n\n\treturn nil\n}\n\nfunc (me *AndroidNotificationServer) SendNotification(msg *PushNotification) PushResponse {\n\tpushType := msg.Type\n\tdata := map[string]string{\n\t\t\"ack_id\":         msg.AckID,\n\t\t\"type\":           pushType,\n\t\t\"sub_type\":       msg.SubType,\n\t\t\"version\":        msg.Version,\n\t\t\"channel_id\":     msg.ChannelID,\n\t\t\"is_crt_enabled\": strconv.FormatBool(msg.IsCRTEnabled),\n\t\t\"server_id\":      msg.ServerID,\n\t\t\"category\":       msg.Category,\n\t}\n\n\tif msg.Badge != -1 {\n\t\tdata[\"badge\"] = strconv.Itoa(msg.Badge)\n\t}\n\n\tif msg.RootID != \"\" {\n\t\tdata[\"root_id\"] = msg.RootID\n\t}\n\n\tif msg.Signature == \"\" {\n\t\tdata[\"signature\"] = \"NO_SIGNATURE\"\n\t} else {\n\t\tdata[\"signature\"] = msg.Signature\n\t}\n\n\tif msg.IsIDLoaded {\n\t\tdata[\"post_id\"] = msg.PostID\n\t\tdata[\"message\"] = msg.Message\n\t\tdata[\"id_loaded\"] = \"true\"\n\t\tdata[\"sender_id\"] = msg.SenderID\n\t\tdata[\"sender_name\"] = \"Someone\"\n\t\tdata[\"team_id\"] = msg.TeamID\n\t} else if pushType == PushTypeMessage || pushType == PushTypeSession {\n\t\tdata[\"team_id\"] = msg.TeamID\n\t\tdata[\"sender_id\"] = msg.SenderID\n\t\tdata[\"sender_name\"] = msg.SenderName\n\t\tdata[\"message\"] = emoji.Sprint(msg.Message)\n\t\tdata[\"channel_name\"] = msg.ChannelName\n\t\tdata[\"post_id\"] = msg.PostID\n\t\tdata[\"override_username\"] = msg.OverrideUsername\n\t\tdata[\"override_icon_url\"] = msg.OverrideIconURL\n\t\tdata[\"from_webhook\"] = msg.FromWebhook\n\t}\n\n\tif me.metrics != nil {\n\t\tme.metrics.incrementNotificationTotal(PushNotifyAndroid, pushType)\n\t}\n\tfcmMsg := &messaging.Message{\n\t\tToken: msg.DeviceID,\n\t\tData:  data,\n\t\tAndroid: &messaging.AndroidConfig{\n\t\t\tPriority: \"high\",\n\t\t},\n\t}\n\n\tme.logger.Info(\n\t\t\"Sending android push notification\",\n\t\tmlog.String(\"device\", me.AndroidPushSettings.Type),\n\t\tmlog.String(\"type\", msg.Type),\n\t\tmlog.String(\"ack_id\", msg.AckID),\n\t)\n\terr := me.SendNotificationWithRetry(fcmMsg)\n\n\tif err != nil {\n\t\terrorCode, hasStatusCode := getErrorCode(err)\n\t\tif !hasStatusCode {\n\t\t\terrorCode = \"NONE\"\n\t\t}\n\n\t\tme.logger.Error(\n\t\t\t\"Failed to send FCM push\",\n\t\t\tmlog.String(\"sid\", msg.ServerID),\n\t\t\tmlog.String(\"did\", msg.DeviceID),\n\t\t\tmlog.Err(err),\n\t\t\tmlog.String(\"type\", me.AndroidPushSettings.Type),\n\t\t\tmlog.String(\"errorCode\", errorCode),\n\t\t)\n\n\t\tif messaging.IsUnregistered(err) || messaging.IsSenderIDMismatch(err) {\n\t\t\tme.logger.Info(\"Android response failure sending remove code\", mlog.String(\"type\", me.AndroidPushSettings.Type))\n\t\t\tif me.metrics != nil {\n\t\t\t\tme.metrics.incrementRemoval(PushNotifyAndroid, pushType, unregistered)\n\t\t\t}\n\t\t\treturn NewRemovePushResponse()\n\t\t}\n\n\t\tvar reason string\n\t\tswitch {\n\t\tcase messaging.IsInternal(err):\n\t\t\treason = internalError\n\t\tcase messaging.IsInvalidArgument(err):\n\t\t\treason = invalidArgument\n\t\tcase messaging.IsQuotaExceeded(err):\n\t\t\treason = quotaExceeded\n\t\tcase messaging.IsThirdPartyAuthError(err):\n\t\t\treason = thirdPartyAuthError\n\t\tcase messaging.IsUnavailable(err):\n\t\t\treason = unavailable\n\t\tdefault:\n\t\t\treason = \"unknown transport error\"\n\n\t\t}\n\t\tif me.metrics != nil {\n\t\t\tme.metrics.incrementFailure(PushNotifyAndroid, pushType, reason)\n\t\t}\n\n\t\treturn NewErrorPushResponse(err.Error())\n\t}\n\n\tif me.metrics != nil {\n\t\tif msg.AckID != \"\" {\n\t\t\tme.metrics.incrementSuccessWithAck(PushNotifyAndroid, pushType)\n\t\t} else {\n\t\t\tme.metrics.incrementSuccess(PushNotifyAndroid, pushType)\n\t\t}\n\t}\n\treturn NewOkPushResponse()\n}\n\nfunc (me *AndroidNotificationServer) SendNotificationWithRetry(fcmMsg *messaging.Message) error {\n\tvar err error\n\twaitTime := time.Second\n\n\tlogger := me.logger.With(mlog.String(\"did\", fcmMsg.Token))\n\n\t// Keep a general context to make sure the whole retry\n\t// doesn't take longer than the timeout.\n\tgeneralContext, cancelGeneralContext := context.WithTimeout(context.Background(), me.sendTimeout)\n\tdefer cancelGeneralContext()\n\n\tfor retries := 0; retries < MAX_RETRIES; retries++ {\n\t\tstart := time.Now()\n\n\t\tretryContext, cancelRetryContext := context.WithTimeout(generalContext, me.retryTimeout)\n\t\tdefer cancelRetryContext()\n\t\t_, err = me.client.Send(retryContext, fcmMsg)\n\t\tif me.metrics != nil {\n\t\t\tme.metrics.observerNotificationResponse(PushNotifyAndroid, time.Since(start).Seconds())\n\t\t}\n\n\t\tif err == nil || !isRetryable(err) {\n\t\t\tbreak\n\t\t}\n\n\t\tlogger.Error(\n\t\t\t\"Failed to send android push\",\n\t\t\tmlog.Int(\"retry\", retries),\n\t\t\tmlog.Err(err),\n\t\t)\n\n\t\tif retries == MAX_RETRIES-1 {\n\t\t\tlogger.Error(\"Max retries reached\")\n\t\t\tbreak\n\t\t}\n\n\t\tselect {\n\t\tcase <-generalContext.Done():\n\t\t\tif generalContext.Err() != nil {\n\t\t\t\tlogger.Info(\n\t\t\t\t\t\"Not retrying because context error\",\n\t\t\t\t\tmlog.Int(\"retry\", retries),\n\t\t\t\t\tmlog.Err(generalContext.Err()),\n\t\t\t\t)\n\t\t\t}\n\t\t\treturn generalContext.Err()\n\t\tcase <-time.After(waitTime):\n\t\t}\n\n\t\twaitTime *= 2\n\t}\n\n\treturn err\n}\n\nfunc isRetryable(err error) bool {\n\t// We retry if the context deadline is exceeded.\n\t// This may cause double notifications, but we expect\n\t// this not to happen often.\n\tif errors.Is(err, context.DeadlineExceeded) {\n\t\treturn true\n\t}\n\n\t// We retry the errors based on https://firebase.google.com/docs/cloud-messaging/http-server-ref\n\treturn messaging.IsInternal(err) ||\n\t\tmessaging.IsQuotaExceeded(err)\n\n\t// messaging.IsUnavailable is retried by the default retry config in\n\t// firebase.google.com/go/v4@v4.14.0/internal/http_client.go\n\t// messaging.IsUnavailable(err)\n}\n\nfunc getErrorCode(err error) (string, bool) {\n\tif err == nil {\n\t\treturn \"\", false\n\t}\n\n\terrorPointer := reflect.ValueOf(err)\n\tif errorPointer.Kind() != reflect.Ptr {\n\t\treturn \"\", false\n\t}\n\n\terrorValue := errorPointer.Elem()\n\tif errorValue.Kind() != reflect.Struct {\n\t\treturn \"\", false\n\t}\n\n\tcodeValue := errorValue.FieldByName(\"ErrorCode\")\n\tif !codeValue.IsValid() {\n\t\treturn \"\", false\n\t}\n\n\treturn codeValue.String(), true\n}\n"
  },
  {
    "path": "server/android_notification_test.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc TestAndroidInitialize(t *testing.T) {\n\tfileName := FindConfigFile(\"mattermost-push-proxy.sample.json\")\n\tcfg, err := LoadConfig(fileName)\n\trequire.NoError(t, err)\n\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\n\t// Verify error for no service file\n\tpushSettings := AndroidPushSettings{}\n\tcfg.AndroidPushSettings[0] = pushSettings\n\trequire.Error(t, NewAndroidNotificationServer(cfg.AndroidPushSettings[0], logger, nil, cfg.SendTimeoutSec, cfg.RetryTimeoutSec).Initialize())\n\n\tf, err := os.CreateTemp(\"\", \"example\")\n\trequire.NoError(t, err)\n\tdefer os.Remove(f.Name()) // clean up\n\n\tcfg.AndroidPushSettings[0].ServiceFileLocation = f.Name()\n\n\t// Verify error for bad JSON\n\t_, err = f.Write([]byte(\"badJSON\"))\n\trequire.NoError(t, err)\n\trequire.Error(t, NewAndroidNotificationServer(cfg.AndroidPushSettings[0], logger, nil, cfg.SendTimeoutSec, cfg.RetryTimeoutSec).Initialize())\n\n\trequire.NoError(t, f.Truncate(0))\n\t_, err = f.Seek(0, 0)\n\trequire.NoError(t, err)\n\n\t// Verify no error for dummy JSON\n\trequire.NoError(t, json.NewEncoder(f).Encode(serviceAccount{\n\t\tType:      \"service_account\",\n\t\tProjectID: \"sample\",\n\t}))\n\trequire.NoError(t, f.Sync())\n\trequire.NoError(t, NewAndroidNotificationServer(cfg.AndroidPushSettings[0], logger, nil, cfg.SendTimeoutSec, cfg.RetryTimeoutSec).Initialize())\n\n\trequire.NoError(t, f.Close())\n}\n\n// Copied from firebase.google.com/go/v4@v4.14.0/internal/errors.go\ntype ErrorCode string\ntype FirebaseError struct {\n\tErrorCode ErrorCode\n\tString    string\n\tResponse  *http.Response\n\tExt       map[string]interface{}\n}\n\nfunc (fe *FirebaseError) Error() string {\n\treturn fe.String\n}\n\nfunc TestGetErrorCode(t *testing.T) {\n\tvar errorCode ErrorCode = \"some error code\"\n\terr := &FirebaseError{\n\t\tErrorCode: errorCode,\n\t}\n\n\textractedCode, found := getErrorCode(err)\n\trequire.True(t, found)\n\trequire.Equal(t, string(errorCode), extractedCode)\n\n\textractedCode, found = getErrorCode(errors.New(\"non firebase error\"))\n\trequire.Equal(t, \"\", extractedCode)\n\trequire.False(t, found)\n}\n"
  },
  {
    "path": "server/apple_notification_server.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/kyokomi/emoji\"\n\tapns \"github.com/sideshow/apns2\"\n\t\"github.com/sideshow/apns2/certificate\"\n\t\"github.com/sideshow/apns2/payload\"\n\t\"github.com/sideshow/apns2/token\"\n\t\"golang.org/x/net/http2\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\ntype AppleNotificationServer struct {\n\tAppleClient       *apns.Client\n\tmetrics           *metrics\n\tlogger            *mlog.Logger\n\tApplePushSettings ApplePushSettings\n\tsendTimeout       time.Duration\n\tretryTimeout      time.Duration\n}\n\nfunc NewAppleNotificationServer(settings ApplePushSettings, logger *mlog.Logger, metrics *metrics, sendTimeoutSecs int, retryTimeoutSecs int) *AppleNotificationServer {\n\treturn &AppleNotificationServer{\n\t\tApplePushSettings: settings,\n\t\tmetrics:           metrics,\n\t\tlogger:            logger,\n\t\tsendTimeout:       time.Duration(sendTimeoutSecs) * time.Second,\n\t\tretryTimeout:      time.Duration(retryTimeoutSecs) * time.Second,\n\t}\n}\n\nfunc (me *AppleNotificationServer) setupProxySettings(appleCert *tls.Certificate) error {\n\t// Override the native transport.\n\tproxyServer := getProxyServer()\n\tif proxyServer != \"\" {\n\t\ttransport := &http.Transport{\n\t\t\tProxy: func(request *http.Request) (*url.URL, error) {\n\t\t\t\treturn url.Parse(proxyServer)\n\t\t\t},\n\t\t\tIdleConnTimeout: apns.HTTPClientTimeout,\n\t\t}\n\n\t\tif appleCert != nil {\n\t\t\ttransport.TLSClientConfig = &tls.Config{\n\t\t\t\tCertificates: []tls.Certificate{*appleCert},\n\t\t\t}\n\t\t}\n\n\t\terr := http2.ConfigureTransport(transport)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Transport Error: %v\", err)\n\t\t}\n\n\t\tme.AppleClient.HTTPClient.Transport = transport\n\t}\n\n\tif appleCert != nil {\n\t\tme.logger.Info(\"Initializing apple notification server with PEM certificate\", mlog.String(\"type\", me.ApplePushSettings.Type))\n\t} else {\n\t\tme.logger.Info(\"Initializing apple notification server with AuthKey\", mlog.String(\"type\", me.ApplePushSettings.Type))\n\t}\n\n\treturn nil\n}\n\nfunc (me *AppleNotificationServer) Initialize() error {\n\tif me.ApplePushSettings.AppleAuthKeyFile != \"\" && me.ApplePushSettings.AppleAuthKeyID != \"\" && me.ApplePushSettings.AppleTeamID != \"\" {\n\t\tauthKey, err := token.AuthKeyFromFile(me.ApplePushSettings.AppleAuthKeyFile)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Failed to initialize apple notification service with AuthKey file err=%v \", err)\n\t\t}\n\n\t\tappleToken := &token.Token{\n\t\t\tAuthKey: authKey,\n\t\t\tKeyID:   me.ApplePushSettings.AppleAuthKeyID,\n\t\t\tTeamID:  me.ApplePushSettings.AppleTeamID,\n\t\t}\n\n\t\tif me.ApplePushSettings.ApplePushUseDevelopment {\n\t\t\tme.AppleClient = apns.NewTokenClient(appleToken).Development()\n\t\t} else {\n\t\t\tme.AppleClient = apns.NewTokenClient(appleToken).Production()\n\t\t}\n\n\t\t// Override the native transport.\n\t\treturn me.setupProxySettings(nil)\n\t}\n\n\tif me.ApplePushSettings.ApplePushCertPrivate != \"\" {\n\t\tappleCert, appleCertErr := certificate.FromPemFile(me.ApplePushSettings.ApplePushCertPrivate, me.ApplePushSettings.ApplePushCertPassword)\n\t\tif appleCertErr != nil {\n\t\t\treturn fmt.Errorf(\"Failed to initialize apple notification service with pem cert err=%v for type=%v\", appleCertErr, me.ApplePushSettings.Type)\n\t\t}\n\n\t\tif me.ApplePushSettings.ApplePushUseDevelopment {\n\t\t\tme.AppleClient = apns.NewClient(appleCert).Development()\n\t\t} else {\n\t\t\tme.AppleClient = apns.NewClient(appleCert).Production()\n\t\t}\n\n\t\t// Override the native transport.\n\t\treturn me.setupProxySettings(&appleCert)\n\t}\n\n\treturn fmt.Errorf(\"Apple push notifications not configured.  Missing ApplePushCertPrivate. for type=%v\", me.ApplePushSettings.Type)\n}\n\nfunc (me *AppleNotificationServer) SendNotification(msg *PushNotification) PushResponse {\n\n\tdata := payload.NewPayload()\n\tif msg.Badge == 0 && msg.Type == PushTypeClear && msg.AppVersion > 1 {\n\t\tdata.Badge(1)\n\t} else if msg.Badge != -1 {\n\t\tdata.Badge(msg.Badge)\n\t}\n\n\tnotification := &apns.Notification{}\n\tnotification.DeviceToken = msg.DeviceID\n\tnotification.Payload = data\n\tnotification.Topic = me.ApplePushSettings.ApplePushTopic\n\tnotification.Priority = apns.PriorityHigh\n\n\tvar pushType = msg.Type\n\tif msg.IsIDLoaded {\n\t\tdata.Category(msg.Category)\n\t\tdata.Sound(\"default\")\n\t\tdata.Custom(\"version\", msg.Version)\n\t\tdata.Custom(\"id_loaded\", true)\n\t\tdata.MutableContent()\n\t\tdata.AlertBody(msg.Message)\n\t\tdata.ContentAvailable()\n\t} else {\n\t\tswitch msg.Type {\n\t\tcase PushTypeMessage, PushTypeSession:\n\t\t\tdata.Category(msg.Category)\n\t\t\tdata.Sound(\"default\")\n\t\t\tdata.Custom(\"version\", msg.Version)\n\t\t\tdata.MutableContent()\n\t\t\tif msg.Type == PushTypeMessage {\n\t\t\t\tdata.ContentAvailable()\n\t\t\t}\n\n\t\t\tif msg.ChannelName != \"\" && msg.Version == \"v2\" {\n\t\t\t\tdata.AlertTitle(msg.ChannelName)\n\t\t\t\tdata.AlertBody(emoji.Sprint(msg.Message))\n\t\t\t\tdata.Custom(\"channel_name\", msg.ChannelName)\n\t\t\t} else {\n\t\t\t\tdata.Alert(emoji.Sprint(msg.Message))\n\n\t\t\t\tif msg.ChannelName != \"\" {\n\t\t\t\t\tdata.Custom(\"channel_name\", msg.ChannelName)\n\t\t\t\t}\n\t\t\t}\n\t\tcase PushTypeClear, PushTypeTest:\n\t\t\tdata.ContentAvailable()\n\t\tcase PushTypeUpdateBadge:\n\t\t\t// Handled by the apps, nothing else to do here\n\t\t}\n\t}\n\tif me.metrics != nil {\n\t\tme.metrics.incrementNotificationTotal(PushNotifyApple, pushType)\n\t}\n\tdata.Custom(\"type\", pushType)\n\tdata.Custom(\"sub_type\", msg.SubType)\n\tdata.Custom(\"server_id\", msg.ServerID)\n\n\tif msg.AckID != \"\" {\n\t\tdata.Custom(\"ack_id\", msg.AckID)\n\t}\n\n\tdata.Custom(\"is_crt_enabled\", msg.IsCRTEnabled)\n\n\tif msg.ChannelID != \"\" {\n\t\tdata.Custom(\"channel_id\", msg.ChannelID)\n\n\t\tif msg.IsCRTEnabled && msg.RootID != \"\" {\n\t\t\tdata.ThreadID(msg.RootID)\n\t\t} else {\n\t\t\tdata.ThreadID(msg.ChannelID)\n\t\t}\n\t}\n\n\tif msg.Signature == \"\" {\n\t\tdata.Custom(\"signature\", \"NO_SIGNATURE\")\n\t} else {\n\t\tdata.Custom(\"signature\", msg.Signature)\n\t}\n\n\tif msg.TeamID != \"\" {\n\t\tdata.Custom(\"team_id\", msg.TeamID)\n\t}\n\n\tif msg.SenderID != \"\" {\n\t\tdata.Custom(\"sender_id\", msg.SenderID)\n\t}\n\n\tif msg.SenderName != \"\" {\n\t\tdata.Custom(\"sender_name\", msg.SenderName)\n\t}\n\n\tif msg.PostID != \"\" {\n\t\tdata.Custom(\"post_id\", msg.PostID)\n\t}\n\n\tif msg.RootID != \"\" {\n\t\tdata.Custom(\"root_id\", msg.RootID)\n\t}\n\n\tif msg.OverrideUsername != \"\" {\n\t\tdata.Custom(\"override_username\", msg.OverrideUsername)\n\t}\n\n\tif msg.OverrideIconURL != \"\" {\n\t\tdata.Custom(\"override_icon_url\", msg.OverrideIconURL)\n\t}\n\n\tif msg.FromWebhook != \"\" {\n\t\tdata.Custom(\"from_webhook\", msg.FromWebhook)\n\t}\n\n\tif me.AppleClient != nil {\n\t\tme.logger.Info(\n\t\t\t\"Sending apple push notification\",\n\t\t\tmlog.String(\"device\", me.ApplePushSettings.Type),\n\t\t\tmlog.String(\"type\", msg.Type),\n\t\t\tmlog.String(\"ack_id\", msg.AckID),\n\t\t)\n\n\t\tres, err := me.SendNotificationWithRetry(notification)\n\t\tif err != nil {\n\t\t\tme.logger.Error(\n\t\t\t\t\"Failed to send apple push\",\n\t\t\t\tmlog.String(\"sid\", msg.ServerID),\n\t\t\t\tmlog.String(\"did\", msg.DeviceID),\n\t\t\t\tmlog.Err(err),\n\t\t\t\tmlog.String(\"type\", me.ApplePushSettings.Type),\n\t\t\t)\n\t\t\tif me.metrics != nil {\n\t\t\t\tme.metrics.incrementFailure(PushNotifyApple, pushType, \"RequestError\")\n\t\t\t}\n\t\t\treturn NewErrorPushResponse(\"unknown transport error\")\n\t\t}\n\n\t\tif !res.Sent() {\n\t\t\tif res.Reason == apns.ReasonBadDeviceToken || res.Reason == apns.ReasonUnregistered || res.Reason == apns.ReasonMissingDeviceToken || res.Reason == apns.ReasonDeviceTokenNotForTopic {\n\t\t\t\tme.logger.Info(\n\t\t\t\t\t\"Failed to send apple push sending remove code res\",\n\t\t\t\t\tmlog.String(\"ApnsID\", res.ApnsID),\n\t\t\t\t\tmlog.String(\"reason\", res.Reason),\n\t\t\t\t\tmlog.Int(\"code\", res.StatusCode),\n\t\t\t\t\tmlog.String(\"type\", me.ApplePushSettings.Type),\n\t\t\t\t)\n\t\t\t\tif me.metrics != nil {\n\t\t\t\t\tme.metrics.incrementRemoval(PushNotifyApple, pushType, res.Reason)\n\t\t\t\t}\n\t\t\t\treturn NewRemovePushResponse()\n\t\t\t}\n\n\t\t\tme.logger.Error(\n\t\t\t\t\"Failed to send apple push with res\",\n\t\t\t\tmlog.String(\"ApnsID\", res.ApnsID),\n\t\t\t\tmlog.String(\"reason\", res.Reason),\n\t\t\t\tmlog.Int(\"code\", res.StatusCode),\n\t\t\t\tmlog.String(\"type\", me.ApplePushSettings.Type),\n\t\t\t)\n\t\t\tif me.metrics != nil {\n\t\t\t\tme.metrics.incrementFailure(PushNotifyApple, pushType, res.Reason)\n\t\t\t}\n\t\t\treturn NewErrorPushResponse(\"unknown send response error\")\n\t\t}\n\t}\n\tif me.metrics != nil {\n\t\tif msg.AckID != \"\" {\n\t\t\tme.metrics.incrementSuccessWithAck(PushNotifyApple, pushType)\n\t\t} else {\n\t\t\tme.metrics.incrementSuccess(PushNotifyApple, pushType)\n\t\t}\n\t}\n\treturn NewOkPushResponse()\n}\n\nfunc (me *AppleNotificationServer) SendNotificationWithRetry(notification *apns.Notification) (*apns.Response, error) {\n\tvar res *apns.Response\n\tvar err error\n\twaitTime := time.Second\n\n\t// Keep a general context to make sure the whole retry\n\t// doesn't take longer than the timeout.\n\tgeneralContext, cancelGeneralContext := context.WithTimeout(context.Background(), me.sendTimeout)\n\tdefer cancelGeneralContext()\n\n\tfor retries := 0; retries < MAX_RETRIES; retries++ {\n\t\tstart := time.Now()\n\n\t\tretryContext, cancelRetryContext := context.WithTimeout(generalContext, me.retryTimeout)\n\t\tdefer cancelRetryContext()\n\t\tres, err = me.AppleClient.PushWithContext(retryContext, notification)\n\t\tif me.metrics != nil {\n\t\t\tme.metrics.observerNotificationResponse(PushNotifyApple, time.Since(start).Seconds())\n\t\t}\n\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tme.logger.Error(\n\t\t\t\"Failed to send apple push\",\n\t\t\tmlog.String(\"did\", notification.DeviceToken),\n\t\t\tmlog.Int(\"retry\", retries),\n\t\t\tmlog.Err(err),\n\t\t)\n\n\t\tif retries == MAX_RETRIES-1 {\n\t\t\tme.logger.Error(\"Max retries reached\", mlog.String(\"did\", notification.DeviceToken))\n\t\t\tbreak\n\t\t}\n\n\t\tselect {\n\t\tcase <-generalContext.Done():\n\t\tcase <-time.After(waitTime):\n\t\t}\n\n\t\tif generalContext.Err() != nil {\n\t\t\tme.logger.Info(\n\t\t\t\t\"Not retrying because context error\",\n\t\t\t\tmlog.String(\"did\", notification.DeviceToken),\n\t\t\t\tmlog.Int(\"retry\", retries),\n\t\t\t\tmlog.Err(generalContext.Err()),\n\t\t\t)\n\t\t\terr = generalContext.Err()\n\t\t\tbreak\n\t\t}\n\n\t\twaitTime *= 2\n\t}\n\n\treturn res, err\n}\n"
  },
  {
    "path": "server/config_push_proxy.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n)\n\ntype ConfigPushProxy struct {\n\tAndroidPushSettings     []AndroidPushSettings\n\tListenAddress           string\n\tThrottleVaryByHeader    string\n\tLogFileLocation         string\n\tSendTimeoutSec          int\n\tRetryTimeoutSec         int\n\tApplePushSettings       []ApplePushSettings\n\tEnableMetrics           bool\n\tEnableConsoleLog        bool\n\tEnableFileLog           bool\n\tLogFormat               string // json or plain\n\tThrottlePerSec          int\n\tThrottleMemoryStoreSize int\n}\n\ntype ApplePushSettings struct {\n\tType                    string\n\tApplePushCertPrivate    string\n\tApplePushCertPassword   string\n\tApplePushTopic          string\n\tAppleAuthKeyFile        string\n\tAppleAuthKeyID          string\n\tAppleTeamID             string\n\tApplePushUseDevelopment bool\n}\n\ntype AndroidPushSettings struct {\n\tType                string\n\tAndroidAPIKey       string `json:\"AndroidApiKey\"`\n\tServiceFileLocation string `json:\"ServiceFileLocation\"`\n}\n\n// FindConfigFile searches for the filepath in a list of directories\n// and then returns the absolute path to that file.\nfunc FindConfigFile(fileName string) string {\n\tif _, err := os.Stat(\"/tmp/\" + fileName); err == nil {\n\t\tfileName, _ = filepath.Abs(\"/tmp/\" + fileName)\n\t} else if _, err := os.Stat(\"./config/\" + fileName); err == nil {\n\t\tfileName, _ = filepath.Abs(\"./config/\" + fileName)\n\t} else if _, err := os.Stat(\"../config/\" + fileName); err == nil {\n\t\tfileName, _ = filepath.Abs(\"../config/\" + fileName)\n\t} else if _, err := os.Stat(fileName); err == nil {\n\t\tfileName, _ = filepath.Abs(fileName)\n\t}\n\n\treturn fileName\n}\n\n// LoadConfig loads the config from the given file path.\nfunc LoadConfig(fileName string) (*ConfigPushProxy, error) {\n\tfile, err := os.Open(fileName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer file.Close()\n\n\tbuf, err := io.ReadAll(file)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar cfg *ConfigPushProxy\n\terr = json.Unmarshal(buf, &cfg)\n\tif err != nil {\n\t\tfmt.Println(buf, err)\n\t\treturn nil, err\n\t}\n\n\tif !cfg.EnableConsoleLog && !cfg.EnableFileLog {\n\t\tcfg.EnableConsoleLog = true\n\t}\n\n\t// Set timeout defaults\n\tif cfg.SendTimeoutSec == 0 {\n\t\tcfg.SendTimeoutSec = 30\n\t}\n\n\tif cfg.RetryTimeoutSec == 0 {\n\t\tcfg.RetryTimeoutSec = 8\n\t}\n\n\tif cfg.RetryTimeoutSec > cfg.SendTimeoutSec {\n\t\tcfg.RetryTimeoutSec = cfg.SendTimeoutSec\n\t}\n\n\tif cfg.EnableFileLog {\n\t\tif cfg.LogFileLocation == \"\" {\n\t\t\t// We just do an mkdir -p equivalent.\n\t\t\t// Otherwise, it would need 2 steps of statting and creating.\n\t\t\terr := os.MkdirAll(\"./logs\", 0755)\n\t\t\tif err != nil {\n\t\t\t\t// If it fails, we log in the current directory itself\n\t\t\t\tcfg.LogFileLocation = \"./push_proxy.log\"\n\t\t\t} else {\n\t\t\t\tcfg.LogFileLocation = \"./logs/push_proxy.log\"\n\t\t\t}\n\t\t}\n\t\t// if file does not exist, create it.\n\t\tif _, err := os.Stat(cfg.LogFileLocation); os.IsNotExist(err) {\n\t\t\tf, err := os.Create(cfg.LogFileLocation)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tif err := f.Close(); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\t}\n\n\treturn cfg, nil\n}\n"
  },
  {
    "path": "server/logger.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc NewLogger(cfg *ConfigPushProxy) (*mlog.Logger, error) {\n\t// Initialize the logger - begin\n\tlogger, err := mlog.NewLogger()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif cfg.LogFormat != \"plain\" && cfg.LogFormat != \"json\" {\n\t\tcfg.LogFormat = \"plain\"\n\t}\n\terr = logger.ConfigureTargets(buildLogConfig(cfg), nil)\n\tif err != nil {\n\t\treturn logger, err\n\t}\n\n\treturn logger, nil\n}\n\nfunc buildLogConfig(cfg *ConfigPushProxy) mlog.LoggerConfiguration {\n\tlogConf := make(mlog.LoggerConfiguration)\n\n\tif cfg.EnableFileLog && cfg.LogFileLocation != \"\" {\n\t\tlogConf[\"file\"] = buildLogFileConfig(cfg.LogFileLocation, cfg.LogFormat)\n\t}\n\n\tif cfg.EnableConsoleLog || cfg.LogFileLocation == \"\" || !cfg.EnableFileLog {\n\t\tlogConf[\"console\"] = buildConsoleLogConfig(cfg.LogFormat)\n\t}\n\n\treturn logConf\n}\n\nfunc buildConsoleLogConfig(format string) mlog.TargetCfg {\n\treturn mlog.TargetCfg{\n\t\tType:          \"console\",\n\t\tLevels:        mlog.StdAll,\n\t\tFormat:        format,\n\t\tOptions:       json.RawMessage(`{\"out\": \"stdout\"}`),\n\t\tFormatOptions: json.RawMessage(`{\"enable_color\": true, \"enable_caller\": true}`),\n\t\tMaxQueueSize:  1000,\n\t}\n}\n\nfunc buildLogFileConfig(filename string, format string) mlog.TargetCfg {\n\topts := struct {\n\t\tFilename    string `json:\"filename\"`\n\t\tMax_size    int    `json:\"max_size\"`\n\t\tMax_age     int    `json:\"max_age\"`\n\t\tMax_backups int    `json:\"max_backups\"`\n\t\tCompress    bool   `json:\"compress\"`\n\t}{\n\t\tFilename:    filename,\n\t\tMax_size:    100,\n\t\tMax_age:     0,\n\t\tMax_backups: 0,\n\t\tCompress:    true,\n\t}\n\tvar optsJsonString, _ = json.Marshal(opts)\n\n\treturn mlog.TargetCfg{\n\t\tType:          \"file\",\n\t\tLevels:        mlog.StdAll,\n\t\tFormat:        format,\n\t\tOptions:       optsJsonString,\n\t\tFormatOptions: json.RawMessage(`{\"enable_color\": true, \"enable_caller\": true}`),\n\t\tMaxQueueSize:  1000,\n\t}\n}\n"
  },
  {
    "path": "server/logger_test.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestNewMlogLogger(t *testing.T) {\n\tt.Run(\"Instancing logger with implicit plain console\", func(t *testing.T) {\n\t\tcfg := &ConfigPushProxy{\n\t\t\tEnableFileLog: false,\n\t\t}\n\t\tlogger, err := NewLogger(cfg)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, logger)\n\t})\n\n\tt.Run(\"Instancing logger with json file\", func(t *testing.T) {\n\t\tlog, err := os.CreateTemp(\"\", \"log\")\n\t\trequire.NoError(t, err)\n\n\t\terr = log.Close()\n\t\trequire.NoError(t, err)\n\t\tdefer os.Remove(log.Name())\n\n\t\tcfg := &ConfigPushProxy{\n\t\t\tEnableFileLog:   true,\n\t\t\tLogFileLocation: log.Name(),\n\t\t\tLogFormat:       \"json\",\n\t\t}\n\n\t\tlogger, err := NewLogger(cfg)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, logger)\n\t})\n\n\tt.Run(\"Instancing logger with both file and console\", func(t *testing.T) {\n\t\tlog, err := os.CreateTemp(\"\", \"log\")\n\t\trequire.NoError(t, err)\n\n\t\terr = log.Close()\n\t\trequire.NoError(t, err)\n\t\tdefer os.Remove(log.Name())\n\n\t\tcfg := &ConfigPushProxy{\n\t\t\tEnableConsoleLog: true,\n\t\t\tEnableFileLog:    true,\n\t\t\tLogFileLocation:  log.Name(),\n\t\t\tLogFormat:        \"json\",\n\t\t}\n\n\t\tlogger, err := NewLogger(cfg)\n\t\tassert.NoError(t, err)\n\t\tassert.NotNil(t, logger)\n\t})\n}\n"
  },
  {
    "path": "server/metrics.go",
    "content": "package server\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/prometheus/client_golang/prometheus\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n)\n\nconst (\n\tmetricNotificationsTotalName   = \"service_notifications_total\"\n\tmetricSuccessName              = \"service_success_total\"\n\tmetricSuccessWithAckName       = \"service_success_with_ack_total\"\n\tmetricDeliveredName            = \"service_delivered_total\"\n\tmetricFailureName              = \"service_failure_total\"\n\tmetricFailureWithReasonName    = \"service_failure_with_reason_total\"\n\tmetricRemovalName              = \"service_removal_total\"\n\tmetricBadRequestName           = \"service_bad_request_total\"\n\tmetricFCMResponseName          = \"service_fcm_request_duration_seconds\"\n\tmetricAPNSResponseName         = \"service_apns_request_duration_seconds\"\n\tmetricServiceResponseName      = \"service_request_duration_seconds\"\n\tmetricNotificationResponseName = \"service_notification_duration_seconds\"\n)\n\n// NewPrometheusHandler returns the http.Handler to expose Prometheus metrics\nfunc NewPrometheusHandler() http.Handler {\n\treturn promhttp.Handler()\n}\n\ntype metrics struct {\n\tmetricNotificationsTotal   *prometheus.CounterVec\n\tmetricSuccess              *prometheus.CounterVec\n\tmetricSuccessWithAck       *prometheus.CounterVec\n\tmetricDelivered            *prometheus.CounterVec\n\tmetricFailure              *prometheus.CounterVec\n\tmetricFailureWithReason    *prometheus.CounterVec\n\tmetricRemoval              *prometheus.CounterVec\n\tmetricBadRequest           prometheus.Counter\n\tmetricAPNSResponse         prometheus.Histogram\n\tmetricFCMResponse          prometheus.Histogram\n\tmetricNotificationResponse *prometheus.HistogramVec\n\tmetricServiceResponse      prometheus.Histogram\n}\n\n// newMetrics initializes the metrics and registers them\nfunc newMetrics() *metrics {\n\tm := &metrics{\n\t\tmetricNotificationsTotal: prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: metricNotificationsTotalName,\n\t\t\tHelp: \"Number of notifications sent\"},\n\t\t\t[]string{\"platform\", \"type\"}),\n\t\tmetricSuccess: prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: metricSuccessName,\n\t\t\tHelp: \"Number of push success.\"},\n\t\t\t[]string{\"platform\", \"type\"}),\n\t\tmetricSuccessWithAck: prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: metricSuccessWithAckName,\n\t\t\tHelp: \"Number of push success that contains ackId.\"},\n\t\t\t[]string{\"platform\", \"type\"},\n\t\t),\n\t\tmetricDelivered: prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: metricDeliveredName,\n\t\t\tHelp: \"Number of push delivered.\"},\n\t\t\t[]string{\"platform\", \"type\"},\n\t\t),\n\t\tmetricFailure: prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: metricFailureName,\n\t\t\tHelp: \"Number of push errors.\"},\n\t\t\t[]string{\"platform\", \"type\"}),\n\t\tmetricFailureWithReason: prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: metricFailureWithReasonName,\n\t\t\tHelp: \"Number of push errors with reasons.\"},\n\t\t\t[]string{\"platform\", \"type\", \"reason\"}),\n\t\tmetricRemoval: prometheus.NewCounterVec(prometheus.CounterOpts{\n\t\t\tName: metricRemovalName,\n\t\t\tHelp: \"Number of device token errors.\"},\n\t\t\t[]string{\"platform\", \"reason\"}),\n\t\tmetricBadRequest: prometheus.NewCounter(prometheus.CounterOpts{\n\t\t\tName: metricBadRequestName,\n\t\t\tHelp: \"Request to push proxy was a bad request\",\n\t\t}),\n\t\tmetricAPNSResponse: prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\t\tName: metricAPNSResponseName,\n\t\t\tHelp: \"Request latency distribution\",\n\t\t}),\n\t\tmetricFCMResponse: prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\t\tName: metricFCMResponseName,\n\t\t\tHelp: \"Request latency distribution\",\n\t\t}),\n\t\tmetricNotificationResponse: prometheus.NewHistogramVec(prometheus.HistogramOpts{\n\t\t\tName: metricNotificationResponseName,\n\t\t\tHelp: \"Notifiction request latency distribution\"},\n\t\t\t[]string{\"platform\"}),\n\t\tmetricServiceResponse: prometheus.NewHistogram(prometheus.HistogramOpts{\n\t\t\tName: metricServiceResponseName,\n\t\t\tHelp: \"Request latency distribution\",\n\t\t}),\n\t}\n\n\tprometheus.MustRegister(\n\t\tm.metricNotificationsTotal,\n\t\tm.metricSuccess,\n\t\tm.metricSuccessWithAck,\n\t\tm.metricFailure,\n\t\tm.metricFailureWithReason,\n\t\tm.metricRemoval,\n\t\tm.metricBadRequest,\n\t\tm.metricAPNSResponse,\n\t\tm.metricFCMResponse,\n\t\tm.metricServiceResponse,\n\t\tm.metricNotificationResponse,\n\t)\n\n\treturn m\n}\n\nfunc (m *metrics) shutdown() {\n\tfunc(cs ...prometheus.Collector) {\n\t\tfor _, c := range cs {\n\t\t\tprometheus.Unregister(c)\n\t\t}\n\t}(\n\t\tm.metricNotificationsTotal,\n\t\tm.metricSuccess,\n\t\tm.metricSuccessWithAck,\n\t\tm.metricFailure,\n\t\tm.metricFailureWithReason,\n\t\tm.metricRemoval,\n\t\tm.metricBadRequest,\n\t\tm.metricAPNSResponse,\n\t\tm.metricFCMResponse,\n\t\tm.metricServiceResponse,\n\t\tm.metricNotificationResponse,\n\t)\n}\n\nfunc (m *metrics) incrementNotificationTotal(platform, pushType string) {\n\tm.metricNotificationsTotal.WithLabelValues(platform, pushType).Inc()\n}\n\nfunc (m *metrics) incrementSuccess(platform, pushType string) {\n\tm.metricSuccess.WithLabelValues(platform, pushType).Inc()\n}\n\nfunc (m *metrics) incrementSuccessWithAck(platform, pushType string) {\n\tm.incrementSuccess(platform, pushType)\n\tm.metricSuccessWithAck.WithLabelValues(platform, pushType).Inc()\n}\n\nfunc (m *metrics) incrementDelivered(platform, pushType string) {\n\tm.metricDelivered.WithLabelValues(platform, pushType).Inc()\n}\n\nfunc (m *metrics) incrementFailure(platform, pushType, reason string) {\n\tm.metricFailure.WithLabelValues(platform, pushType).Inc()\n\tif reason != \"\" {\n\t\tm.metricFailureWithReason.WithLabelValues(platform, pushType, reason).Inc()\n\t}\n}\n\nfunc (m *metrics) incrementRemoval(platform, pushType, reason string) {\n\tm.metricRemoval.WithLabelValues(platform, reason).Inc()\n\tm.incrementFailure(platform, pushType, reason)\n}\n\nfunc (m *metrics) incrementBadRequest() {\n\tm.metricBadRequest.Inc()\n}\n\nfunc (m *metrics) observeAPNSResponse(dur float64) {\n\tm.metricAPNSResponse.Observe(dur)\n}\n\nfunc (m *metrics) observeFCMResponse(dur float64) {\n\tm.metricFCMResponse.Observe(dur)\n}\n\nfunc (m *metrics) observeServiceResponse(dur float64) {\n\tm.metricServiceResponse.Observe(dur)\n}\n\nfunc (m *metrics) observerNotificationResponse(platform string, dur float64) {\n\tm.metricNotificationResponse.WithLabelValues(platform).Observe(dur)\n\tswitch platform {\n\tcase PushNotifyApple:\n\t\tm.observeAPNSResponse(dur)\n\tcase PushNotifyAndroid:\n\t\tm.observeFCMResponse(dur)\n\t}\n}\n"
  },
  {
    "path": "server/metrics_test.go",
    "content": "package server\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/prometheus/common/expfmt\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nfunc TestMetricDisabled(t *testing.T) {\n\tt.Log(\"Testing Metrics Enabled\")\n\tplatform := \"junk\"\n\tpushType := PushTypeMessage\n\n\tfileName := FindConfigFile(\"mattermost-push-proxy.sample.json\")\n\tcfg, err := LoadConfig(fileName)\n\trequire.NoError(t, err)\n\tcfg.AndroidPushSettings[0].AndroidAPIKey = platform\n\tcfg.EnableMetrics = false\n\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\n\tsrv := New(cfg, logger)\n\tsrv.Start()\n\n\ttime.Sleep(time.Second * 2)\n\tdefer func() {\n\t\tsrv.Stop()\n\t\ttime.Sleep(time.Second * 2)\n\t}()\n\n\tm := newMetrics()\n\tdefer m.shutdown()\n\n\tm.incrementBadRequest()\n\tm.incrementNotificationTotal(platform, pushType)\n\tm.incrementSuccess(platform, pushType)\n\tm.incrementRemoval(platform, pushType, \"not registered\")\n\tm.incrementFailure(platform, pushType, \"error\")\n\tm.observerNotificationResponse(PushNotifyApple, 1)\n\tm.observerNotificationResponse(PushNotifyAndroid, 1)\n\tm.observeServiceResponse(1)\n\n\tresp, err := http.Get(\"http://localhost:8066/metrics\")\n\tif err != nil {\n\t\tt.Fatalf(\"service should not return an http error\")\n\t}\n\tdefer resp.Body.Close()\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"service should return a parsable response\")\n\t}\n\tif !strings.Contains(string(data), \"404 page not found\") {\n\t\tt.Fatalf(\"service should return a 404\")\n\t}\n}\n\nfunc TestMetricEnabled(t *testing.T) {\n\tt.Log(\"Testing Metrics Enabled\")\n\tplatform := \"junk\"\n\tpushType := PushTypeMessage\n\n\tfileName := FindConfigFile(\"mattermost-push-proxy.sample.json\")\n\tcfg, err := LoadConfig(fileName)\n\trequire.NoError(t, err)\n\tcfg.AndroidPushSettings[0].AndroidAPIKey = platform\n\tcfg.EnableMetrics = true\n\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\n\tsrv := New(cfg, logger)\n\tsrv.Start()\n\n\ttime.Sleep(time.Second * 2)\n\tdefer func() {\n\t\tsrv.Stop()\n\t\ttime.Sleep(time.Second * 2)\n\t}()\n\n\tsrv.metrics.incrementBadRequest()\n\tsrv.metrics.incrementNotificationTotal(platform, pushType)\n\tsrv.metrics.incrementSuccess(platform, pushType)\n\tsrv.metrics.incrementRemoval(platform, pushType, \"not registered\")\n\tsrv.metrics.incrementFailure(platform, pushType, \"error\")\n\tsrv.metrics.observerNotificationResponse(PushNotifyApple, 1)\n\tsrv.metrics.observerNotificationResponse(PushNotifyAndroid, 1)\n\tsrv.metrics.observeServiceResponse(1)\n\n\tresp, err := http.Get(\"http://localhost:8066/metrics\")\n\tif err != nil {\n\t\tt.Fatalf(\"failed to get metrics endpoint - %s\", err.Error())\n\t}\n\tdefer resp.Body.Close()\n\n\tparser := &expfmt.TextParser{}\n\tmetrics, _ := parser.TextToMetricFamilies(resp.Body)\n\n\tcounters := []string{metricSuccessName, metricFailureName, metricFailureWithReasonName, metricRemovalName, metricBadRequestName, metricNotificationsTotalName}\n\tfor _, cn := range counters {\n\t\tif m, ok := metrics[cn]; !ok {\n\t\t\tt.Fatalf(\"metric not found. name: %s\", cn)\n\t\t} else {\n\t\t\tval := m.Metric[0].Counter.Value\n\t\t\tresult := float64(1)\n\n\t\t\tif cn == metricFailureName {\n\t\t\t\tresult = float64(2)\n\t\t\t}\n\n\t\t\tif val == nil {\n\t\t\t\tt.Fatalf(\"no metric value. name: %s\", cn)\n\t\t\t}\n\t\t\tif *val != result {\n\t\t\t\tt.Fatalf(\"metric value does not match. mame: %s, got: %v, expected: %v\",\n\t\t\t\t\tcn, *val, result)\n\t\t\t}\n\t\t}\n\t}\n\n\thistograms := []string{metricAPNSResponseName, metricFCMResponseName, metricServiceResponseName, metricNotificationResponseName}\n\tfor _, hn := range histograms {\n\t\tif m, ok := metrics[hn]; !ok {\n\t\t\tt.Fatalf(\"metric not found. name: %s\", hn)\n\t\t} else {\n\t\t\tval := m.Metric[0].Histogram.SampleCount\n\t\t\tif val == nil {\n\t\t\t\tt.Fatalf(\"no metric value. name: %s\", hn)\n\t\t\t}\n\t\t\tif *val != 1 {\n\t\t\t\tt.Fatalf(\"metric value does not match. mame: %s, got: %v, expected: %v\",\n\t\t\t\t\thn, *val, 1)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "server/push_notification.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nconst (\n\tPushNotifyApple   = \"apple\"\n\tPushNotifyAndroid = \"android\"\n\n\tPushTypeMessage     = \"message\"\n\tPushTypeClear       = \"clear\"\n\tPushTypeUpdateBadge = \"update_badge\"\n\tPushTypeSession     = \"session\"\n\tPushTypeTest        = \"test\"\n\n\tPushMessageV2 = \"v2\"\n\n\tPushSoundNone = \"none\"\n)\n\ntype PushNotificationAck struct {\n\tID       string `json:\"id\"`\n\tPlatform string `json:\"platform\"`\n\tType     string `json:\"type\"`\n}\n\ntype PushNotification struct {\n\tID               string `json:\"id\"`\n\tAckID            string `json:\"ack_id\"`\n\tPlatform         string `json:\"platform\"`\n\tServerID         string `json:\"server_id\"`\n\tDeviceID         string `json:\"device_id\"`\n\tCategory         string `json:\"category\"`\n\tSound            string `json:\"sound\"`\n\tMessage          string `json:\"message\"`\n\tTeamID           string `json:\"team_id\"`\n\tChannelID        string `json:\"channel_id\"`\n\tPostID           string `json:\"post_id\"`\n\tRootID           string `json:\"root_id\"`\n\tChannelName      string `json:\"channel_name\"`\n\tType             string `json:\"type\"`\n\tSubType          string `json:\"sub_type,omitempty\"`\n\tSenderName       string `json:\"sender_name\"`\n\tSenderID         string `json:\"sender_id\"`\n\tOverrideUsername string `json:\"override_username\"`\n\tOverrideIconURL  string `json:\"override_icon_url\"`\n\tFromWebhook      string `json:\"from_webhook\"`\n\tVersion          string `json:\"version\"`\n\tAppVersion       int    `json:\"app_version,omitempty\"`\n\tBadge            int    `json:\"badge\"`\n\tContentAvailable int    `json:\"cont_ava\"`\n\tIsCRTEnabled     bool   `json:\"is_crt_enabled\"`\n\tIsIDLoaded       bool   `json:\"is_id_loaded\"`\n\tSignature        string `json:\"signature\"`\n}\n"
  },
  {
    "path": "server/push_response.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n)\n\nconst (\n\tPUSH_STATUS           = \"status\"\n\tPUSH_STATUS_OK        = \"OK\"\n\tPUSH_STATUS_FAIL      = \"FAIL\"\n\tPUSH_STATUS_REMOVE    = \"REMOVE\"\n\tPUSH_STATUS_ERROR_MSG = \"error\"\n)\n\ntype PushResponse map[string]string\n\nfunc NewOkPushResponse() PushResponse {\n\tm := make(map[string]string)\n\tm[PUSH_STATUS] = PUSH_STATUS_OK\n\treturn m\n}\n\nfunc NewRemovePushResponse() PushResponse {\n\tm := make(map[string]string)\n\tm[PUSH_STATUS] = PUSH_STATUS_REMOVE\n\treturn m\n}\n\nfunc NewErrorPushResponse(message string) PushResponse {\n\tm := make(map[string]string)\n\tm[PUSH_STATUS] = PUSH_STATUS_FAIL\n\tm[PUSH_STATUS_ERROR_MSG] = message\n\treturn m\n}\n\nfunc PushResponseFromJson(data io.Reader) PushResponse {\n\tdecoder := json.NewDecoder(data)\n\n\tvar objmap PushResponse\n\tif err := decoder.Decode(&objmap); err != nil {\n\t\treturn make(map[string]string)\n\t} else {\n\t\treturn objmap\n\t}\n}\n"
  },
  {
    "path": "server/server.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gorilla/handlers\"\n\t\"github.com/gorilla/mux\"\n\tthrottled \"gopkg.in/throttled/throttled.v1\"\n\tthrottledStore \"gopkg.in/throttled/throttled.v1/store\"\n\n\t\"github.com/mattermost/mattermost-push-proxy/internal/version\"\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n)\n\nconst (\n\tHEADER_FORWARDED           = \"X-Forwarded-For\"\n\tHEADER_REAL_IP             = \"X-Real-IP\"\n\tWAIT_FOR_SERVER_SHUTDOWN   = time.Second * 5\n\tCONNECTION_TIMEOUT_SECONDS = 60\n\tMAX_RETRIES                = 3\n)\n\ntype NotificationServer interface {\n\tSendNotification(msg *PushNotification) PushResponse\n\tInitialize() error\n}\n\n// Server is the main struct which performs all activities.\ntype Server struct {\n\tcfg         *ConfigPushProxy\n\thttpServer  *http.Server\n\tpushTargets map[string]NotificationServer\n\tmetrics     *metrics\n\tlogger      *mlog.Logger\n}\n\n// New returns a new Server instance.\nfunc New(cfg *ConfigPushProxy, logger *mlog.Logger) *Server {\n\treturn &Server{\n\t\tcfg:         cfg,\n\t\tpushTargets: make(map[string]NotificationServer),\n\t\tlogger:      logger,\n\t}\n}\n\n// Start starts the server.\nfunc (s *Server) Start() {\n\tv := version.VersionInfo()\n\ts.logger.Info(\"Push proxy server is initializing...\", mlog.String(\"version\", v.String()))\n\n\tproxyServer := getProxyServer()\n\tif proxyServer != \"\" {\n\t\ts.logger.Info(\"Proxy server detected.\", mlog.String(\"proxyServer\", proxyServer))\n\t}\n\n\tvar m *metrics\n\tif s.cfg.EnableMetrics {\n\t\tm = newMetrics()\n\t\ts.metrics = m\n\t}\n\n\tfor _, settings := range s.cfg.ApplePushSettings {\n\t\tserver := NewAppleNotificationServer(settings, s.logger, m, s.cfg.SendTimeoutSec, s.cfg.RetryTimeoutSec)\n\t\terr := server.Initialize()\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"Failed to initialize client\", mlog.Err(err))\n\t\t\tcontinue\n\t\t}\n\t\ts.pushTargets[settings.Type] = server\n\t}\n\n\tfor _, settings := range s.cfg.AndroidPushSettings {\n\t\tserver := NewAndroidNotificationServer(settings, s.logger, m, s.cfg.SendTimeoutSec, s.cfg.RetryTimeoutSec)\n\t\terr := server.Initialize()\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"Failed to initialize client\", mlog.Err(err))\n\t\t\tcontinue\n\t\t}\n\t\ts.pushTargets[settings.Type] = server\n\t}\n\n\trouter := mux.NewRouter()\n\tvary := throttled.VaryBy{}\n\tvary.RemoteAddr = false\n\tvary.Headers = strings.Fields(s.cfg.ThrottleVaryByHeader)\n\tth := throttled.RateLimit(throttled.PerSec(s.cfg.ThrottlePerSec), &vary, throttledStore.NewMemStore(s.cfg.ThrottleMemoryStoreSize))\n\n\tth.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\ts.logger.Error(\"Error: code=429\", mlog.String(\"path\", r.URL.Path), mlog.String(\"ip\", s.getIpAddress(r)))\n\t\tthrottled.DefaultDeniedHandler.ServeHTTP(w, r)\n\t})\n\n\thandler := th.Throttle(router)\n\n\trouter.HandleFunc(\"/\", root).Methods(\"GET\")\n\trouter.HandleFunc(\"/version\", s.version).Methods(\"GET\")\n\n\tmetricCompatibleSendNotificationHandler := s.handleSendNotification\n\tmetricCompatibleAckNotificationHandler := s.handleAckNotification\n\tif s.cfg.EnableMetrics {\n\t\tmetrics := NewPrometheusHandler()\n\t\trouter.Handle(\"/metrics\", metrics).Methods(\"GET\")\n\t\tmetricCompatibleSendNotificationHandler = s.responseTimeMiddleware(s.handleSendNotification)\n\t\tmetricCompatibleAckNotificationHandler = s.responseTimeMiddleware(s.handleAckNotification)\n\t}\n\tr := router.PathPrefix(\"/api/v1\").Subrouter()\n\tr.HandleFunc(\"/send_push\", metricCompatibleSendNotificationHandler).Methods(\"POST\")\n\tr.HandleFunc(\"/ack\", metricCompatibleAckNotificationHandler).Methods(\"POST\")\n\n\ts.httpServer = &http.Server{\n\t\tAddr:         s.cfg.ListenAddress,\n\t\tHandler:      handlers.RecoveryHandler(handlers.PrintRecoveryStack(true))(handler),\n\t\tReadTimeout:  time.Duration(CONNECTION_TIMEOUT_SECONDS) * time.Second,\n\t\tWriteTimeout: time.Duration(CONNECTION_TIMEOUT_SECONDS) * time.Second,\n\t}\n\tgo func() {\n\t\terr := s.httpServer.ListenAndServe()\n\t\tif err != http.ErrServerClosed {\n\t\t\ts.logger.Fatal(err.Error())\n\t\t}\n\t}()\n\n\ts.logger.Info(\"Server is listening on \" + s.cfg.ListenAddress)\n}\n\n// Stop stops the server.\nfunc (s *Server) Stop() {\n\ts.logger.Info(\"Stopping Server...\")\n\tctx, cancel := context.WithTimeout(context.Background(), WAIT_FOR_SERVER_SHUTDOWN)\n\tdefer cancel()\n\tif s.metrics != nil {\n\t\ts.metrics.shutdown()\n\t}\n\t// Close shop\n\terr := s.httpServer.Shutdown(ctx)\n\tif err != nil {\n\t\ts.logger.Error(err.Error())\n\t}\n}\n\nfunc root(w http.ResponseWriter, r *http.Request) {\n\t_, _ = w.Write([]byte(\"<html><body>Mattermost Push Proxy</body></html>\"))\n}\n\nfunc (s *Server) version(w http.ResponseWriter, _ *http.Request) {\n\tinfo := version.VersionInfo()\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tif err := json.NewEncoder(w).Encode(info); err != nil {\n\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err))\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t}\n}\n\nfunc (s *Server) responseTimeMiddleware(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tstart := time.Now()\n\t\tf(w, r)\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.observeServiceResponse(time.Since(start).Seconds())\n\t\t}\n\t}\n}\n\nfunc (s *Server) handleSendNotification(w http.ResponseWriter, r *http.Request) {\n\tvar msg PushNotification\n\terr := json.NewDecoder(r.Body).Decode(&msg)\n\tif err != nil {\n\t\trMsg := fmt.Sprintf(\"Failed to read message body: %v\", err)\n\t\ts.logger.Error(rMsg)\n\t\tresp := NewErrorPushResponse(rMsg)\n\t\tif err2 := json.NewEncoder(w).Encode(resp); err2 != nil {\n\t\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err2))\n\t\t}\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t\treturn\n\t}\n\n\tif msg.ServerID == \"\" {\n\t\trMsg := \"Failed because of missing server Id\"\n\t\ts.logger.Error(rMsg)\n\t\tresp := NewErrorPushResponse(rMsg)\n\t\tif err2 := json.NewEncoder(w).Encode(resp); err2 != nil {\n\t\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err2))\n\t\t}\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t\treturn\n\t}\n\n\tif msg.DeviceID == \"\" {\n\t\trMsg := fmt.Sprintf(\"Failed because of missing device Id serverId=%v\", msg.ServerID)\n\t\ts.logger.Error(rMsg)\n\t\tresp := NewErrorPushResponse(rMsg)\n\t\tif err2 := json.NewEncoder(w).Encode(resp); err2 != nil {\n\t\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err2))\n\t\t}\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t\treturn\n\t}\n\n\tif len(msg.Message) > 2047 {\n\t\tmsg.Message = msg.Message[0:2046]\n\t}\n\n\tif len(msg.ChannelName) > 64 {\n\t\tmsg.ChannelName = msg.ChannelName[0:64]\n\t}\n\n\t// Parse the app version if available\n\tindex := strings.Index(msg.Platform, \"-v\")\n\tplatform := msg.Platform\n\tmsg.AppVersion = 1\n\tif index > -1 {\n\t\tmsg.Platform = platform[:index]\n\t\tappVersionString := platform[index+2:]\n\t\tversion, e := strconv.Atoi(appVersionString)\n\t\tif e == nil {\n\t\t\tmsg.AppVersion = version\n\t\t} else {\n\t\t\trMsg := fmt.Sprintf(\"Could not determine the app version in %v appVersion=%v\", msg.Platform, appVersionString)\n\t\t\ts.logger.Error(rMsg)\n\t\t}\n\t}\n\n\tif server, ok := s.pushTargets[msg.Platform]; ok {\n\t\trMsg := server.SendNotification(&msg)\n\t\tif err2 := json.NewEncoder(w).Encode(rMsg); err2 != nil {\n\t\t\ts.logger.Error(\"Failed to write message\", mlog.Err(err2))\n\t\t}\n\t\treturn\n\t}\n\trMsg := fmt.Sprintf(\"Did not send message because of missing platform property type=%v serverId=%v\", msg.Platform, msg.ServerID)\n\ts.logger.Error(rMsg)\n\tresp := NewErrorPushResponse(rMsg)\n\terr = json.NewEncoder(w).Encode(resp)\n\tif err != nil {\n\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err))\n\t}\n\tif s.metrics != nil {\n\t\ts.metrics.incrementBadRequest()\n\t}\n}\n\nfunc (s *Server) handleAckNotification(w http.ResponseWriter, r *http.Request) {\n\tvar ack PushNotificationAck\n\terr := json.NewDecoder(r.Body).Decode(&ack)\n\tif err != nil {\n\t\tmsg := fmt.Sprintf(\"Failed to read ack body: %v\", err)\n\t\ts.logger.Error(msg)\n\t\tresp := NewErrorPushResponse(msg)\n\t\tif err2 := json.NewEncoder(w).Encode(resp); err2 != nil {\n\t\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err2))\n\t\t}\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t\treturn\n\t}\n\n\tif ack.ID == \"\" {\n\t\tmsg := \"Failed because of missing ack Id\"\n\t\ts.logger.Error(msg)\n\t\tresp := NewErrorPushResponse(msg)\n\t\tif err := json.NewEncoder(w).Encode(resp); err != nil {\n\t\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err))\n\t\t}\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t\treturn\n\t}\n\n\tif ack.Platform == \"\" {\n\t\tmsg := \"Failed because of missing ack platform\"\n\t\ts.logger.Error(msg)\n\t\tresp := NewErrorPushResponse(msg)\n\t\tif err := json.NewEncoder(w).Encode(resp); err != nil {\n\t\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err))\n\t\t}\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t\treturn\n\t}\n\n\tif ack.Type == \"\" {\n\t\tmsg := \"Failed because of missing ack type\"\n\t\ts.logger.Error(msg)\n\t\tresp := NewErrorPushResponse(msg)\n\t\tif err := json.NewEncoder(w).Encode(resp); err != nil {\n\t\t\ts.logger.Error(\"Failed to write response\", mlog.Err(err))\n\t\t}\n\t\tif s.metrics != nil {\n\t\t\ts.metrics.incrementBadRequest()\n\t\t}\n\t\treturn\n\t}\n\n\t// Increment ACK\n\ts.logger.Info(\"Acknowledged delivery receipt\", mlog.String(\"ack_id\", ack.ID))\n\tif s.metrics != nil {\n\t\ts.metrics.incrementDelivered(ack.Platform, ack.Type)\n\t}\n\n\trMsg := NewOkPushResponse()\n\tif err := json.NewEncoder(w).Encode(rMsg); err != nil {\n\t\ts.logger.Error(\"Failed to write message\", mlog.Err(err))\n\t}\n}\n\nfunc (s *Server) getIpAddress(r *http.Request) string {\n\taddress := r.Header.Get(HEADER_FORWARDED)\n\tvar err error\n\n\tif address == \"\" {\n\t\taddress = r.Header.Get(HEADER_REAL_IP)\n\t}\n\n\tif address == \"\" {\n\t\taddress, _, err = net.SplitHostPort(r.RemoteAddr)\n\t\tif err != nil {\n\t\t\ts.logger.Error(\"error in getting IP address\", mlog.Err(err))\n\t\t}\n\t}\n\n\treturn address\n}\n\nfunc getProxyServer() string {\n\t// HTTPS_PROXY gets the higher priority.\n\tproxyServer := os.Getenv(\"HTTPS_PROXY\")\n\tif proxyServer == \"\" {\n\t\tproxyServer = os.Getenv(\"HTTP_PROXY\")\n\t}\n\treturn proxyServer\n}\n"
  },
  {
    "path": "server/server_test.go",
    "content": "// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.\n// See License.txt for license information.\n\npackage server\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/mattermost/mattermost-push-proxy/internal/version\"\n\n\t\"github.com/mattermost/mattermost/server/public/shared/mlog\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestBasicServer(t *testing.T) {\n\tfileName := FindConfigFile(\"mattermost-push-proxy.sample.json\")\n\tcfg, err := LoadConfig(fileName)\n\trequire.NoError(t, err)\n\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\n\tsrv := New(cfg, logger)\n\tsrv.Start()\n\n\ttime.Sleep(time.Second * 2)\n\n\tmsg := PushNotification{}\n\tmsg.Message = \"test\"\n\tmsg.Badge = 1\n\tmsg.DeviceID = \"test\"\n\n\t// Test for missing server Id\n\tclient := http.Client{}\n\tbuf, err := json.Marshal(msg)\n\trequire.NoError(t, err)\n\trq, _ := http.NewRequest(\"POST\", \"http://localhost:8066/api/v1/send_push\", bytes.NewReader(buf))\n\tif resp, err2 := client.Do(rq); err2 != nil {\n\t\tt.Fatal(err2)\n\t} else {\n\t\tpr := PushResponseFromJson(resp.Body)\n\t\tif pr == nil || pr[PUSH_STATUS] != PUSH_STATUS_FAIL {\n\t\t\tt.Fatal(\"invalid response\")\n\t\t}\n\t}\n\n\t// Test for missing platform type\n\tmsg.ServerID = \"test\"\n\tclient = http.Client{}\n\tbuf, err = json.Marshal(msg)\n\trequire.NoError(t, err)\n\trq, _ = http.NewRequest(\"POST\", \"http://localhost:8066/api/v1/send_push\", bytes.NewReader(buf))\n\tif resp, err2 := client.Do(rq); err2 != nil {\n\t\tt.Fatal(err2)\n\t} else {\n\t\tpr := PushResponseFromJson(resp.Body)\n\t\tif pr == nil || pr[PUSH_STATUS] != PUSH_STATUS_FAIL {\n\t\t\tt.Fatal(\"invalid response\")\n\t\t}\n\t}\n\n\t// Test for junk platform type\n\tmsg.Platform = \"junk\"\n\tbuf, err = json.Marshal(msg)\n\trequire.NoError(t, err)\n\trq, _ = http.NewRequest(\"POST\", \"http://localhost:8066/api/v1/send_push\", bytes.NewReader(buf))\n\tif resp, err := client.Do(rq); err != nil {\n\t\tt.Fatal(err)\n\t} else {\n\t\tpr := PushResponseFromJson(resp.Body)\n\t\tif pr == nil || pr[PUSH_STATUS] != PUSH_STATUS_FAIL {\n\t\t\tt.Fatal(\"invalid response\")\n\t\t}\n\t}\n\n\tsrv.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n\nfunc TestAndroidSend(t *testing.T) {\n\tfileName := FindConfigFile(\"mattermost-push-proxy.sample.json\")\n\tcfg, err := LoadConfig(fileName)\n\trequire.NoError(t, err)\n\n\tcfg.AndroidPushSettings[0].AndroidAPIKey = \"junk\"\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\n\tsrv := New(cfg, logger)\n\tsrv.Start()\n\n\ttime.Sleep(time.Second * 2)\n\n\tmsg := PushNotification{}\n\tmsg.Message = \"test\"\n\tmsg.Badge = 1\n\tmsg.Platform = PushNotifyAndroid\n\tmsg.ServerID = \"test\"\n\tmsg.DeviceID = \"test\"\n\n\tclient := http.Client{}\n\tbuf, err := json.Marshal(msg)\n\trequire.NoError(t, err)\n\trq, _ := http.NewRequest(\"POST\", \"http://localhost:8066/api/v1/send_push\", bytes.NewReader(buf))\n\tif resp, err := client.Do(rq); err != nil {\n\t\tt.Fatal(err)\n\t} else {\n\t\tpr := PushResponseFromJson(resp.Body)\n\t\tif pr == nil || pr[PUSH_STATUS] != PUSH_STATUS_FAIL {\n\t\t\tt.Fatal(\"invalid response\")\n\t\t}\n\t}\n\n\tsrv.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n\nfunc TestServer_version(t *testing.T) {\n\tfileName := FindConfigFile(\"mattermost-push-proxy.sample.json\")\n\tcfg, err := LoadConfig(fileName)\n\trequire.NoError(t, err)\n\tlogger, err := mlog.NewLogger()\n\trequire.NoError(t, err)\n\n\tsrv := New(cfg, logger)\n\n\treq := httptest.NewRequest(http.MethodGet, \"/version\", nil)\n\tres := httptest.NewRecorder()\n\tsrv.version(res, req)\n\tassert.Equal(t, res.Code, http.StatusOK)\n\n\tinfo := version.VersionInfo()\n\tret := struct {\n\t\tVersion string\n\t\tHash    string\n\t}{}\n\terr = json.NewDecoder(res.Body).Decode(&ret)\n\tassert.NoError(t, err)\n\tassert.Equal(t, info.BuildVersion, ret.Version)\n\tassert.Equal(t, info.BuildHash, ret.Hash)\n}\n"
  },
  {
    "path": "swagger/spec.yaml",
    "content": "openapi: 3.0.1\ninfo:\n  title: Mattermost Push Proxy\n  description: 'This is the OpenAPI documentation for Mattermost Push Proxy REST API'\n  version: 1.0.0\nservers:\n- url: http://url-to-push-proxy.com/api/v1\npaths:\n  /send_push:\n    post:\n      summary: Send push notification\n      requestBody:\n        description: Push notification request body\n        content:\n          '*/*':\n            schema:\n              $ref: '#/components/schemas/PushNotification'\n            example:\n              type: \"update_badge\"\n              device_id: \"ackljrfoegdflghdg\"\n              platform: \"android\"\n              ack_id: \"kfs095jsdsdfjslj\"\n              version: \"v2\"\n              sound: \"none\"\n              cont_ava: 1\n              badge: 2\n        required: true\n      responses:\n        default:\n          description: response\n          content:\n            application/json:\n              schema:\n                oneOf:\n                  - $ref: '#/components/schemas/PushResponseOK'\n                  - $ref: '#/components/schemas/PushResponseRemove'\n                  - $ref: '#/components/schemas/PushResponseError'\n              example:\n                status: OK\n  /ack:\n    post:\n      summary: Send acknowledgement of a push notification\n      requestBody:\n        description: Send acknowledgement request body\n        content:\n          '*/*':\n            schema:\n              $ref: '#/components/schemas/PushNotificationAck'\n            example:\n              id: \"abcxyz\"\n              platform: \"android\"\n              type: \"message\"\n        required: true\n      responses:\n        default:\n          description: response\n          content:\n            application/json:\n              schema:\n                oneOf:\n                  - $ref: '#/components/schemas/PushResponseOK'\n                  - $ref: '#/components/schemas/PushResponseError'\n              example:\n                status: OK\ncomponents:\n  schemas:\n    PushNotification:\n      type: object\n      properties:\n        ack_id:\n          type: string\n          description: \"id of the acknowledgement\"\n        platform:\n          type: string\n          description: \"type of the platform\"\n          enum:\n          - apple\n          - android\n          - apple_rn\n          - android_rn\n        server_id:\n          type: string\n          description: \"id of the server\"\n        device_id:\n          type: string\n          description: \"id of the device\"\n        category:\n          type: string\n          description: \"category of the message\"\n        sound:\n          type: string\n          description: \"type of sound to make when message arrives\"\n        message:\n          type: string\n          description: \"contents of the message\"\n        badge:\n          type: integer\n          description: \"badge count\"\n          format: int64\n        cont_ava:\n          description: \"content available\"\n          type: integer\n          format: int64\n        team_id:\n          description: \"id of the team\"\n          type: string\n        channel_id:\n          description: \"id of the channel\"\n          type: string\n        root_id:\n          description: \"id of the root of the post\"\n          type: string\n        post_id:\n          description: \"id of the post\"\n          type: string\n        channel_name:\n          description: \"name of the channel\"\n          type: string\n        type:\n          type: string\n          description: \"type of the message\"\n          enum:\n          - message\n          - clear\n          - update_badge\n          - session\n        sender_name:\n          description: \"name of the sender\"\n          type: string\n        sender_id:\n          description: \"id of the sender\"\n          type: string\n        override_username:\n          description: \"override the default username\"\n          type: string\n        override_icon_url:\n          description: \"override the default icon url\"\n          type: string\n        from_webhook:\n          description: \"name of the webhook\"\n          type: string\n        version:\n          type: string\n          description: \"version of the message\"\n          default: \"v2\"\n        is_id_loaded:\n          description: \"whether the message is id_loaded or not\"\n          type: boolean\n    PushNotificationAck:\n      type: object\n      properties:\n        id:\n          type: string\n          description: \"id of the acknowledgement\"\n        platform:\n          type: string\n          description: \"type of the platform\"\n          enum:\n          - apple\n          - android\n          - apple_rn\n          - android_rn\n        type:\n          type: string\n          description: \"type of the message\"\n          enum:\n          - message\n          - clear\n          - update_badge\n          - session\n    PushResponseOK:\n      type: object\n      properties:\n        status:\n          type: string\n          default: \"OK\"\n    PushResponseRemove:\n      type: object\n      properties:\n        status:\n          type: string\n          default: \"REMOVE\"\n    PushResponseError:\n      type: object\n      properties:\n        status:\n          type: string\n          default: \"FAIL\"\n        error:\n          type: string\n"
  },
  {
    "path": "swagger/template.hbs",
    "content": "<!DOCTYPE html>\n<html>\n\n<head>\n  <meta charset=\"utf8\" />\n  <title>{{title}}</title>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <style>\n    body {\n      padding: 0;\n      margin: 0;\n    }\n  </style>\n  {{{redocHead}}}\n  {{#unless disableGoogleFont}}<link href=\"https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700\" rel=\"stylesheet\">{{/unless}}\n</head>\n\n<body>\n  {{{redocHTML}}}\n</body>\n\n</html>"
  }
]