diff --git a/.github/assets/logo.jpg b/.github/assets/logo.jpg
new file mode 100644
index 00000000..e44d8878
Binary files /dev/null and b/.github/assets/logo.jpg differ
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..3198c0f1
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+ - package-ecosystem: "github-actions" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
+ - package-ecosystem: "gomod" # See documentation for possible values
+ directory: "/" # Location of package manifests
+ schedule:
+ interval: "weekly"
diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml
new file mode 100644
index 00000000..e3098b8b
--- /dev/null
+++ b/.github/workflows/helm.yaml
@@ -0,0 +1,58 @@
+---
+name: helm
+on:
+ - push
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: set up helm
+ uses: azure/setup-helm@v4
+ with:
+ version: v3.13.0
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: 3.7
+
+ - name: set up chart-testing
+ uses: helm/chart-testing-action@v2.6.1
+
+ - name: run chart-testing (lint)
+ run: ct lint --all
+
+ - name: create kind cluster
+ uses: helm/kind-action@v1.10.0
+
+ - name: run chart-testing (install)
+ run: ct install --all
+
+ release:
+ runs-on: ubuntu-latest
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ steps:
+ - name: checkout
+ uses: actions/checkout@v4
+
+ - name: set up helm
+ uses: azure/setup-helm@v4
+ with:
+ version: v3.13.0
+
+ - name: login to github container registry using helm
+ run: |
+ echo ${{ secrets.GITHUB_TOKEN }} | helm registry login ghcr.io/snapp-incubator/soteria --username ${{ github.repository_owner }} --password-stdin
+
+ - name: package soteria helm chart
+ run: |
+ version=${{ github.ref_name }}
+ helm package --version "${version##v}" --app-version "${version}" ./charts/soteria
+
+ - name: publish soteria chart to github container registry
+ run: |
+ version=${{ github.ref_name }}
+ helm push "soteria-${version##v}".tgz oci://ghcr.io/snapp-incubator/soteria-chart
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
new file mode 100644
index 00000000..4e1ca3ac
--- /dev/null
+++ b/.github/workflows/test.yaml
@@ -0,0 +1,61 @@
+---
+name: test
+on:
+ - push
+
+jobs:
+ lint:
+ name: lint
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version-file: "go.mod"
+ - name: golangci-lint
+ uses: golangci/golangci-lint-action@v6
+ with:
+ version: latest
+ args: --enable-all --timeout=30m
+
+ test:
+ name: test
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version-file: "go.mod"
+ - run: go test -v ./... -covermode=atomic -coverprofile=coverage.out
+ - uses: codecov/codecov-action@v4.5.0
+ with:
+ files: coverage.out
+ token: ${{ secrets.CODECOV_TOKEN }}
+ slug: snapp-incubator/soteria
+
+ docker:
+ runs-on: ubuntu-latest
+ needs:
+ - lint
+ - test
+ if: github.event_name != 'pull_request'
+ steps:
+ - uses: actions/checkout@v4
+ - uses: docker/setup-buildx-action@v3
+ - uses: docker/login-action@v3
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+ - uses: docker/bake-action@v5
+ if: ${{ startsWith(github.ref, 'refs/tags/v') }}
+ env:
+ TAG: ${{ github.ref_name }}
+ with:
+ push: true
+ files: 'build/package/docker-bake.json'
+ - uses: docker/bake-action@v5
+ if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
+ with:
+ push: true
+ files: 'build/package/docker-bake.json'
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
deleted file mode 100644
index cdf2ae0b..00000000
--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,43 +0,0 @@
----
-image: registry.snapp.tech/docker/golang:1.11.4-alpine3.8
-
-stages:
- - lint
- - pre-compile
- - compile
- - test
- - build
- - release
- - deploy
-
-variables:
- BUILD_PATH: "./"
- OKD_TEH1_CLUSTER_ADDRESS: "okd.private.teh-1.snappcloud.io"
- OKD_TEH2_CLUSTER_ADDRESS: "okd.private.teh-2.snappcloud.io"
- OKD_TEH1_REGISTRY: 'registry.apps.private.teh-1.snappcloud.io'
- OKD_TEH2_REGISTRY: 'registry.apps.private.teh-2.snappcloud.io'
- PROJECT_SERVICE_NAME: 'soteria'
- PROJECT_STAGING: 'realtime-staging'
- PROJECT_PRODUCTION_TEH1: 'realtime-production'
- PROJECT_PRODUCTION_TEH2: 'rtc'
- PROJECT_MOZART: 'mozart'
- PROJECT_VERSION: "${CI_COMMIT_SHA}"
-
-
-include:
- - local: .gitlab/ci/templates/precompile.giltab-ci.yml
- - local: .gitlab/ci/templates/compile.giltab-ci.yml
- - local: .gitlab/ci/templates/test.giltab-ci.yml
- - local: .gitlab/ci/templates/build.giltab-ci.yml
- - local: .gitlab/ci/templates/release.giltab-ci.yml
- - local: .gitlab/ci/templates/deploy.vm.giltab-ci.yml
- - local: .gitlab/ci/templates/deploy.env.giltab-ci.yml
- - local: .gitlab/ci/templates/deploy.cloud.giltab-ci.yml
- - local: .gitlab/ci/templates/deploy.chart.giltab-ci.yml
-
- - project: dispatching/igniteg/easy-ci
- ref: master
- file: "easy-ci.yml"
-
- - project: templates/gitlab-templates
- file: sorush.gitlab-ci.yml
diff --git a/.gitlab/ci/env/env.conf b/.gitlab/ci/env/env.conf
deleted file mode 100644
index 5bdbaa25..00000000
--- a/.gitlab/ci/env/env.conf
+++ /dev/null
@@ -1,30 +0,0 @@
-# Application: Soteria
-# Last update: $(date)
-#
-GIN_MODE=release
-SOTERIA_ALLOWED_ACCESS_TYPES='sub,pub'
-SOTERIA_CACHE_ENABLED='true'
-SOTERIA_CACHE_EXPIRATION=600s
-SOTERIA_GRPC_PORT='50051'
-SOTERIA_HTTP_PORT='9999'
-SOTERIA_LOGGER_LEVEL=ERROR
-SOTERIA_LOGGER_SENTRY_DSN=''
-SOTERIA_LOGGER_SENTRY_ENABLED='false'
-SOTERIA_LOGGER_SENTRY_TIMEOUT=500ms
-SOTERIA_REDIS_ADDRESS='emqx-redis-01.app.afra.snapp.infra:6379'
-SOTERIA_REDIS_IDLE_CHECK_FREQUENCY=60s
-SOTERIA_REDIS_IDLE_TIMEOUT=300s
-SOTERIA_REDIS_MAX_RETRIES='0'
-SOTERIA_REDIS_MAX_RETRY_BACKOFF=512ms
-SOTERIA_REDIS_MIN_IDLE_CONNECTIONS='5'
-SOTERIA_REDIS_MIN_RETRY_BACKOFF=8ms
-SOTERIA_REDIS_PASS=''
-SOTERIA_REDIS_POOL_SIZE='10'
-SOTERIA_REDIS_POOL_TIMEOUT=4s
-SOTERIA_REDIS_READ_TIMEOUT=3s
-SOTERIA_REDIS_SET_MEMBER_EXP_TIME=300s
-SOTERIA_DRIVER_SALT='___'
-SOTERIA_PASSENGER_SALT='___'
-SOTERIA_JWT_KEYS_PATH="/var/lib/soteria/jwt/"
-SOTERIA_PASSENGER_HASH_LENGTH=15
-SOTERIA_DRIVER_HASH_LENGTH=15
diff --git a/.gitlab/ci/scripts/deploy.sh b/.gitlab/ci/scripts/deploy.sh
deleted file mode 100644
index 708a4039..00000000
--- a/.gitlab/ci/scripts/deploy.sh
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env bash
-set -eu
-
-APP_NAME=soteria
-APP_USERNAME=soteria
-APP_HOME_PATH=/var/lib/${APP_USERNAME}
-APP_PATH=/usr/local/bin/${APP_NAME}/
-APP_GREEN_PATH=/usr/local/bin/${APP_NAME}/${APP_NAME}-green
-APP_RELEASES_PATH=${APP_HOME_PATH}/releases
-APP_LATEST_RELEASE_PATH=${APP_RELEASES_PATH}/"$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA"
-APP_HOSTNAME=${APP_HOSTNAME:?Please specify "APP_HOSTNAME", e.g. "APP_HOSTNAME=emqx-01.app.afra.snapp.infra"};
-
-#TOTAL_APP_NODES=5
-UNHEALTHY_NODE_EXISTS=0
-
-cat << EOF > /etc/resolv.conf
-nameserver 172.16.76.22
-nameserver 172.21.49.230
-EOF
-
-mkdir -p /root/.ssh && touch /root/.ssh/id_rsa
-echo "$DEPLOYER_PRIVATE_KEY" > /root/.ssh/id_rsa
-chmod 0600 /root/.ssh/id_rsa
-
-
-echo -e "\e[33m# Deployment on \e[1m$APP_HOSTNAME ...\e[0m"
-
-if ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" "stat $APP_RELEASES_PATH/$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA/$APP_NAME > /dev/null 2>&1"
-then
- echo -e "\e[33m# The release has alredy exist.\e[0m"
-else
- echo -e "\e[33m# Sending Artifact to Server\e[0m"
- rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz "./artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz" "$APP_USERNAME@$APP_HOSTNAME:$APP_HOME_PATH"
-fi
-
-
-ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" "
- mkdir -p ${APP_RELEASES_PATH} ${APP_LATEST_RELEASE_PATH}
-
- if [ -f "${APP_HOME_PATH}/artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz" ]; then
- echo -e '\e[33m# Extract Release\e[0m'
- tar -xzf ${APP_HOME_PATH}/artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz -C ${APP_LATEST_RELEASE_PATH}
- rm -f ${APP_HOME_PATH}/artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz
- fi
-
- "
-
-echo -e "\e[33m# Trying to connect to 'http://$APP_HOSTNAME' ...\e[0m"
-timeout=60
-
-
-#done
-
-echo
-echo
-
-#for i in $(seq 1 ${TOTAL_APP_NODES}); do
-# APP_HOSTNAME=$(printf "${APP_NAME}-%02d.app.afra.snapp.infra" ${i})
-echo -e "\e[33m# Connecting to Server\e[0m"
- echo -e "\e[33m# Connecting to Server \e[1m$APP_HOSTNAME\e[0m"
- ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" "
- echo -e '\e[33m# Activating Latest Realese\e[0m'
- ln -sf ${APP_LATEST_RELEASE_PATH}/$APP_USERNAME ${APP_PATH}
-
- echo -e '\e[33m# Restarting $APP_NAME ...\e[0m'
- sudo systemctl restart $APP_NAME.service
-
- "
- echo
-#done
diff --git a/.gitlab/ci/scripts/notify-eye.sh b/.gitlab/ci/scripts/notify-eye.sh
deleted file mode 100644
index e6347ca1..00000000
--- a/.gitlab/ci/scripts/notify-eye.sh
+++ /dev/null
@@ -1,14 +0,0 @@
-# To notify matrix about CI job
-
-MATRIX_SERVER="https://matrix.snapp.cab"
-MATRIX_MSQTYPE=m.text
-MX_TXN="`date "+%s"`$(( RANDOM % 9999 ))"
-
-
-BODY="[Gitlab🚀] CI run by *$GITLAB_USER_LOGIN* on the $CI_PROJECT_TITLE project: \n Job name : $CI_JOB_NAME \n Commit : $CI_COMMIT_TITLE \n Job url : $CI_JOB_URL "
-
-# Post into maint room
-curl -X PUT --header 'Content-Type: application/json' --header 'Accept: application/json' -d "{
-\"msgtype\": \"$MATRIX_MSQTYPE\",
-\"body\": \"$BODY\"
- }" "$MATRIX_SERVER/_matrix/client/unstable/rooms/$MATRIX_ROOM_ID/send/m.room.message/$MX_TXN?access_token=$MATRIX_ACCESS_TOKEN" >/dev/nul 2>&1
diff --git a/.gitlab/ci/scripts/update-environments.sh b/.gitlab/ci/scripts/update-environments.sh
deleted file mode 100644
index 50ddbb68..00000000
--- a/.gitlab/ci/scripts/update-environments.sh
+++ /dev/null
@@ -1,93 +0,0 @@
-#!/usr/bin/env bash
-set -eu
-
-APP_NAME=soteria
-APP_USERNAME=soteria
-APP_HOSTNAME="emqx-0${CI_NODE_INDEX}.app.afra.snapp.infra"
-APP_HOME_PATH=/var/lib/${APP_USERNAME}
-APP_CONFIG_PATH="/etc/soteria"
-ENV_DEFAULT_FILE=.gitlab/ci/env/env.conf
-ENV_HOSTNAME=$APP_HOSTNAME
-ENV_SOTERIA_JWT_KEYS_PATH=${ENV_SOTERIA_JWT_KEYS_PATH}
-ENV_THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION=${THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION}
-ENV_PASSENGER_JWT_PUBLIC_KEY_PRODUCTION=${ENV_PASSENGER_JWT_PUBLIC_KEY_PRODUCTION}
-ENV_DRIVER_JWT_PUBLIC_KEY_PRODUCTION=${DRIVER_JWT_PUBLIC_KEY_PRODUCTION}
-ENV_THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION=${THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION}
-
-cat << EOF > /etc/resolv.conf
-nameserver 172.16.76.22
-nameserver 172.21.49.230
-EOF
-
-mkdir -p /root/.ssh && touch /root/.ssh/id_rsa
-echo "$DEPLOYER_PRIVATE_KEY" > /root/.ssh/id_rsa
-chmod 0600 /root/.ssh/id_rsa
-
-sed -i "2s/\(.*\)/\#Last update\:\ $(date)/" $ENV_DEFAULT_FILE
-
-
-while read LINE
-do
-
-
- if echo $LINE | egrep -v "^#|^$" >/dev/null 2>&1
- then
-
- ENV="$(echo $LINE | sed 's/\(.*\)=\(.*\)/\1/g' )"
- ENV_NAME="ENV_$ENV"
- ENV_VALUE="$(echo $LINE | sed 's/\(.*\)=\(.*\)/\2/g' )"
-
- echo -e "\e[33m# Ckeck variable \e[1m$ENV_NAME\e[0m"
-
- if [ -v $ENV_NAME ]
- then
-
- if [ $ENV_VALUE = "'___'" ]
- then
-
- sed -i "s/$ENV\(\s\)*=\(\s\)*'\(.*\)'/$ENV=\'${!ENV_NAME}\'/g" $ENV_DEFAULT_FILE
- [ $? -eq 0 ] && echo -e "\e[32m==> Changed variable \e[1m$ENV_NAME\e[0m \e[32mfrom \e[1m$ENV_VALUE\e[0m \e[32mto \e[1m😜\e[0m "
-
- else
-
- sed -i "s/$ENV\(\s\)*=\(\s\)*'\(.*\)'/$ENV=\'${!ENV_NAME}\'/g" $ENV_DEFAULT_FILE
- [ $? -eq 0 ] && echo -e "\e[32m==> Changed variable \e[1m$ENV_NAME\e[0m \e[32mfrom \e[1m$ENV_VALUE\e[0m \e[32mto \e[1m${!ENV_NAME}\e[0m "
- fi
-
- fi
-
- fi
-
-done < $ENV_DEFAULT_FILE
-
-
-
- rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz "$ENV_DEFAULT_FILE" "$APP_USERNAME@$APP_HOSTNAME:$APP_CONFIG_PATH/$APP_NAME.conf"
- [ $? -eq 0 ] && echo -e "\e[32m# Connecting to Server \e[1m$APP_HOSTNAME\e[0m \e[32m and update \e[1m$APP_CONFIG_PATH/$APP_NAME.conf\e[0m"
- echo
-
- if [ -v UPDATE_ENVIRONMENT_VARIABLE ]
- then
- ssh -o StrictHostKeyChecking=no "$APP_USERNAME@$APP_HOSTNAME" "
-
- echo -e '\e[33m# Restarting $APP_NAME ...\e[0m'
- sudo systemctl restart $APP_NAME.service
-
- "
- fi
-
-## JWT keys
-
-mkdir jwt
-echo "${ENV_DRIVER_JWT_PUBLIC_KEY_PRODUCTION}" > "jwt"/0.pem
-echo "${ENV_PASSENGER_JWT_PUBLIC_KEY_PRODUCTION}" > "jwt"/1.pem
-echo "${ENV_THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION}" > "jwt"/100.pem
-echo "${ENV_THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION}" > "jwt"/100.private.pem
-rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz ./jwt/* "$APP_USERNAME@$APP_HOSTNAME:${ENV_SOTERIA_JWT_KEYS_PATH}"
-rm -r jwt
-
-### Green service config file
-# sed -i "1s/\(.*\)/\# Application: $APP_NAME-green /" $ENV_DEFAULT_FILE
-sed -i "2s/\(.*\)/\# Last update: $(date) /" $ENV_DEFAULT_FILE
-sed -i "s/SOTERIA_HTTP_PORT\(\s\)*=\(\s\)*'\(.*\)'/ENV_NAME=\'9998\'/g" $ENV_DEFAULT_FILE
-rsync -e 'ssh -o "StrictHostKeyChecking=no"' -avz "$ENV_DEFAULT_FILE" "$APP_USERNAME@$APP_HOSTNAME:$APP_CONFIG_PATH/$APP_NAME-green.conf"
diff --git a/.gitlab/ci/templates/build.giltab-ci.yml b/.gitlab/ci/templates/build.giltab-ci.yml
deleted file mode 100644
index b2a6553b..00000000
--- a/.gitlab/ci/templates/build.giltab-ci.yml
+++ /dev/null
@@ -1,23 +0,0 @@
-build:
- image: docker:latest
- stage: build
- variables:
- HTTP_PROXY: ${SNAPP_HTTP_PROXY}
- HTTPS_PROXY: ${SNAPP_HTTPS_PROXY}
- no_proxy: ${SNAPP_NOPROXY}
- script:
- - export CURRENT_DATETIME=$(TZ=Asia/Tehran date '+%FT%T')
- - docker build --build-arg BUILD_DATE=$CURRENT_DATETIME --build-arg VCS_REF=${CI_COMMIT_SHA} --build-arg BUILD_VERSION=${CI_COMMIT_REF_SLUG} -t ${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG} .
- after_script:
- - docker save -o ${CI_PROJECT_NAME}-${CI_COMMIT_REF_SLUG}.tar ${CI_PROJECT_NAME}:${CI_COMMIT_REF_SLUG}
- artifacts:
- name: "docker-image-$CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG"
- paths:
- - ${CI_PROJECT_NAME}-${CI_COMMIT_REF_SLUG}.tar
- expire_in: 1 week
- dependencies:
- - code-generator
- - compile
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
\ No newline at end of file
diff --git a/.gitlab/ci/templates/compile.giltab-ci.yml b/.gitlab/ci/templates/compile.giltab-ci.yml
deleted file mode 100644
index 03dae838..00000000
--- a/.gitlab/ci/templates/compile.giltab-ci.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-.go_mod_config: &go_mod
- image: registry.snapp.tech/docker/golang:1.13.5-alpine3.10
- cache:
- policy: pull
- paths:
- - vendor/
- before_script:
- - go env -w GOPROXY="https://repo.snapp.tech/repository/goproxy/"
-
-compile:
- <<: *go_mod
- stage: compile
- variables:
- GOOS: "linux"
- GOARCH: "amd64"
- CGO_ENABLED: 0
- script:
- - go mod vendor -v
- - go build -mod vendor -v -o ${BUILD_PATH}/${CI_PROJECT_NAME} cmd/soteria/soteria.go
- artifacts:
- name: "$CI_PROJECT_NAME-$CI_COMMIT_REF_SLUG"
- paths:
- - ${BUILD_PATH}/${CI_PROJECT_NAME}
- expire_in: 1 week
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
diff --git a/.gitlab/ci/templates/deploy.chart.giltab-ci.yml b/.gitlab/ci/templates/deploy.chart.giltab-ci.yml
deleted file mode 100644
index 3ac95901..00000000
--- a/.gitlab/ci/templates/deploy.chart.giltab-ci.yml
+++ /dev/null
@@ -1,38 +0,0 @@
----
-lint:helm:soteria:
- stage: lint
- extends: .easy_ci:helm:lint
- variables:
- HELM_CHARTS_DIR: "deployments"
- only:
- - tags
- when: manual
-
-package:helm:soteria:
- stage: build
- extends: .easy_ci:helm:package
- variables:
- HELM_CHARTS_DIR: "deployments"
- HELM_PACKAGE_DIR: "helm_packages"
- needs: ["lint:helm:soteria"]
- artifacts:
- name: "HELM_PACKAGES-$CI_COMMIT_REF_SLUG"
- paths:
- - $HELM_PACKAGE_DIR
- expire_in: 1h
- only:
- - tags
- when: manual
-
-chart:helm::soteria:
- stage: release
- extends: .easy_ci:helm:push
- variables:
- HELM_PACKAGE_DIR: "helm_packages"
- HELM_REPO_ADDRESS: "$DISPATCHING_HELM_REPO_ADDRESS"
- HELM_REPO_USER: "$DISPATCHING_HELM_REPO_USER"
- HELM_REPO_PASS: "$DISPATCHING_HELM_REPO_PASS"
- needs: ["package:helm:soteria"]
- only:
- - tags
- when: manual
diff --git a/.gitlab/ci/templates/deploy.cloud.giltab-ci.yml b/.gitlab/ci/templates/deploy.cloud.giltab-ci.yml
deleted file mode 100644
index e82f44a8..00000000
--- a/.gitlab/ci/templates/deploy.cloud.giltab-ci.yml
+++ /dev/null
@@ -1,124 +0,0 @@
-.deploy_template: &deploy_job_definition
- image: openshift/origin-cli:v3.10
- stage: deploy
- before_script:
- - oc version
- - oc login https://${DEPLOY_ADDRESS} --token=${DEPLOY_TOKEN}
- - oc project ${DEPLOY_PROJECT}
- - - oc process -f .okd/DeploymentConfig.yaml --param APP_IMAGE=${CONTAINER_REGISTRY_IMAGE} --param SERVICE_NAME=${DEPLOY_SERVICE_NAME} | oc apply -f -
- - |
- oc process -f .okd/ConfigMap.yaml \
- --param SERVICE_NAME="${DEPLOY_SERVICE_NAME}" \
- --param APP_VERSION="${PROJECT_VERSION}" \
- --param REDIS_ADDR="${DEPLOY_REDIS_ADDR}" \
- --param REDIS_PASS="${DEPLOY_REDIS_PASS}" \
- --param TRACER_ENABLED=${DEPLOY_TRACER_ENABLED} \
- --param TRACER_HOST=${DEPLOY_TRACER_HOST} \
- --param CI_COMMIT_REF_SLUG="${CI_COMMIT_REF_SLUG}" | oc apply -f -
- - |
- oc process -f .okd/Secret.yaml \
- --param SERVICE_NAME="${DEPLOY_SERVICE_NAME}" \
- --param APP_VERSION="${PROJECT_VERSION}" \
- --param CI_COMMIT_REF_SLUG="${CI_COMMIT_REF_SLUG}" \
- --param PASSENGER_HASH_SALT="${DEPLOY_PASSENGER_SALT}" \
- --param PASSENGER_HASH_LENGTH=15 \
- --param DRIVER_HASH_SALT="${DEPLOY_DRIVER_SALT}" \
- --param DRIVER_HASH_LENGTH=15 \
- --param DRIVER_JWT_PUBLIC_KEY="${DEPLOY_DRIVER_JWT_PUBLIC_KEY}" \
- --param PASSENGER_JWT_PUBLIC_KEY="${DEPLOY_PASSENGER_JWT_PUBLIC_KEY}" \
- --param THIRD_PARTY_JWT_PUBLIC_KEY="${DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY}" \
- --param THIRD_PARTY_JWT_PRIVATE_KEY="${DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY}" | oc apply -f -
- - |
- oc process -f .okd/AutoScaler.yaml \
- --param MIN_REPLICA=${DEPLOY_MIN_REPLICA} \
- --param MAX_REPLICA=${DEPLOY_MAX_REPLICA} \
- --param CPU_UTILIZATION=${DEPLOY_CPU_UTILIZATION} | oc apply -f -
- - oc process -f .okd/Route.yaml --param APP_HOST=${DEPLOY_SERVICE_NAME} --param SERVICE_NAME=${DEPLOY_SERVICE_NAME} | oc apply -f -
- - oc process -f .okd/Service.yaml --param SERVICE_NAME=${DEPLOY_SERVICE_NAME} | oc apply -f -
- script:
- - oc rollout latest dc/${DEPLOY_SERVICE_NAME}
- only:
- - tags
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
- when: manual
-
-deploy:staging:teh_1:
- <<: *deploy_job_definition
- variables:
- DEPLOY_MIN_REPLICA: "1"
- DEPLOY_MAX_REPLICA: "2"
- DEPLOY_CPU_UTILIZATION: "80"
- CONTAINER_REGISTRY_IMAGE: "$PROJECT_STAGING/${PROJECT_SERVICE_NAME}:$CI_COMMIT_REF_SLUG"
- DEPLOY_ADDRESS: "${OKD_TEH1_CLUSTER_ADDRESS}"
- DEPLOY_PROJECT: "${PROJECT_STAGING}"
- DEPLOY_TOKEN: "${OKD_STAGING_TOKEN}"
- DEPLOY_PASSENGER_SALT: "${PASSENGER_SALT_DEV}"
- DEPLOY_DRIVER_SALT: "${DRIVER_SALT_DEV}"
- DEPLOY_SERVICE_NAME: "${PROJECT_SERVICE_NAME}-staging"
- DEPLOY_DRIVER_JWT_PUBLIC_KEY: "${DRIVER_JWT_PUBLIC_KEY_MOZART}"
- DEPLOY_PASSENGER_JWT_PUBLIC_KEY: "${PASSENGER_JWT_PUBLIC_KEY_MOZART}"
- DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY: "${THIRD_PARTY_JWT_PUBLIC_KEY_MOZART}"
- DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY: "${THIRD_PARTY_JWT_PRIVATE_KEY_MOZART}"
- DEPLOY_REDIS_ADDR: "soteria-redis-haproxy:6379"
- DEPLOY_TRACER_ENABLED: "false"
- DEPLOY_TRACER_HOST: "localhost"
-
- only:
- - master
- - branches
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
- needs: ["release:staging"]
-
-deploy:production:teh_1:
- <<: *deploy_job_definition
-# Extends the global template so it add an after_script step for deployment announcement
- extends: .deployments_after_script
- variables:
- DEPLOY_MIN_REPLICA: "3"
- DEPLOY_MAX_REPLICA: "20"
- DEPLOY_CPU_UTILIZATION: "65"
- CONTAINER_REGISTRY_IMAGE: "$PROJECT_PRODUCTION_TEH1/$PROJECT_SERVICE_NAME:$CI_COMMIT_REF_SLUG"
- DEPLOY_ADDRESS: "${OKD_TEH1_CLUSTER_ADDRESS}"
- DEPLOY_PROJECT: "${PROJECT_PRODUCTION_TEH1}"
- DEPLOY_TOKEN: "${OKD_PRODUCTION_TOKEN_TEH1}"
- DEPLOY_PASSENGER_SALT: "${PASSENGER_SALT_PRODUCTION}"
- DEPLOY_DRIVER_SALT: "${DRIVER_SALT_PRODUCTION}"
- DEPLOY_SERVICE_NAME: "${PROJECT_SERVICE_NAME}"
- DEPLOY_DRIVER_JWT_PUBLIC_KEY: "${DRIVER_JWT_PUBLIC_KEY_PRODUCTION}"
- DEPLOY_PASSENGER_JWT_PUBLIC_KEY: "${PASSENGER_JWT_PUBLIC_KEY_PRODUCTION}"
- DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY: "${THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION}"
- DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY: "${THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION}"
- DEPLOY_REDIS_ADDR: "soteria-redis-haproxy.realtime-production.svc:6379"
- CI_DEPLOYMENT_INFRA: "teh-1"
- DEPLOY_TRACER_ENABLED: "false"
- DEPLOY_TRACER_HOST: "localhost"
- needs: ["release:production:teh-1"]
-
-deploy:production:teh_2:
- <<: *deploy_job_definition
-# Extends the global template so it add an after_script step for deployment announcement
- extends: .deployments_after_script
- variables:
- DEPLOY_MIN_REPLICA: "3"
- DEPLOY_MAX_REPLICA: "20"
- DEPLOY_CPU_UTILIZATION: "70"
- CONTAINER_REGISTRY_IMAGE: "$PROJECT_PRODUCTION_TEH2/$PROJECT_SERVICE_NAME:$CI_COMMIT_REF_SLUG"
- DEPLOY_ADDRESS: "${OKD_TEH2_CLUSTER_ADDRESS}"
- DEPLOY_PROJECT: "${PROJECT_PRODUCTION_TEH2}"
- DEPLOY_TOKEN: "${OKD_PRODUCTION_TOKEN_TEH2}"
- DEPLOY_PASSENGER_SALT: "${PASSENGER_SALT_PRODUCTION}"
- DEPLOY_DRIVER_SALT: "${DRIVER_SALT_PRODUCTION}"
- DEPLOY_SERVICE_NAME: "${PROJECT_SERVICE_NAME}"
- DEPLOY_DRIVER_JWT_PUBLIC_KEY: "${DRIVER_JWT_PUBLIC_KEY_PRODUCTION}"
- DEPLOY_PASSENGER_JWT_PUBLIC_KEY: "${PASSENGER_JWT_PUBLIC_KEY_PRODUCTION}"
- DEPLOY_THIRD_PARTY_JWT_PUBLIC_KEY: "${THIRD_PARTY_JWT_PUBLIC_KEY_PRODUCTION}"
- DEPLOY_THIRD_PARTY_JWT_PRIVATE_KEY: "${THIRD_PARTY_JWT_PRIVATE_KEY_PRODUCTION}"
- DEPLOY_REDIS_ADDR: "soteria-redis-haproxy.realtime-production.svc:6379"
- CI_DEPLOYMENT_INFRA: "teh-2"
- DEPLOY_TRACER_ENABLED: "false"
- DEPLOY_TRACER_HOST: "localhost"
- needs: ["release:production:teh-2"]
diff --git a/.gitlab/ci/templates/deploy.env.giltab-ci.yml b/.gitlab/ci/templates/deploy.env.giltab-ci.yml
deleted file mode 100644
index 8eaf3e89..00000000
--- a/.gitlab/ci/templates/deploy.env.giltab-ci.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-update:env:
- image: registry.snapp.tech/docker/deployer:alpine3.11
- stage: deploy
- parallel: 5
- variables:
- DEPLOYER_PRIVATE_KEY: ${DEPLOYER_PRIVATE_KEY}
- script:
- - chmod +x .gitlab/ci/scripts/update-environments.sh
- - echo "Update ENV Soteria-0${CI_NODE_INDEX}"
- - .gitlab/ci/scripts/update-environments.sh
- - chmod +x .gitlab/ci/scripts/notify-eye.sh
- - echo "Send notif to Dispatching-alert room ..."
- - .gitlab/ci/scripts/notify-eye.sh
- rules:
- - if: '$UPDATE_ENVIRONMENT_VARIABLE == "true"'
- when: manual
\ No newline at end of file
diff --git a/.gitlab/ci/templates/deploy.vm.giltab-ci.yml b/.gitlab/ci/templates/deploy.vm.giltab-ci.yml
deleted file mode 100644
index 4af5c32c..00000000
--- a/.gitlab/ci/templates/deploy.vm.giltab-ci.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-deploy:production:vm:
-# Extends the global template so it add an after_script step for deployment announcement
- extends: .deployments_after_script
- image: registry.snapp.tech/docker/deployer:alpine3.11
- stage: deploy
- parallel: 5
- variables:
-# Add a variable to specify where deployment will be happened for announcement
- CI_DEPLOYMENT_INFRA: "Afranet"
- DEPLOYER_PRIVATE_KEY: ${DEPLOYER_PRIVATE_KEY}
- APP_HOSTNAME: "emqx-0${CI_NODE_INDEX}.app.afra.snapp.infra"
- before_script:
- - tar -C ${BUILD_PATH} -cvzf "artifacts-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA.tar.gz" ${CI_PROJECT_NAME}
- script:
- - chmod +x ./.gitlab/ci/scripts/deploy.sh
- - echo "Deploy Soteria-0${CI_NODE_INDEX}"
- - ./.gitlab/ci/scripts/deploy.sh
- needs: ["compile"]
- only:
- - tag
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
- when: manual
-
-deploy:env:
- image: registry.snapp.tech/docker/deployer:alpine3.11
- stage: deploy
- parallel: 5
- variables:
- DEPLOYER_PRIVATE_KEY: ${DEPLOYER_PRIVATE_KEY}
- script:
- - chmod +x .gitlab/ci/scripts/update-environments.sh
- - echo "Update ENV Soteria-0${CI_NODE_INDEX}"
- - .gitlab/ci/scripts/update-environments.sh
- - chmod +x .gitlab/ci/scripts/notify-eye.sh
- - echo "Send notif to Dispatching-alert room ..."
- - .gitlab/ci/scripts/notify-eye.sh
- needs: ['compile']
- only:
- - tag
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
- when: manual
diff --git a/.gitlab/ci/templates/precompile.giltab-ci.yml b/.gitlab/ci/templates/precompile.giltab-ci.yml
deleted file mode 100644
index 1dceda4c..00000000
--- a/.gitlab/ci/templates/precompile.giltab-ci.yml
+++ /dev/null
@@ -1,17 +0,0 @@
-code-generator:
- stage: pre-compile
- image:
- name: namely/protoc-all
- entrypoint: [""]
- script:
- - entrypoint.sh -d ./internal/web/grpc/contracts -l go -o ./internal/web/grpc/contracts --with-gateway --go-source-relative
- artifacts:
- paths:
- - ./internal/web/grpc/contracts
- expire_in: 1 week
-# cache:
-# paths:
-# - ./web/grpc/contracts
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
\ No newline at end of file
diff --git a/.gitlab/ci/templates/release.giltab-ci.yml b/.gitlab/ci/templates/release.giltab-ci.yml
deleted file mode 100644
index 17bc823f..00000000
--- a/.gitlab/ci/templates/release.giltab-ci.yml
+++ /dev/null
@@ -1,97 +0,0 @@
----
-release:staging:
- extends: .easy_ci:release:docker
- variables:
- RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH1_REGISTRY"
- RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH1_REGISTRY/$PROJECT_STAGING/$CI_PROJECT_NAME"
- RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci"
- RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_STAGING_TOKEN"
- dependencies:
- - build
- only:
- - master
- - tags
- - branches
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
-
-release:mozart:
- extends: .easy_ci:release:docker
- variables:
- RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH1_REGISTRY"
- RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH1_REGISTRY/$PROJECT_MOZART/$CI_PROJECT_NAME"
- RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci"
- RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_MOZART_TOKEN"
- dependencies:
- - build
- only:
- - master
- - tags
- - branches
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
- when: manual
-
-release:production:teh-1:
- extends: .easy_ci:release:docker
- variables:
- RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH1_REGISTRY"
- RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH1_REGISTRY/$PROJECT_PRODUCTION_TEH1/$CI_PROJECT_NAME"
- RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci"
- RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_PRODUCTION_TOKEN_TEH1"
- dependencies:
- - build
- only:
- - tags
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
-
-release:production:teh-2:
- extends: .easy_ci:release:docker
- variables:
- RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD_TEH2_REGISTRY"
- RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD_TEH2_REGISTRY/$PROJECT_PRODUCTION_TEH2/$CI_PROJECT_NAME"
- RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci"
- RELEASE_CONTAINER_REGISTRY_PASSWORD: "$OKD_PRODUCTION_TOKEN_TEH2"
- dependencies:
- - build
- only:
- - tags
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
-
-release:production:okd4:teh-1:
- extends: .easy_ci:release:docker
- variables:
- OKD4_TEH1_REGISTRY: "image-registry.apps.private.okd4.teh-1.snappcloud.io"
- RELEASE_CONTAINER_REGISTRY_ADDRESS: "$OKD4_TEH1_REGISTRY"
- RELEASE_CONTAINER_REGISTRY_IMAGE: "$OKD4_TEH1_REGISTRY/realtime-production/$CI_PROJECT_NAME"
- RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci"
- RELEASE_CONTAINER_REGISTRY_PASSWORD: ${OKD4_TEH1_REALTIME_PRODUCTION_TOKEN}
- dependencies:
- - build
- only:
- - tags
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
-
-release:production:baly:
- extends: .easy_ci:release:docker
- variables:
- BALY_REGISTRY: "registry.cloud.baly.app"
- RELEASE_CONTAINER_REGISTRY_ADDRESS: "$BALY_REGISTRY"
- RELEASE_CONTAINER_REGISTRY_IMAGE: "$BALY_REGISTRY/baly-dispatching/$CI_PROJECT_NAME"
- RELEASE_CONTAINER_REGISTRY_USERNAME: "gitlab-ci"
- RELEASE_CONTAINER_REGISTRY_PASSWORD: ${BALY_DISPATCHING_PRODUCTION_TOKEN}
- dependencies:
- - build
- only:
- - tags
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
diff --git a/.gitlab/ci/templates/test.giltab-ci.yml b/.gitlab/ci/templates/test.giltab-ci.yml
deleted file mode 100644
index 58e9c092..00000000
--- a/.gitlab/ci/templates/test.giltab-ci.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-.go_mod_config: &go_mod
- image: registry.snapp.tech/docker/golang:1.13.5-alpine3.10
- cache:
- policy: pull
- paths:
- - vendor/
- before_script:
- - go env -w GOPROXY="https://repo.snapp.tech/repository/goproxy/"
-
-unit_tests:
- <<: *go_mod
- stage: test
- coverage: '/total:\s*\(statements\)\s*([\d.]+)%/'
- script:
- - go test -gcflags=-l -v -coverprofile .coverage.out.tmp ./...
- - cat .coverage.out.tmp | grep -v "mock.go" > .coverage.out
- - rm -rf .coverage.out.tmp
- - go tool cover -func .coverage.out
- dependencies:
- - code-generator
- - compile
- except:
- variables:
- - $UPDATE_ENVIRONMENT_VARIABLE
\ No newline at end of file
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 00000000..b2bc0bd4
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,31 @@
+---
+linters:
+ enable-all: true
+ disable:
+ - depguard
+ # we don't use json with camel-case
+ - tagliatelle
+ - nolintlint
+ # it should improve to support more known patterns
+ - varnamelen
+ - ireturn
+ # deprecated linters
+ - gomnd
+ - execinquery
+ - exportloopref
+
+linters-settings:
+ wrapcheck:
+ ignoreSigs:
+ - .JSON
+ - .NewHTTPError
+ - .Redirect
+ - .NoContent
+ - .Errorf(
+ - errors.New(
+ - errors.Unwrap(
+ - .Wrap(
+ - .Wrapf(
+ - .WithMessage(
+ - .WithMessagef(
+ - .WithStack(
diff --git a/.okd/.env b/.okd/.env
deleted file mode 100644
index 33e14e43..00000000
--- a/.okd/.env
+++ /dev/null
@@ -1,80 +0,0 @@
-GJS_DEBUG_TOPICS=JS ERROR;JS LOG
-LC_TIME=az_IR
-USER=amin
-TEXTDOMAIN=im-config
-XDG_SEAT=seat0
-XDG_SESSION_TYPE=x11
-SSH_AGENT_PID=6340
-SHLVL=1
-QT4_IM_MODULE=xim
-OLDPWD=/home/amin/Development/snapp/soteria/.okd
-HOME=/home/amin
-LESS=-R
-DESKTOP_SESSION=ubuntu
-_INTELLIJ_FORCE_SET_GOPATH=/home/amin/go
-TERMINAL_EMULATOR=JetBrains-JediTerm
-_INTELLIJ_FORCE_SET_GOPROXY=direct
-GIO_LAUNCHED_DESKTOP_FILE=/home/amin/.local/share/applications/jetbrains-goland.desktop
-ZSH=/home/amin/.oh-my-zsh
-LSCOLORS=Gxfxcxdxbxegedabagacad
-GTK_MODULES=gail:atk-bridge
-GNOME_SHELL_SESSION_MODE=ubuntu
-PAGER=less
-LC_MONETARY=az_IR
-GOROOT=/usr/local/go
-DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
-GIO_LAUNCHED_DESKTOP_FILE_PID=15825
-GOFLAGS= -mod=vendor
-IM_CONFIG_PHASE=2
-_INTELLIJ_FORCE_SET_PATH=/usr/local/go/bin:/home/amin/.cargo/bin:/home/amin/go/bin:/usr/local/go/bin:/home/amin/.local/bin:/home/amin/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/amin/Development/flutter/bin:/home/amin/go/bin
-MANDATORY_PATH=/usr/share/gconf/ubuntu.mandatory.path
-GO111MODULE=on
-GTK_IM_MODULE=ibus
-LOGNAME=amin
-_=/home/amin/Development/snapp/soteria/.okd/./deploy.sh
-DEFAULTS_PATH=/usr/share/gconf/ubuntu.default.path
-USERNAME=amin
-XDG_SESSION_ID=3
-TERM=xterm-256color
-SERVICE_NAME=soteria
-GNOME_DESKTOP_SESSION_ID=this-is-deprecated
-WINDOWPATH=2
-PATH=/usr/local/go/bin:/home/amin/.cargo/bin:/home/amin/go/bin:/usr/local/go/bin:/home/amin/.local/bin:/home/amin/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/amin/Development/flutter/bin:/home/amin/go/bin
-__INTELLIJ_COMMAND_HISTFILE__=/home/amin/.GoLand2019.3/config/terminal/history/history-254
-SESSION_MANAGER=local/amin-ThinkPad-E490:@/tmp/.ICE-unix/6244,unix/amin-ThinkPad-E490:/tmp/.ICE-unix/6244
-S_COLORS=auto
-XDG_RUNTIME_DIR=/run/user/1000
-XDG_MENU_PREFIX=gnome-
-LC_ADDRESS=az_IR
-DISPLAY=:0
-LC_TELEPHONE=az_IR
-XDG_CURRENT_DESKTOP=ubuntu:GNOME
-LANG=en_US.UTF-8
-XDG_SESSION_DESKTOP=ubuntu
-XAUTHORITY=/run/user/1000/gdm/Xauthority
-XMODIFIERS=@im=ibus
-LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:
-SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
-LC_NAME=az_IR
-GOPROXY=direct
-GOPATH=/home/amin/go
-SHELL=/usr/bin/zsh
-GDMSESSION=ubuntu
-QT_ACCESSIBILITY=1
-LC_MEASUREMENT=az_IR
-TEXTDOMAINDIR=/usr/share/locale/
-LC_IDENTIFICATION=az_IR
-GPG_AGENT_INFO=/run/user/1000/gnupg/S.gpg-agent:0:1
-GJS_DEBUG_OUTPUT=stderr
-_INTELLIJ_FORCE_SET_GOROOT=/usr/local/go
-QT_IM_MODULE=ibus
-XDG_VTNR=2
-PWD=/home/amin/Development/snapp/soteria/.okd
-_INTELLIJ_FORCE_SET_GOFLAGS= -mod=vendor
-ZDOTDIR=
-XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg
-XDG_DATA_DIRS=/usr/share/ubuntu:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop
-CLUTTER_IM_MODULE=xim
-_INTELLIJ_FORCE_SET_GO111MODULE=on
-LC_NUMERIC=az_IR
-LC_PAPER=az_IR
diff --git a/.okd/AutoScaler.yaml b/.okd/AutoScaler.yaml
deleted file mode 100644
index ab27cb8b..00000000
--- a/.okd/AutoScaler.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-scaler-tpl
-objects:
-- apiVersion: autoscaling/v1
- kind: HorizontalPodAutoscaler
- metadata:
- labels:
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- scaleTargetRef:
- kind: DeploymentConfig
- name: ${SERVICE_NAME}
- apiVersion: v1
- subresource: scale
- minReplicas: ${{MIN_REPLICA}}
- maxReplicas: ${{MAX_REPLICA}}
- targetCPUUtilizationPercentage: ${{CPU_UTILIZATION}}
-
-parameters:
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: "soteria"
-- name: MIN_REPLICA
- displayName: Minimum number of replica
- required: true
-- name: MAX_REPLICA
- displayName: Maximum number of replica
- required: true
-- name: CPU_UTILIZATION
- displayName: Target CPU Utilization Percentage
- required: true
\ No newline at end of file
diff --git a/.okd/ConfigMap.yaml b/.okd/ConfigMap.yaml
deleted file mode 100644
index 00e8b4de..00000000
--- a/.okd/ConfigMap.yaml
+++ /dev/null
@@ -1,71 +0,0 @@
----
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-configmap
-objects:
- - apiVersion: v1
- kind: ConfigMap
- metadata:
- name: ${SERVICE_NAME}-env
- labels:
- app: "${SERVICE_NAME}"
- app.snappcloud.io/managed-by: "${MANAGED_BY}"
- app.snappcloud.io/version: "${APP_VERSION}"
- app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}"
- data:
- SOTERIA_HTTP_PORT: "9999"
- SOTERIA_GRPC_PORT: "50051"
- SOTERIA_REDIS_ADDRESS: "${REDIS_ADDR}"
- SOTERIA_REDIS_PASS: "${REDIS_PASS}"
- SOTERIA_REDIS_POOL_SIZE: "10"
- SOTERIA_REDIS_MAX_RETRIES: "0"
- SOTERIA_REDIS_READ_TIMEOUT: "3s"
- SOTERIA_REDIS_POOL_TIMEOUT: "4s"
- SOTERIA_REDIS_MIN_RETRY_BACKOFF: "8ms"
- SOTERIA_REDIS_MAX_RETRY_BACKOFF: "512ms"
- SOTERIA_REDIS_IDLE_TIMEOUT: "300s"
- SOTERIA_REDIS_IDLE_CHECK_FREQUENCY: "60s"
- SOTERIA_REDIS_SET_MEMBER_EXP_TIME: "300s"
- SOTERIA_REDIS_MIN_IDLE_CONNECTIONS: "5"
- SOTERIA_LOGGER_LEVEL: "${LOG_LEVEL}"
- SOTERIA_LOGGER_SENTRY_ENABLED: "false"
- SOTERIA_LOGGER_SENTRY_DSN: ""
- SOTERIA_LOGGER_SENTRY_TIMEOUT: "500ms"
- SOTERIA_ALLOWED_ACCESS_TYPES: "sub,pub"
- SOTERIA_CACHE_ENABLED: "true"
- SOTERIA_CACHE_EXPIRATION: "600s"
- SOTERIA_TRACER_ENABLED: "${TRACER_ENABLED}"
- SOTERIA_TRACER_SERVICE_NAME: "soteria"
- SOTERIA_TRACER_SAMPLER_TYPE: "const"
- SOTERIA_TRACER_SAMPLER_PARAM: "1"
- SOTERIA_TRACER_HOST: "${TRACER_HOST}"
- SOTERIA_TRACER_PORT: "6831"
- SOTERIA_MODE: "${GIN_MODE}"
-
-parameters:
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
-- name: LOG_LEVEL
- value: "ERROR"
- displayName: Log Level
- description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF)
-- name: GIN_MODE
- value: "release"
-- name: REDIS_ADDR
- required: true
-- name: REDIS_PASS
- required: false
-- name: TRACER_ENABLED
- value: "false"
-- name: TRACER_HOST
- required: false
-- name: MANAGED_BY
- displayName: Managed by
- required: true
- value: "dispatching-team"
-- name: APP_VERSION
- required: true
-- name: CI_COMMIT_REF_SLUG
- required: true
diff --git a/.okd/DeploymentConfig.yaml b/.okd/DeploymentConfig.yaml
deleted file mode 100644
index 4d540a03..00000000
--- a/.okd/DeploymentConfig.yaml
+++ /dev/null
@@ -1,116 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-deployment-config
-objects:
- - apiVersion: v1
- kind: DeploymentConfig
- metadata:
- labels:
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- selector:
- app: ${SERVICE_NAME}
- strategy:
- rollingParams:
- intervalSeconds: 1
- maxSurge: 5
- maxUnavailable: 0
- timeoutSeconds: 600
- updatePeriodSeconds: 10
- type: Rolling
- template:
- metadata:
- annotations:
- prometheus.io/scrape: "${METRICS_ENABLED}"
- prometheus.io/path: "/metrics"
- prometheus.io/port: "9999"
- labels:
- app: ${SERVICE_NAME}
- spec:
- containers:
- - name: ${SERVICE_NAME}-app
- image: "docker-registry.default.svc:5000/${APP_IMAGE}"
- imagePullPolicy: Always
- ports:
- - containerPort: 9999
- protocol: TCP
- - containerPort: 50051
- protocol: TCP
- resources:
- limits:
- memory: "${MEMORY_LIMIT}"
- cpu: "${CPU_LIMIT}"
- requests:
- memory: "${MEMORY_REQUEST}"
- cpu: "${CPU_REQUEST}"
- readinessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 23
- successThreshold: 1
- tcpSocket:
- port: 9999
- timeoutSeconds: 2
- livenessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 29
- successThreshold: 1
- tcpSocket:
- port: 9999
- timeoutSeconds: 2
- envFrom:
- - secretRef:
- name: "${SERVICE_NAME}-auth-env"
- - configMapRef:
- name: "${SERVICE_NAME}-env"
- env:
- - name: HTTP_PROXY
- - name: HTTPS_PROXY
- volumeMounts:
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/100.private.pem"
- subPath: 100.private.pem
- readOnly: true
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/100.pem"
- subPath: 100.pem
- readOnly: true
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/1.pem"
- subPath: 1.pem
- readOnly: true
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/0.pem"
- subPath: 0.pem
- readOnly: true
- volumes:
- - name: "${SERVICE_NAME}-jwt-keys"
- secret:
- secretName: "${SERVICE_NAME}-jwt-keys"
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
- triggers: []
-parameters:
- - name: APP_IMAGE
- displayName: Application Docker Image
- description: application will pull and run this docker image. It has to be in Snapp Cloud registery
- value: "$CONTAINER_REGISTRY_IMAGE"
- - name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- - name: MEMORY_LIMIT
- value: "1024Mi"
- - name: MEMORY_REQUEST
- value: "512Mi"
- - name: CPU_LIMIT
- value: "2"
- - name: CPU_REQUEST
- value: "1"
- - name: METRICS_ENABLED
- value: "true"
- displayName: Metrics Endpoint?
- description: /metrics endpoint will be available for Prometheus
diff --git a/.okd/Route.yaml b/.okd/Route.yaml
deleted file mode 100644
index 18b499ad..00000000
--- a/.okd/Route.yaml
+++ /dev/null
@@ -1,62 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-route
-objects:
-- apiVersion: v1
- kind: Route
- metadata:
- labels:
- app: ${SERVICE_NAME}
- router: inter-dc
- name: ${SERVICE_NAME}-inter-dc
- spec:
- host: ${APP_HOST}.apps.inter-dc.${DC}.snappcloud.io
- port:
- targetPort: 9999-tcp
- to:
- kind: Service
- name: ${SERVICE_NAME}
- weight: 100
- wildcardPolicy: None
-- apiVersion: v1
- kind: Route
- metadata:
- annotations:
- haproxy.router.openshift.io/ip_whitelist: "${IP_WHITE_LIST}"
- labels:
- app: ${SERVICE_NAME}
- router: "public"
- name: "${SERVICE_NAME}-publi"
- spec:
- host: "${APP_HOST}.apps.public.${DC}.snappcloud.io"
- port:
- targetPort: 9999-tcp
- tls:
- termination: edge
- insecureEdgeTerminationPolicy: Redirect
- to:
- kind: Service
- name: ${SERVICE_NAME}
- weight: 100
- wildcardPolicy: None
-
-parameters:
-- name: APP_HOST
- required: true
- displayName: Application host
-- name: PORT
- displayName: Port
- description: the port which will be open by your application
- value: "9999"
-- name: IP_WHITE_LIST
- displayName: IP White list
- description: The valid IP Addresses which can send request to public route
- value: "*"
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: "soteria"
-- name: DC
- displayName: Data center
- value: "teh-1"
diff --git a/.okd/Secret.yaml b/.okd/Secret.yaml
deleted file mode 100644
index fc84574c..00000000
--- a/.okd/Secret.yaml
+++ /dev/null
@@ -1,81 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: "${SERVICE_NAME}-secret"
-objects:
- - apiVersion: v1
- kind: Secret
- type: Opaque
- metadata:
- name: "${SERVICE_NAME}-auth-env"
- labels:
- app: "${SERVICE_NAME}"
- app.snappcloud.io/managed-by: "${MANAGED_BY}"
- app.snappcloud.io/version: "${APP_VERSION}"
- app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}"
- stringData:
- SOTERIA_JWT_KEYS_PATH: "${JWT_KEYS_PATH}"
- SOTERIA_DRIVER_SALT: "${DRIVER_HASH_SALT}"
- SOTERIA_PASSENGER_SALT: "${PASSENGER_HASH_SALT}"
- SOTERIA_DRIVER_HASH_LENGTH: "${DRIVER_HASH_LENGTH}"
- SOTERIA_PASSENGER_HASH_LENGTH: "${PASSENGER_HASH_LENGTH}"
- - apiVersion: v1
- kind: Secret
- type: Opaque
- metadata:
- name: "${SERVICE_NAME}-jwt-keys"
- labels:
- app: "${SERVICE_NAME}"
- app.snappcloud.io/managed-by: "${MANAGED_BY}"
- app.snappcloud.io/version: "${APP_VERSION}"
- app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}"
- stringData:
- 0.pem: "${DRIVER_JWT_PUBLIC_KEY}"
- 1.pem: "${PASSENGER_JWT_PUBLIC_KEY}"
- 100.pem: "${THIRD_PARTY_JWT_PUBLIC_KEY}"
- 100.private.pem: "${THIRD_PARTY_JWT_PRIVATE_KEY}"
-
-parameters:
- - name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- - name: APP_VERSION
- required: true
- - name: MANAGED_BY
- displayName: Managed by
- required: true
- value: "dispatching-team"
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- - name: PASSENGER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- required: true
- - name: PASSENGER_HASH_LENGTH
- displayName: Minimum length of passenger generated hash for HashIds
- - name: DRIVER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- required: true
- - name: DRIVER_HASH_LENGTH
- displayName: Minimum length of driver generated hash for HashIds
- - name: JWT_KEYS_PATH
- displayName: Private Key Path
- description: Private key Path
- value: "/jwt_pems/"
- required: true
- - name: THIRD_PARTY_JWT_PRIVATE_KEY
- displayName: Third party Private Key
- description: Private key of third party's token
- required: true
- - name: DRIVER_JWT_PUBLIC_KEY
- displayName: Driver public key
- description: Public Key of driver's token
- required: true
- - name: PASSENGER_JWT_PUBLIC_KEY
- displayName: Passenger public key
- description: Public Key of passenger's token
- required: true
- - name: THIRD_PARTY_JWT_PUBLIC_KEY
- displayName: Third party public key
- description: Public key of third parties
- required: true
diff --git a/.okd/Service.yaml b/.okd/Service.yaml
deleted file mode 100644
index 8d503d52..00000000
--- a/.okd/Service.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-svc-tpl
-objects:
-- apiVersion: v1
- kind: Service
- metadata:
- annotations:
- labels:
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- ports:
- - name: 9999-tcp
- port: 9999
- protocol: TCP
- targetPort: 9999
- - name: 50051-tcp
- port: 50051
- protocol: TCP
- targetPort: 50051
- selector:
- app: ${SERVICE_NAME}
- sessionAffinity: None
- type: ClusterIP
-parameters:
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: "soteria"
-- name: PORT
- displayName: Port
- description: the port which will be open by your application
- value: "9999"
diff --git a/.okd/Template.yaml b/.okd/Template.yaml
deleted file mode 100644
index c8e629b6..00000000
--- a/.okd/Template.yaml
+++ /dev/null
@@ -1,218 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: emq-auth-module
- annotations:
- description: "EMQ Authentication/Autherization (ACL/AUTH)"
- iconClass: "go-gopher"
- tags: "emq,realtime"
-objects:
-- apiVersion: v1
- kind: Template
- metadata:
- name: ${SERVICE_NAME}
- objects:
- - apiVersion: autoscaling/v1
- kind: HorizontalPodAutoscaler
- metadata:
- labels:
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- scaleTargetRef:
- kind: DeploymentConfig
- name: ${SERVICE_NAME}
- apiVersion: v1
- subresource: scale
- minReplicas: ${MIN_REPLICA}
- maxReplicas: ${MAX_REPLICA}
- targetCPUUtilizationPercentage: ${CPU_UTILIZATION}
- - apiVersion: v1
- kind: ConfigMap
- metadata:
- name: "${SERVICE_NAME}-jwt-keys"
- labels:
- app: "${SERVICE_NAME}"
-
- - apiVersion: v1
- kind: Template
- metadata:
- name: ${SERVICE_NAME}
- objects:
- - apiVersion: v1
- kind: Route
- metadata:
- labels:
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- host: ${APP_HOST}.apps.private.teh-1.snappcloud.io
- port:
- targetPort: ${PORT}-tcp
- tls:
- termination: edge
- insecureEdgeTerminationPolicy: Redirect
- to:
- kind: Service
- name: ${SERVICE_NAME}
- weight: 100
- wildcardPolicy: None
- - apiVersion: v1
- kind: Secret
- type: Opaque
- metadata:
- name: "${SERVICE_NAME}-auth-env"
- labels:
- app: "${SERVICE_NAME}"
- stringData:
- CHANNEL_PREFIX: "emqch-"
- JWT_BASE_PATH: "/jwt_pems/"
- PASSENGER_HASH_SALT: "${PASSENGER_HASH_SALT}"
- PASSENGER_HASH_MIN_LENGTH: "${PASSENGER_HASH_MIN_LENGTH}"
- DRIVER_HASH_SALT: "${DRIVER_HASH_SALT}"
- DRIVER_HASH_MIN_LENGTH: "${DRIVER_HASH_MIN_LENGTH}"
- PASSENGER_ISSUER: "0"
- DRIVER_ISSUER: "1"
- - apiVersion: v1
- kind: Template
- metadata:
- name: ${SERVICE_NAME}-svc
- objects:
- - apiVersion: v1
- kind: Service
- metadata:
- annotations:
- labels:
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- ports:
- - name: ${PORT}-tcp
- port: ${PORT}
- protocol: TCP
- targetPort: ${PORT}
- selector:
- app: ${SERVICE_NAME}
- sessionAffinity: None
- type: ClusterIP
- - apiVersion: v1
- kind: Template
- metadata:
- name: ${SERVICE_NAME}
- objects:
- - apiVersion: v1
- kind: DeploymentConfig
- metadata:
- labels:
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- selector:
- app: ${SERVICE_NAME}
- strategy:
- rollingParams:
- intervalSeconds: 1
- maxSurge: 1
- maxUnavailable: 0
- timeoutSeconds: 600
- updatePeriodSeconds: 10
- type: Rolling
- template:
- metadata:
- # annotations:
- # prometheus.io/scrape: "true"
- # prometheus.io/path: "/metrics"
- labels:
- app: ${SERVICE_NAME}
- spec:
- containers:
- - name: ${SERVICE_NAME}-app
- image: "docker-registry.default.svc:5000/${APP_IMAGE}"
- imagePullPolicy: Always
- ports:
- - containerPort: ${PORT}
- protocol: TCP
- resources:
- limits:
- memory: "${MEMORY_LIMIT}"
- # cpu: "${CPU_LIMIT}"
- requests:
- memory: "${MEMORY_REQUEST}"
- # cpu: "${CPU_REQUEST}"
- readinessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 23
- successThreshold: 1
- tcpSocket:
- port: ${PORT}
- timeoutSeconds: 2
- livenessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 29
- successThreshold: 1
- tcpSocket:
- port: ${PORT}
- timeoutSeconds: 2
- envFrom:
- - secretRef:
- name: "${SERVICE_NAME}-auth-env"
- env:
- - name: HTTP_PROXY
- - name: HTTPS_PROXY
- volumeMounts:
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/"
- volumes:
- - name: "${SERVICE_NAME}-jwt-keys"
- configMap:
- name: "${SERVICE_NAME}-jwt-keys"
- # - name: "readiness-check"
- # emptyDir: {}
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
- triggers: []
-
-parameters:
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: soteria
-- name: MIN_REPLICA
- displayName: Minimum number of replica
- value: 2
-- name: MAX_REPLICA
- displayName: Maximum number of replica
- value: 80
-- name: CPU_UTILIZATION
- displayName: Target CPU Utilization Percentage
- value: 80
-- name: PASSENGER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- required: true
-- name: PASSENGER_HASH_MIN_LENGTH
- displayName: Minimum length of passenger generated hash for HashIds
- value: 15
-- name: DRIVER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- required: true
-- name: DRIVER_HASH_MIN_LENGTH
- displayName: Minimum length of driver generated hash for HashIds
- value: 15
-- name: APP_HOST
- required: true
- displayName: Application host
-- name: APP_IMAGE
- displayName: Application Docker Image
- description: application will pull and run this docker image. It has to be in Snapp Cloud registery
- value: "bravo-testing/soteria:master"
-- name: MEMORY_LIMIT
- value: "512Mi"
-- name: MEMORY_REQUEST
- value: "512Mi"
-- name: CPU_LIMIT
- value: "600m"
-- name: CPU_REQUEST
- value: "600m"
\ No newline at end of file
diff --git a/.okd/deploy.sh b/.okd/deploy.sh
deleted file mode 100755
index 76728654..00000000
--- a/.okd/deploy.sh
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env sh
-
-env > .env
-
-echo "processing config map..."
-oc process -f ./mozart/ConfigMap.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f -
-
-echo "processing secret..."
-oc process -f ./mozart/Secret.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f -
-
-echo "processing service..."
-oc process -f ./mozart/Service.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f -
-
-echo "processing deployment config..."
-oc process -f ./mozart/DeploymentConfig.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f -
-
-echo "processing route..."
-oc process -f ./mozart/Route.yaml --param-file=.env --ignore-unknown-parameters=true | oc apply -f -
-
-echo "rolling out $SERVICE_NAME ..."
-oc rollout latest dc/$SERVICE_NAME
diff --git a/.okd/mozart/ConfigMap.yaml b/.okd/mozart/ConfigMap.yaml
deleted file mode 100644
index c08062bc..00000000
--- a/.okd/mozart/ConfigMap.yaml
+++ /dev/null
@@ -1,58 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-configmap
-objects:
- - apiVersion: v1
- kind: ConfigMap
- metadata:
- name: ${SERVICE_NAME}-env
- labels:
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: ${MANAGED_BY}
- app.snappcloud.io/created-by: mozart
- app: ${SERVICE_NAME}
- data:
- SOTERIA_HTTP_PORT: "9999"
- SOTERIA_GRPC_PORT: "50051"
- SOTERIA_REDIS_ADDRESS: "${REDIS_ADDR}"
- SOTERIA_REDIS_POOL_SIZE: "10"
- SOTERIA_REDIS_MAX_RETRIES: "0"
- SOTERIA_REDIS_READ_TIMEOUT: "3s"
- SOTERIA_REDIS_POOL_TIMEOUT: "4s"
- SOTERIA_REDIS_MIN_RETRY_BACKOFF: "8ms"
- SOTERIA_REDIS_MAX_RETRY_BACKOFF: "512ms"
- SOTERIA_REDIS_IDLE_TIMEOUT: "300s"
- SOTERIA_REDIS_IDLE_CHECK_FREQUENCY: "60s"
- SOTERIA_REDIS_SET_MEMBER_EXP_TIME: "300s"
- SOTERIA_REDIS_MIN_IDLE_CONNECTIONS: "5"
- SOTERIA_LOGGER_LEVEL: "${LOG_LEVEL}"
- SOTERIA_LOGGER_SENTRY_ENABLED: "false"
- SOTERIA_LOGGER_SENTRY_DSN: ""
- SOTERIA_LOGGER_SENTRY_TIMEOUT: "500ms"
- SOTERIA_ALLOWED_ACCESS_TYPES: "sub,pub"
- SOTERIA_CACHE_ENABLED: "true"
- SOTERIA_CACHE_EXPIRATION: "600s"
-
-parameters:
- - name: SERVICE_NAME
- displayName: Service name
- required: true
- value: "soteria"
- - name: MANAGED_BY
- displayName: Created By
- required: true
- value: dispatching-team
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "dev"
- - name: LOG_LEVEL
- value: "DEBUG"
- displayName: Log Level
- description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF)
- - name: REDIS_ADDR
- required: true
- value: "rediscentral:6379"
diff --git a/.okd/mozart/DeploymentConfig.yaml b/.okd/mozart/DeploymentConfig.yaml
deleted file mode 100644
index 238d545b..00000000
--- a/.okd/mozart/DeploymentConfig.yaml
+++ /dev/null
@@ -1,151 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-deployment-config
-objects:
- - apiVersion: v1
- kind: DeploymentConfig
- metadata:
- labels:
- app: ${SERVICE_NAME}
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: ${MANAGED_BY}
- app.snappcloud.io/created-by: mozart
- name: ${SERVICE_NAME}
- spec:
- replicas: 1
- selector:
- app: ${SERVICE_NAME}
- deploymentconfig: ${SERVICE_NAME}
- triggers: []
- template:
- metadata:
- annotations:
- prometheus.io/scrape: "${METRICS_ENABLED}"
- prometheus.io/path: "/metrics"
- labels:
- app: ${SERVICE_NAME}
- deploymentconfig: ${SERVICE_NAME}
- spec:
- initContainers:
- - name: ${SERVICE_NAME}-app-init
- image: docker-registry.default.svc:5000/mozart/${SERVICE_NAME}:${SERVICE_IMAGE_TAG}
- command: ["/app/soteria", "accounts", "init", "db.json"]
- imagePullPolicy: Always
- envFrom:
- - secretRef:
- name: ${SERVICE_NAME}-auth-env
- - configMapRef:
- name: "${SERVICE_NAME}-env"
- volumeMounts:
- - name: "${SERVICE_NAME}-db"
- mountPath: "/app/db.json"
- subPath: db.json
- readOnly: true
- containers:
- - name: ${SERVICE_NAME}-app
- image: docker-registry.default.svc:5000/mozart/${SERVICE_NAME}:${SERVICE_IMAGE_TAG}
- command: ["/app/soteria", "serve"]
- imagePullPolicy: Always
- ports:
- - containerPort: 9999
- protocol: TCP
- resources:
- limits:
- memory: "${MEMORY_LIMIT}"
- # cpu: "${CPU_LIMIT}"
- requests:
- memory: "${MEMORY_REQUEST}"
- # cpu: "${CPU_REQUEST}"
- readinessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 23
- successThreshold: 1
- tcpSocket:
- port: 9999
- timeoutSeconds: 2
- livenessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 29
- successThreshold: 1
- tcpSocket:
- port: 9999
- timeoutSeconds: 2
- envFrom:
- - secretRef:
- name: ${SERVICE_NAME}-auth-env
- - configMapRef:
- name: "${SERVICE_NAME}-env"
- env:
- - name: LOG_LEVEL
- value: "${LOG_LEVEL}"
- - name: HTTP_PROXY
- - name: HTTPS_PROXY
- volumeMounts:
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/100.private.pem"
- subPath: 100.private.pem
- readOnly: true
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/100.pem"
- subPath: 100.pem
- readOnly: true
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/1.pem"
- subPath: 1.pem
- readOnly: true
- - name: "${SERVICE_NAME}-jwt-keys"
- mountPath: "/jwt_pems/0.pem"
- subPath: 0.pem
- readOnly: true
- volumes:
- - name: "${SERVICE_NAME}-jwt-keys"
- secret:
- secretName: "${SERVICE_NAME}-jwt-keys"
- - name: "${SERVICE_NAME}-db"
- secret:
- secretName: "${SERVICE_NAME}-db"
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
-parameters:
-- name: SERVICE_IMAGE_TAG
- displayName: Application Docker Image
- description: application will pull and run this docker image. It has to be in Snapp Cloud registery
- value: "dev"
-- name: CI_COMMIT_REF_SLUG
- displayName: Deployment branch
- required: true
- value: "dev"
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: "soteria"
-- name: PORT
- value: "9999"
- displayName: Port
- description: the port which will be open by your application
-- name: MEMORY_LIMIT
- value: "150Mi"
-- name: MEMORY_REQUEST
- value: "150Mi"
-- name: CPU_LIMIT
- value: "500m"
-- name: CPU_REQUEST
- value: "500m"
-- name: LOG_LEVEL
- value: "DEBUG"
- displayName: Log Level
- description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF)
-- name: METRICS_ENABLED
- value: "false"
- displayName: Metrics Endpoint?
- description: /metrics endpoint will be available for Prometheus
-- name: MANAGED_BY
- displayName: Managed By
- required: true
- value: dispatching-team
diff --git a/.okd/mozart/Route.yaml b/.okd/mozart/Route.yaml
deleted file mode 100644
index 223d1754..00000000
--- a/.okd/mozart/Route.yaml
+++ /dev/null
@@ -1,52 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}
-objects:
-
- - apiVersion: v1
- kind: Route
- metadata:
- labels:
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: dispatching-team
- app.snappcloud.io/created-by: ${CREATED_BY}
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}-tcp
- spec:
- host: ${SERVICE_NAME}-${SERVICE_NAMESPACE}.apps.private.${CLUSTER_NAME}.snappcloud.io
- port:
- targetPort: ${SERVICE_NAME}-tcp
- tls:
- termination: edge
- insecureEdgeTerminationPolicy: Allow
- to:
- kind: Service
- name: ${SERVICE_NAME}
- weight: 100
- wildcardPolicy: None
-
-parameters:
- - name: CLUSTER_NAME
- displayName: Cluster name
- required: true
- value: "teh-1"
- - name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- required: true
- value: soteria
- - name: SERVICE_NAMESPACE
- displayName: Application Unique ID
- required: true
- value: "snapp-ode-mozart"
- - name: CREATED_BY
- displayName: Service name
- required: true
- value: mozart
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "dev"
diff --git a/.okd/mozart/Secret.yaml b/.okd/mozart/Secret.yaml
deleted file mode 100644
index 9fbb3430..00000000
--- a/.okd/mozart/Secret.yaml
+++ /dev/null
@@ -1,152 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: "${SERVICE_NAME}-secret"
-objects:
- - apiVersion: v1
- kind: Secret
- type: Opaque
- metadata:
- name: "${SERVICE_NAME}-auth-env"
- labels:
- app: "${SERVICE_NAME}"
- app.snappcloud.io/created-by: "mozart"
- app.snappcloud.io/managed-by: "${MANAGED_BY}"
- app.snappcloud.io/version: "${APP_VERSION}"
- app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}"
- stringData:
- SOTERIA_JWT_KEYS_PATH: "${JWT_KEYS_PATH}"
- SOTERIA_DRIVER_SALT: "${DRIVER_HASH_SALT}"
- SOTERIA_PASSENGER_SALT: "${PASSENGER_HASH_SALT}"
- SOTERIA_DRIVER_HASH_LENGTH: "${DRIVER_HASH_LENGTH}"
- SOTERIA_PASSENGER_HASH_LENGTH: "${PASSENGER_HASH_LENGTH}"
- - apiVersion: v1
- kind: Secret
- type: Opaque
- metadata:
- name: "${SERVICE_NAME}-jwt-keys"
- labels:
- app: "${SERVICE_NAME}"
- app.snappcloud.io/created-by: "mozart"
- app.snappcloud.io/managed-by: "${MANAGED_BY}"
- app.snappcloud.io/version: "${APP_VERSION}"
- app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}"
- stringData:
- 0.pem: "${DRIVER_JWT_PUBLIC_KEY}"
- 1.pem: "${PASSENGER_JWT_PUBLIC_KEY}"
- 100.pem: "${THIRD_PARTY_JWT_PUBLIC_KEY}"
- 100.private.pem: "${THIRD_PARTY_JWT_PRIVATE_KEY}"
- - apiVersion: v1
- kind: Secret
- type: Opaque
- metadata:
- name: "${SERVICE_NAME}-db"
- labels:
- app: "${SERVICE_NAME}"
- app.snappcloud.io/created-by: "mozart"
- app.snappcloud.io/managed-by: "${MANAGED_BY}"
- app.snappcloud.io/version: "${APP_VERSION}"
- app.snappcloud.io/instance: "${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}"
- stringData:
- db.json: "${DB}"
-
-
-parameters:
- - name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: "soteria"
- - name: APP_VERSION
- required: true
- value: "dev"
- - name: MANAGED_BY
- displayName: Managed by
- required: true
- value: "dispatching-team"
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "dev"
- - name: PASSENGER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- required: true
- value: "secret"
- - name: PASSENGER_HASH_LENGTH
- displayName: Minimum length of passenger generated hash for HashIds
- value: "15"
- - name: DRIVER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- required: true
- value: "secret"
- - name: DRIVER_HASH_LENGTH
- displayName: Minimum length of driver generated hash for HashIds
- value: "15"
- - name: JWT_KEYS_PATH
- displayName: Private Key Path
- description: Private key Path
- value: "/jwt_pems/"
- required: true
- - name: THIRD_PARTY_JWT_PRIVATE_KEY
- displayName: Third Party JWT Private Key
- required: true
- value: |-
- -----BEGIN RSA PRIVATE KEY-----
- MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n
- gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs
- AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB
- AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1
- j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP
- hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs
- hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh
- 1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E
- pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg
- FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT
- 6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r
- m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ
- l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g==
- -----END RSA PRIVATE KEY-----
- - name: DRIVER_JWT_PUBLIC_KEY
- displayName: Driver public key
- description: Public Key of driver's token
- required: true
- value: |-
- -----BEGIN PUBLIC KEY-----
- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBk7O6M5p4eYNAwtVU2beGa
- W4mhFG94OtYUWDl1E7UUrhUNGf97Eb/45NjQszu0YPERnApJc2RUm2TrS7iq0mHz
- Xbwf+CbNF54Q5mjuHcpBKgvFwUUSCCYBftmRc4xbFIH4Oh3nHC2GeukUS9TmJwjM
- tJKyU0Ve8BK5BgjhagM7XSs+scE2mxemoWtcs6mJLtBuEgRGMgHW00mSdOcLp/+l
- oHpSzRYN92/DomwmmjGVy8Ji0faeHx+r79ZzE0E8Rcc29Yhrg1ymrjfkXg98WjAb
- TSv4UAN20lsBDejpnGEZKJrxHZ56gHgaJn6PKKCD6ItJA7y7iraCdBhCfAIUIz/z
- AgMBAAE=
- -----END PUBLIC KEY-----
- - name: PASSENGER_JWT_PUBLIC_KEY
- displayName: Passenger public key
- description: Public Key of passenger's token
- required: true
- value: |-
- -----BEGIN PUBLIC KEY-----
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1lNRwyNsDieWs6LvHOJ+
- GyehhRC4Pn5yL5edKP3565F3LtRDMrkzwDRsQbqnUtTea9HCdTdBv+lI8vE17qRi
- RQn10IMaIH6e4Aa3OWNClFhuqNOag7VmffsjTOgxHgHpfGAKVF/4BwqOHrdHFbAD
- VOiWB1hv9Uc0C5laffGAub7fj+EAI02zlrsNDxYW8vyF2H47N7VWcvgd3RhZpxlG
- 8bq9phl7Ja55YmQiT2Ic3/K5tsazg5z9lz6OTrx+JvWbefHFlJpjCLz5yefEaRmX
- 9L/zyDMi4jgFTZEWNXC2vIrxwZMFwFhBXEp0PcCbuHJgJIucbRrbwukQC16uHJwP
- zQIDAQAB
- -----END PUBLIC KEY-----
- - name: THIRD_PARTY_JWT_PUBLIC_KEY
- displayName: Third party public key
- description: Public key of third parties
- required: true
- value: |-
- -----BEGIN PUBLIC KEY-----
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8
- OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu
- GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o
- Wsupn2HVhMSjFbE6dwIDAQAB
- -----END PUBLIC KEY-----
- - name: DB
- displayName: Initial database state
- description: Initial database state
- required: true
- value: |-
- [{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106544359+03:30","date_modified":"2020-09-29T10:44:15.106544423+03:30"},"username":"driver","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"84df6017-a850-42a3-a06f-22306a006a8d","endpoint":"","topic":"cab_event","access_type":"1"},{"uuid":"df0b884e-41dc-444e-81d4-67f62ae2174c","endpoint":"","topic":"driver_location","access_type":"2"},{"uuid":"1375192a-6fc5-4409-abd8-0d21ef106bee","endpoint":"","topic":"shared_location","access_type":"1"},{"uuid":"d7aeb294-6271-470a-987a-01ea4f035e5d","endpoint":"","topic":"superapp_event","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106560757+03:30","date_modified":"2020-09-29T10:44:15.106560814+03:30"},"username":"passenger","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"a51f1898-3381-456f-a5fe-0e9d80b5bee0","endpoint":"","topic":"cab_event","access_type":"1"},{"uuid":"8de97e3f-e772-4da4-8f15-4dc048f5c06e","endpoint":"","topic":"superapp_event","access_type":"1"},{"uuid":"c41260ed-70eb-4554-bc15-5714fc1bf3f8","endpoint":"","topic":"passenger_location","access_type":"2"},{"uuid":"1375192a-6fc5-4409-abd8-0d21ef106bee","endpoint":"","topic":"shared_location","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106567252+03:30","date_modified":"2020-09-29T10:44:15.106567311+03:30"},"username":"box","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"e591af90-8191-4118-941a-7ef4434414ee","endpoint":"","topic":"box_event","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106569957+03:30","date_modified":"2020-09-29T10:44:15.106570022+03:30"},"username":"colony-subscriber","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"bcbe0a57-1706-4de7-a948-71c56c9700c4","endpoint":"","topic":"driver_location","access_type":"1"}]},{"meta_data":{"model_name":"user","date_created":"2020-09-29T10:44:15.106573104+03:30","date_modified":"2020-09-29T10:44:15.106573162+03:30"},"username":"gabriel","password":"password","type":"HeraldUser","ips":null,"secret":"secret","token_expiration_duration":2592000000000000,"rules":[{"uuid":"305dc844-5df0-42dc-849b-deccfe2dab96","endpoint":"/notification","topic":"","access_type":"2"},{"uuid":"5795227f-10a8-4822-ba5e-a1fc057a0a95","endpoint":"/event","topic":"","access_type":"2"}]},{"meta_data":{"model_name":"user","date_created":"2021-06-23T08:42:24.722521722Z","date_modified":"2021-07-06T18:40:10.074710788Z"},"username":"gossiper","password":"password","type":"EMQUser","ips":null,"secret":"secret","token_expiration_duration":30758400000000000,"rules":[{"uuid":"ec6f659e-fe73-41cd-805e-c5285bd92a8d","endpoint":"","topic":"shared_location","access_type":"2"},{"uuid":"a8dc06c4-bb11-4cb9-8da7-84ae34d2d14a","endpoint":"","topic":"driver_location","access_type":"1"},{"uuid":"cda59530-d412-4c6d-bb3b-7a89c6bf58c5","endpoint":"","topic":"passenger_location","access_type":"1"}]}]
diff --git a/.okd/mozart/Service.yaml b/.okd/mozart/Service.yaml
deleted file mode 100644
index 79d7b005..00000000
--- a/.okd/mozart/Service.yaml
+++ /dev/null
@@ -1,39 +0,0 @@
-apiVersion: v1
-kind: Template
-objects:
- - apiVersion: v1
- kind: Service
- metadata:
- name: ${SERVICE_NAME}
- labels:
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: dispatching-team
- app.snappcloud.io/created-by: ${CREATED_BY}
- app: "${SERVICE_NAME}"
- spec:
- ports:
- - name: ${SERVICE_NAME}-tcp
- port: 9999
- protocol: TCP
- targetPort: 9999
- - name: 50051-tcp
- port: 50051
- protocol: TCP
- targetPort: 50051
- selector:
- deploymentconfig: ${SERVICE_NAME}
-parameters:
- - name: SERVICE_NAME
- displayName: Service name
- required: true
- value: "soteria"
- - name: CREATED_BY
- displayName: Created By
- required: true
- value: mozart
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "dev"
diff --git a/.okd/oldMozart/ConfigMap.yaml b/.okd/oldMozart/ConfigMap.yaml
deleted file mode 100644
index ba656114..00000000
--- a/.okd/oldMozart/ConfigMap.yaml
+++ /dev/null
@@ -1,61 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-configmap
-objects:
- - apiVersion: v1
- kind: ConfigMap
- metadata:
- name: ${SERVICE_NAME}-jwt-keys
- labels:
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: bravo
- app.snappcloud.io/created-by: ${CREATED_BY}
- app: ${SERVICE_NAME}
- data:
- 100.private.pem: ${BOX_JWT_PRIVATE_KEY}
- 100.pem: ${BOX_JWT_PUBLIC_KEY}
-parameters:
- - name: SERVICE_NAME
- displayName: Service name
- required: true
- value: "soteria"
- - name: CREATED_BY
- displayName: Created By
- required: true
- value: mozart
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "qe"
- - name: BOX_JWT_PUBLIC_KEY
- displayName: Box JWT Public Key
- required: true
- value: |-
- -----BEGIN PUBLIC KEY-----
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8
- OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu
- GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o
- Wsupn2HVhMSjFbE6dwIDAQAB
- -----END PUBLIC KEY-----
- - name: BOX_JWT_PRIVATE_KEY
- displayName: Box JWT Private Key
- required: true
- value: |-
- -----BEGIN RSA PRIVATE KEY-----
- MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n
- gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs
- AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB
- AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1
- j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP
- hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs
- hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh
- 1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E
- pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg
- FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT
- 6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r
- m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ
- l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g==
- -----END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/.okd/oldMozart/DeploymentConfig.yaml b/.okd/oldMozart/DeploymentConfig.yaml
deleted file mode 100644
index 8dbdc528..00000000
--- a/.okd/oldMozart/DeploymentConfig.yaml
+++ /dev/null
@@ -1,151 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-deployment-config
-objects:
- - apiVersion: v1
- kind: DeploymentConfig
- metadata:
- labels:
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: bravo
- app.snappcloud.io/created-by: ${CREATED_BY}
- app: ${SERVICE_NAME}
- name: ${SERVICE_NAME}
- spec:
- replicas: 1
- selector:
- app: ${SERVICE_NAME}
- deploymentconfig: ${SERVICE_NAME}
- triggers: []
- template:
- metadata:
- annotations:
- prometheus.io/scrape: "${METRICS_ENABLED}"
- prometheus.io/path: "/metrics"
- labels:
- app: ${SERVICE_NAME}
- deploymentconfig: ${SERVICE_NAME}
- spec:
- containers:
- - name: ${SERVICE_NAME}-app
- image: docker-registry.default.svc:5000/mozart/${SERVICE_NAME}:master
- imagePullPolicy: Always
- ports:
- - containerPort: 9999
- protocol: TCP
- resources:
- limits:
- memory: "${MEMORY_LIMIT}"
- # cpu: "${CPU_LIMIT}"
- requests:
- memory: "${MEMORY_REQUEST}"
- # cpu: "${CPU_REQUEST}"
- readinessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 23
- successThreshold: 1
- tcpSocket:
- port: 9999
- timeoutSeconds: 2
- livenessProbe:
- failureThreshold: 3
- initialDelaySeconds: 10
- periodSeconds: 29
- successThreshold: 1
- tcpSocket:
- port: 9999
- timeoutSeconds: 2
- envFrom:
- - secretRef:
- name: "${SERVICE_NAME}-auth-env"
- env:
- - name: LOG_LEVEL
- value: "${LOG_LEVEL}"
- - name: DEBUG
- value: "${DEBUG}"
- - name: HTTP_PROXY
- - name: HTTPS_PROXY
- volumeMounts:
- - name: "${SERVICE_NAME}-passenger-keys"
- mountPath: "/jwt_pems/1.pem"
- subPath: "1.pem"
- readOnly: true
- - name: "${SERVICE_NAME}-driver-keys"
- mountPath: "/jwt_pems/0.pem"
- subPath: "0.pem"
- readOnly: true
- - name: "${SERVICE_NAME}-3rd-party-keys"
- mountPath: "/jwt_pems/100.private.pem"
- subPath: 100.private.pem
- readOnly: true
- - name: "${SERVICE_NAME}-3rd-party-keys"
- mountPath: "/jwt_pems/100.pem"
- subPath: 100.pem
- readOnly: true
- volumes:
- - name: "${SERVICE_NAME}-passenger-keys"
- secret:
- secretName: passengeroauth-keys
- items:
- - key: passenger-public.pem
- path: "1.pem"
- - name: "${SERVICE_NAME}-driver-keys"
- secret:
- secretName: driveroauth-keys
- items:
- - key: driver-public.pem
- path: "0.pem"
- - name: "${SERVICE_NAME}-3rd-party-keys"
- configMap:
- name: ${SERVICE_NAME}-jwt-keys
-
- # - name: "readiness-check"
- # emptyDir: {}
- dnsPolicy: ClusterFirst
- restartPolicy: Always
- terminationGracePeriodSeconds: 30
-parameters:
-- name: SERVICE_IMAGE_TAG
- displayName: Application Docker Image
- description: application will pull and run this docker image. It has to be in Snapp Cloud registery
- value: "qe"
-- name: CI_COMMIT_REF_SLUG
- displayName: Deployment branch
- required: true
- value: "master"
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: "soteria"
-- name: PORT
- value: "9999"
- displayName: Port
- description: the port which will be open by your application
-- name: MEMORY_LIMIT
- value: "150Mi"
-- name: MEMORY_REQUEST
- value: "150Mi"
-- name: CPU_LIMIT
- value: "500m"
-- name: CPU_REQUEST
- value: "500m"
-- name: DEBUG
- value: "0"
- displayName: Debug Mode
- description: Add more debug info under /pprof route if it's enabled
-- name: LOG_LEVEL
- value: "DEBUG"
- displayName: Log Level
- description: How much log do you want? (DEBUG|INFO|WARN|ERROR|OFF)
-- name: METRICS_ENABLED
- value: "false"
- displayName: Metrics Endpoint?
- description: /metrics endpoint will be available for Prometheus
-- name: CREATED_BY
- displayName: Created By
- required: true
- value: mozart
diff --git a/.okd/oldMozart/Route.yaml b/.okd/oldMozart/Route.yaml
deleted file mode 100644
index 08967366..00000000
--- a/.okd/oldMozart/Route.yaml
+++ /dev/null
@@ -1,44 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}
-objects:
-- apiVersion: v1
- kind: Route
- metadata:
- labels:
- app: ${SERVICE_NAME}
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: bravo
- app.snappcloud.io/created-by: ${CREATED_BY}
- name: ${SERVICE_NAME}-tcp
- spec:
- host: ${SERVICE_NAME}-${SERVICE_NAMESPACE}.apps.private.teh-1.snappcloud.io
- port:
- targetPort: ${SERVICE_NAME}-tcp
- to:
- kind: Service
- name: ${SERVICE_NAME}
- weight: 100
- wildcardPolicy: None
-
-parameters:
-- name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- required: true
- value: soteria
-- name: SERVICE_NAMESPACE
- displayName: Application Unique ID
- required: true
- value: "snapp-ode-mozart"
-- name: CREATED_BY
- displayName: Service name
- required: true
- value: mozart
-- name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "qe"
diff --git a/.okd/oldMozart/Secret.yaml b/.okd/oldMozart/Secret.yaml
deleted file mode 100644
index 1fd334a8..00000000
--- a/.okd/oldMozart/Secret.yaml
+++ /dev/null
@@ -1,61 +0,0 @@
-apiVersion: v1
-kind: Template
-metadata:
- name: ${SERVICE_NAME}-secret-tpl
-objects:
- - apiVersion: v1
- kind: Secret
- type: Opaque
- metadata:
- name: "${SERVICE_NAME}-auth-env"
- labels:
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: bravo
- app.snappcloud.io/created-by: ${CREATED_BY}
- app: ${SERVICE_NAME}
- stringData:
- CHANNEL_PREFIX: "emqch-"
- JWT_BASE_PATH: "/jwt_pems/"
- SALT_1: "${PASSENGER_HASH_SALT}"
- HASH_LENGTH_1: "${PASSENGER_HASH_MIN_LENGTH}"
- SALT_0: "${DRIVER_HASH_SALT}"
- HASH_LENGTH_0: "${DRIVER_HASH_MIN_LENGTH}"
- ISS_TYPE_0: "driver"
- ISS_TYPE_1: "passenger"
- ISS_TYPE_100: "third party"
- THIRDPARTY_CONFIG: "${VENDOR_APPLICATION_DATA}"
-
-parameters:
- - name: PASSENGER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- value: "secret"
- required: true
- - name: PASSENGER_HASH_MIN_LENGTH
- displayName: Minimum length of passenger generated hash for HashIds
- value: "15"
- - name: DRIVER_HASH_SALT
- displayName: Passenger hash salt for HashIds
- value: "secret"
- required: true
- - name: DRIVER_HASH_MIN_LENGTH
- displayName: Minimum length of driver generated hash for HashIds
- value: "15"
- - name: VENDOR_APPLICATION_DATA
- displayName: Vendor Application Data
- description: The configuration of 3rd parties application
- required: true
- value: "ewogICAiYm94Ijp7CiAgICAgICJ0eXBlIjoic3Vic2NyaWJlciIsCiAgICAgICJ0b2tlbiI6IktKSWlraklLYklZVkdqKVlpaFlVR0lCJiIsCiAgICAgICJ0b3BpY3MiOiJidWNrcywgMTExMSIsCiAgICAgICJleHAiOjg2NDAwCiAgIH0sCiAgICJoZXJhbGQtcHVibGlzaCI6ewogICAgICAidHlwZSI6InB1Ymxpc2giLAogICAgICAidG9rZW4iOiJ1dTBKVlB0aygxbnRHYWN1IiwKICAgICAgInRvcGljcyI6IioiLAogICAgICAiZXhwIjoxNTc2ODAwMDAKICAgfSwKICAgImhlcmFsZC1zdWJzY3JpYmVyIjp7CiAgICAgICJ0eXBlIjoic3Vic2NyaWJlciIsCiAgICAgICJ0b2tlbiI6InV1MEpWUHRrKDFudEdhY3UiLAogICAgICAidG9waWNzIjoiKiIsCiAgICAgICJleHAiOjE1NzY4MDAwMAogICB9LAogICAiY29sb255LXN1YnNjcmliZXIiOnsKICAgICAgInR5cGUiOiJzdWJzY3JpYmVyIiwKICAgICAgInRva2VuIjoidXUwSlZQdGsoMW50R2FjdXpwIiwKICAgICAgInRvcGljcyI6IioiLAogICAgICAiZXhwIjoxNTc2ODAwMDAKICAgfQp9"
- - name: SERVICE_NAME
- displayName: Service name
- description: The name of the application (should be unique in the namespace)
- value: "soteria"
- - name: CREATED_BY
- displayName: Created By
- required: true
- value: mozart
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "qe"
diff --git a/.okd/oldMozart/Service.yaml b/.okd/oldMozart/Service.yaml
deleted file mode 100644
index b2f4154c..00000000
--- a/.okd/oldMozart/Service.yaml
+++ /dev/null
@@ -1,35 +0,0 @@
-apiVersion: v1
-kind: Template
-objects:
- - apiVersion: v1
- kind: Service
- metadata:
- name: ${SERVICE_NAME}
- labels:
- app.snappcloud.io/name: ${SERVICE_NAME}
- app.snappcloud.io/instance: ${SERVICE_NAME}-${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/version: ${CI_COMMIT_REF_SLUG}
- app.snappcloud.io/managed-by: bravo
- app.snappcloud.io/created-by: ${CREATED_BY}
- app: "${SERVICE_NAME}"
- spec:
- ports:
- - name: ${SERVICE_NAME}-tcp
- port: 9999
- protocol: TCP
- targetPort: 9999
- selector:
- deploymentconfig: ${SERVICE_NAME}
-parameters:
- - name: SERVICE_NAME
- displayName: Service name
- required: true
- value: "soteria"
- - name: CREATED_BY
- displayName: Created By
- required: true
- value: mozart
- - name: CI_COMMIT_REF_SLUG
- displayName: Commit Ref Slug
- required: true
- value: "qe"
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 5a843f7c..00000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# Change log
-
-# Unreleased - (yyyy-mm-dd)
-
----
-
-## July 17, 2021
-
-### Added
-
-- Add superuser token support
-
-### Changed
-
-- Refactor internal packages
-
-### Fixed
-
-## May 31, 2021
-
-### Added
-
-- Add chat topic in acl logics
-
-### Changed
-
-### Fixed
-
-## May 29, 2021
-
-### Added
-
-- Add location sharing topics acl logic.
-- Ignore tokens which contain `is_superuser` flag in their claims.
-- Tracing with jeager. jeager configuration is added
-
-| **Variable Name** | **Type** | **Description** |
-| ------------------------------ | -------- | --------------------------------------------- |
-| `SOTERIA_TRACER_SAMPLER_TYPE` | string | client sampler type e.g. const, probabilistic |
-| `SOTERIA_TRACER_SAMPLER_PARAM` | float | client sampler paramer e.g. 1, 0 |
-
-For more on sampler please refer to [this](https://www.jaegertracing.io/docs/1.22/sampling/).
-
-### Changed
-
-### Fixed
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 057f0305..00000000
--- a/Dockerfile
+++ /dev/null
@@ -1,16 +0,0 @@
-FROM alpine
-ARG BUILD_DATE
-ARG VCS_REF
-ARG BUILD_VERSION
-RUN echo -e "https://repo.snapp.tech/repository/alpine/v3.12/main\nhttps://repo.snapp.tech/repository/alpine/v3.12/community" > /etc/apk/repositories
-
-RUN apk --no-cache --update add ca-certificates
-
-RUN mkdir /app
-
-COPY ./soteria /app
-WORKDIR /app
-ENV SOTERIA_BUILD_DATE=${BUILD_DATE}
-ENV SOTERIA_VCS_REF=${VCS_REF}
-ENV SOTERIA_BUILD_VERSION=${BUILD_VERSION}
-CMD ["/app/soteria", "serve"]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 7228a4ab..00000000
--- a/Makefile
+++ /dev/null
@@ -1,36 +0,0 @@
-IMAGE_FLAG := $(shell git rev-parse --abbrev-ref HEAD | tr '/' '-') #get image flag from current branch name.
-IMAGE_TAG ?= registry.apps.private.teh-1.snappcloud.io/dispatching-staging/soteria:${IMAGE_FLAG}
-
-dependencies:
- go mod vendor -v
-
-compile: dependencies
- go build --ldflags "-linkmode external -extldflags '-static'" -mod vendor -v cmd/soteria/soteria.go
-
-test:
- go test --race -gcflags=-l -v -coverprofile .coverage.out.tmp ./...
- cat .coverage.out.tmp | grep -v "mock.go" > .coverage.out
- rm -rf .coverage.out.tmp
- go tool cover -func .coverage.out
-
-html-report: test
- go tool cover -html=.coverage.out -o coverage.html
-
-build-image: compile
- docker build -t ${IMAGE_TAG} .
-
-push-image: build-image
- docker push ${IMAGE_TAG}
-
-build-image-dev: compile
- docker build -t soteria:latest .
-
-up: build-image-dev
- docker-compose up
-
-down:
- docker-compose down
- docker-compose stop
- docker rmi soteria
-
-.PHONY: all test clean
diff --git a/README.md b/README.md
index 012bf1b5..3e598785 100644
--- a/README.md
+++ b/README.md
@@ -1,134 +1,271 @@
-# Soteria
-
-# What is Soteria?
-
-Soteria is responsible for Authentication and Authorization of every request sent to EMQ and Herald.
-
-# How to compile?
-
-- [Install Golang](https://golang.org/doc/install)
-
-- Set `snapp goproxy`
-
-`go env -w GOPROXY="https://repo.snapp.tech/repository/goproxy/"`
-
-- Run the following command to compile the application
+ Soteria
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Introduction
+
+Soteria is responsible for Authentication and Authorization of every request sent to [EMQ](https://github.com/emqx/emqx/).
+The following configuration in [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md)
+format, configure EMQ to use HTTP Service for Authentication and Authorization.
+
+```hocon
+{
+ mechanism = password_based
+ backend = http
+ enable = true
+
+ method = post
+ url = "http://127.0.0.1:8080/v2/auth"
+ body {
+ username = "${username}"
+ password = "${password}"
+ token = "${username}"
+ clientid = "${clientid}"
+ }
+ headers {
+ "Content-Type" = "application/json"
+ "X-Request-Source" = "EMQX"
+ }
+}
+```
-`make compile`
+```hocon
+{
+ type = http
+ enable = true
+
+ method = post
+ url = "http://127.0.0.1:32333/v2/acl"
+ body {
+ username = "${username}"
+ topic = "${topic}"
+ action = "${action}"
+ }
+ headers {
+ "Content-Type" = "application/json"
+ "X-Request-Source" = "EMQX"
+ }
+}
-# How to run it locally?
+```
-By executing the following command Herald will be up with EMQX and RabbitMQ brokers.
+We are using the [Authentication HTTP Service](https://www.emqx.io/docs/en/v5.2/access-control/authn/http.html)
+and [Authorization HTTP Service](https://www.emqx.io/docs/en/v5.2/access-control/authn/http.html)
+plugins of EMQ for forwarding these requests to Soteria and doing Authentication and Authorization.
+EMQ has caching mechanism, but it sends requests almost for each Publish message to Soteria.
+PS: On Subscribe we have only one message from client that need authorization and other messages are coming from server.
+
+## Architecture
+
+
+
+## Support Vendors
+
+Soteria supports having multiple vendors at the same time.
+Means you can use single cluster for multiple companies at the same time and validate their tokens
+and control accesses.
+
+### Vendor Configuration
+
+```yaml
+company: "<>"
+driver_salt: ""
+passenger_salt: ""
+passenger_hash_length: 15
+driver_hash_length: 15
+allowed_access_types: ["pub", "sub"]
+keys:
+ iss-0: "key-value"
+ iss-1: "key-value"
+iss_entity_map:
+ 0: "entity-0"
+ 1: "entity-1"
+ default: "default-entity"
+iss_peer_map:
+ 0: "peer-0"
+ 1: "peer-1"
+ default: "default-peer"
+jwt:
+ iss_name: "iss"
+ sub_name: "sub"
+ signing_method: "RS512"
+topics:
+ - topic1
+ - topic2
+ - ...
+```
-`make up`
+### HashID Manager
-# How to test?
+`driver_salt`,`passenger_salt`, `passenger_hash_length`, `driver_hash_length` are used for HashIDManager.
+This component only works for passenger and driver issuers.
-## Unit testing
+### Keys
-`make test`
+The following is a mapping that associates vendors (companies) with the keys used for opening JWT tokens.
+If symmetrical keys are utilized, it is important to use their base64 representation.
+It should also be noted that Soteria only requires public keys in cases where asymmetrical keys are employed.
-# Deployment
+### IssEntityMap & IssPeerMap
-## Staging
+These two configuration map iss to entity and peer respectively.
-You can deploy `soteria` to the staging environments using
-helm charts.
+**Note**: default case is `required`
+```yaml
+iss_entity_map:
+ 0: "driver"
+ 1: "passenger"
+ default: "none"
+iss_peer_map:
+ 0: "passenger"
+ 1: "driver"
+ default: "none"
```
-cd deployments
-helm install soteria --generate-name
-```
-
-## Production
-
-We deploy `soteria` on two different infrastructures.
-
-- VM
-- Cloud (okd)
-### VM
-
-For VM deployments following 2 steps are required.
-
-- Preparing VMs which is done by `ansible playbooks`. You can find `herald`'s
- `ansible playbooks` in the following link
- [bravo/new-ansible-playbook](https://gitlab.snapp.ir/bravo/new-ansible-playbook)
+In the example above, we have two maps for entity & peer maps. As it's clear for **entity** structure **0** and **1** is mapped to **driver** and **passenger**, respectively. Vice Versa, for peer structure it can be seen that **1** and **0** is mapped to **driver** and **passenger**. We have also the **default** key for both two cases.
+
+In the topic example, we have an accesses section in which **0** is mapped to **2** and **1** is mapped to **-1** which can be interpreted as a map from **IssEntity's Keys** to **Access Types**. In the other words this structure means:
+
+- **Driver** has a **Pub** access on topic
+- **Passenger** has a **None** access on topic (No Access)
+
+### JWT
+
+This is the JWT configuration. `iss_name` and `sub_name` are the name of issuer
+and subject in the JWT token's payload respectively.
+
+`signing_method` is the method that is used to sign the JWT token.
+Here are list of different signing methods
+
+- ES384
+- RS512 \*
+- PS512
+- RS384 \*
+- HS256 \*
+- HS384 \*
+- RS256 \*
+- PS384
+- ES256
+- ES512
+- EdDSA
+- HS512 \*
+- PS256
+ **Note**: only the methods with `*` are supported for now.
+
+### Topic Configuration
+
+```yaml
+type: "<>"
+template: "<>"
+hash_type: 0|1
+accesses:
+ iss-0: "<>"
+ iss-1: "<>"
+```
-- Deploying `soteria` with CI/CD pipelines.
+### Template
-### Cloud (okd)
+Topic template is a string consist of [Variables](##Available_Variables) and [Functions](##Available_Functions)
+and regular expressions.
-[dispatching/ignite](https://gitlab.snapp.ir/dispatching/ignite) is responsible
-for production deployments on Cloud (okd).
+Variables and Function are replaced first, and then the whole template will compile as a regular expression.
+The end result will be compared against the requested topic.
-# Folder Structure
+#### Example
-- `.api`: API documentation like swagger files
-- `.gitlab`: Gitlab CI templates
-- `.okd`: OpenShift deployment configs (no longer is use. please use Helm charts)
-- `configs`: Config files
-- `deployments`: Helm Charts
-- `internal`: Main application directory for codes
-- `pkg`: Go packages that their logic is independent of this project and can become handy in other projects as well.
-- `web`: web interface of application including rest and grpc
-- `test`: test data like jwt keys
+This is template topic given in `vendor:topics[#]:template`.
-# Accounting with Soteria-CLI
+```yaml
+- type: driver_location
+ template: ^{{.company}}/driver/{{.sub}}/location$
+ accesses:
+ 0: "2"
+ 1: "-1"
+```
-## Installation
+```regex
+^{{.company}}/driver/{{HashID .hashType .sub (IssToSnappID .iss)}}/location/[a-zA-Z0-9-_]+$
+```
-Install [pipenv](https://pipenv.pypa.io/en/latest/).
+After parsing the template we get something like this
-```sh
-poetry install
-poetry shell
+```regex
+// company=snapp
+// hashType=0
+// sub=D96ZbvJakLp4PYd
+// iss=0
+^snapp/driver/D96ZbvJakLp4PYd/location/[a-zA-Z0-9-_]+$
```
-## EMQ User (http-auth)
-
-1. First create an `emq` account
+Now if the requested topic match the created topic it is considered as a valid topic for that particular user.
-```sh
-python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" new -u gossiper -p password -t emq
+```text
+requested_topic: snapp/driver/D96ZbvJakLp4PYd/location/23fw49vxd
+created_topic_regex: ^snapp/driver/D96ZbvJakLp4PYd/location/[a-zA-Z0-9-_]+$
```
-2. Add rule for accessing the requested topic
+#### Available Variables
-```sh
-python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" rules-add --username gossiper --password password --topic shared_location --access-type pubsub
-```
+These are the variables available to use in the topic templates.
-3. Set account secret
+- `iss`
+ issuer obtained from JWT token
+- `sub`
+ subject obtained from JWT token
+- `hashType`
+ Hash type field defined in topic template configuration
-```sh
-python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" set-secret --username gossiper --password password --secret secret
-```
+ | HashType | Value |
+ | -------- | ----- |
+ | HashID | 0 |
+ | MD5 | 1 |
-4. Set token timeout in nanoseconds
+- `company`
+ company field defined in vendor configuration
-```sh
-python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" set-expire --username gossiper --password password -e $(( 356 * 24 * 60 * 60 * 1000 * 1000 * 1000))
-```
+#### Available Functions
-5. Checkout the created user
+These are the function available to use in the topic templates.
-```sh
-python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" show --username gossiper --password password
-```
+- `IssToEntity(iss string) string`
+ convert `iss` obtained from JWT token to defined entity in `issEntityMap`
+- `IssToPeer(iss string) string`
+ convert `iss` obtained from JWT token to define peer in `issPeerMap`
+- `IssToSnappID(iss string) string`
+ convert `iss` obtained from JWT token to `snappid.audience`
+- `HashID(hashType int, sub string, snappID snappid.audience)`
+ generated `hashID` for the given `subject` base on the `hashType` and `snappid.audience`
-6. Generate token and have fun with `emq`
+**Note**: `snappid.audience` only is available for issuer 0 and 1 which are for driver and passenger respectively.
-```sh
-python3 main.py -b "https://soteria-snapp-ode-004.apps.private.teh-1.snappcloud.io/" token --username gossiper --secret secret --grant-type pub
-```
+#### Accesses
-## EMQ Superuser (redis-auth)
+List of all types of access on a topic.
-The main job of superuser authentication is on the emq and the redis. soteria only makes the creation process easier.
+| Access | Value |
+| ------------------- | ----- |
+| Subscribe | 1 |
+| Publish | 2 |
+| Subscribe & Publish | 3 |
+| None | -1 |
-1. Create EMQ superuser on redis. the command returns the authentication information for your service.
+#### Suggested Issuers
-```sh
-python3 main.py -b "https://soteria-snapp-ode-012.apps.private.teh-1.snappcloud.io/" superuser --username parham --password password --duration $((1000 * 1000 * 1000))
-```
+Use any value for issuer but if you have an entity called `Driver` or `Passenger`,
+we recommend use the following issuers for them.
+
+| Issuer | Value |
+| --------- | ----- |
+| Driver | 0 |
+| Passenger | 1 |
diff --git a/api/swagger.yml b/api/swagger.yml
index 5cae6513..221ecc01 100644
--- a/api/swagger.yml
+++ b/api/swagger.yml
@@ -1,65 +1,46 @@
---
swagger: '2.0'
info:
- version: 3.5.0
+ version: 5.0.0
title: Soteria
-
paths:
/auth:
post:
summary: Authenticates EMQ user's connection request
produces:
- - text
+ - text
consumes:
- - application/json
+ - application/json
parameters:
- - in: body
- name: request
- description: Request body
- schema:
- $ref: '#/definitions/auth'
+ - in: body
+ name: request
+ description: Request body
+ schema:
+ $ref: '#/definitions/auth'
responses:
401:
description: Request is not authorized
400:
description: Bad input parameter
200:
- description: if body is empty means OK, if contains "ignore" it means request is ignored
+ description: >
+ if body is empty means OK,
+ if contains "ignore" it means request is ignored
/acl:
post:
summary: Authorizes EMQ user's subscription request
produces:
- - text
+ - text
consumes:
- - application/json
- parameters:
- - in: body
- name: request
- description: Request body
- schema:
- $ref: '#/definitions/acl'
- responses:
- 401:
- description: Request is not authorized
- 400:
- description: Bad input parameter
- 200:
- description: OK
- /token:
- post:
- summary: Generates token for EMQ users
- produces:
- - text
- consumes:
- - application/json
+ - application/json
parameters:
- - in: body
- name: request
- description: Request body
- schema:
- $ref: '#/definitions/token'
+ - in: body
+ name: request
+ description: Request body
+ schema:
+ $ref: '#/definitions/acl'
responses:
401:
description: Request is not authorized
@@ -68,187 +49,6 @@ paths:
200:
description: OK
- /accounts:
- post:
- summary: Creates new account
- produces:
- - application/json
- consumes:
- - application/json
- parameters:
- - in: body
- name: request
- description: Request body
- schema:
- $ref: '#/definitions/createaccount'
- responses:
- 400:
- description: Bad input parameter
- 200:
- description: OK
-
- /accounts/:username:
- get:
- summary: gets account
- produces:
- - application/json
- consumes:
- - application/json
- security:
- - basicAuth: []
- responses:
- 400:
- description: Bad input parameter
- schema:
- $ref: '#/definitions/response'
- 200:
- description: OK
- schema:
- $ref: '#/definitions/user'
- put:
- summary: updates the account
- produces:
- - application/json
- consumes:
- - application/json
- security:
- - basicAuth: []
- parameters:
- - in: body
- name: request
- description: Request body
- schema:
- $ref: '#/definitions/updateaccount'
-
- responses:
- 400:
- description: Bad input parameter
- schema:
- $ref: '#/definitions/response'
- 500:
- description: Internal Server Error
- schema:
- $ref: '#/definitions/response'
- 200:
- description: OK
- schema:
- $ref: '#/definitions/response'
- delete:
- summary: deletes the account
- produces:
- - application/json
- consumes:
- - application/json
- security:
- - basicAuth: []
- responses:
- 400:
- description: Bad input parameter
- schema:
- $ref: '#/definitions/response'
- 500:
- description: Internal Server Error
- schema:
- $ref: '#/definitions/response'
- 200:
- description: OK
- schema:
- $ref: '#/definitions/response'
-
-
- /accounts/:username/rules:
- post:
- summary: creates new rule
- produces:
- - application/json
- consumes:
- - application/json
- security:
- - basicAuth: []
- parameters:
- - in: body
- name: request
- description: Request body
- schema:
- $ref: '#/definitions/rule'
- responses:
- 400:
- description: Bad input parameter
- schema:
- $ref: '#/definitions/response'
- 200:
- description: OK
- schema:
- $ref: '#/definitions/response'
-
-
- /accounts/:username/rules/:uuid:
- get:
- summary: get the rule with uuid
- produces:
- - application/json
- consumes:
- - application/json
- security:
- - basicAuth: []
- responses:
- 400:
- description: Bad input parameter
- schema:
- $ref: '#/definitions/response'
- 200:
- description: OK
- schema:
- $ref: '#/definitions/response'
-
- put:
- summary: updates a rule
- produces:
- - application/json
- consumes:
- - application/json
- security:
- - basicAuth: []
- parameters:
- - in: body
- name: request
- description: Request body
- schema:
- $ref: '#/definitions/rule'
- responses:
- 400:
- description: Bad input parameter
- schema:
- $ref: '#/definitions/response'
- 200:
- description: OK
- schema:
- $ref: '#/definitions/response'
-
- delete:
- summary: deletes the rule with uuid
- produces:
- - application/json
- consumes:
- - application/json
- security:
- - basicAuth: []
- responses:
- 400:
- description: Bad input parameter
- schema:
- $ref: '#/definitions/response'
- 200:
- description: OK
- schema:
- $ref: '#/definitions/response'
-
-components:
- securitySchemes:
- basicAuth: # <-- arbitrary name for the security scheme
- type: http
- scheme: basic
-
definitions:
auth:
type: object
@@ -274,94 +74,3 @@ definitions:
type: string
enum: ["1", "2", "3"]
example: "1"
-
- token:
- type: object
- required:
- - grant_type
- - client_id
- - client_secret
- properties:
- grant_type:
- type: string
- client_id:
- type: string
- example: "snapp-box"
- client_secret:
- type: string
-
- createaccount:
- type: object
- required:
- - username
- - password
- - user_type
- properties:
- username:
- type: string
- password:
- type: string
- user_type:
- type: string
- enum: ["HeraldUser", "EMQUser", "Staff"]
-
- updateaccount:
- type: object
- properties:
- new_password:
- type: string
- IPs:
- type: array
- items:
- type: string
- token_expiration:
- type: string
- secret:
- type: string
- user_type:
- type: string
- enum: ["HeraldUser", "EMQUser", "Staff"]
-
- user:
- type: object
- properties:
- username:
- type: string
- password:
- type: string
- IPs:
- type: array
- items:
- type: string
- token_expiration_duration:
- type: string
- rules:
- type: array
- items:
- schema:
- $ref: '#/definitions/rule'
- example:
- uuid: "1"
- endpoint: "/event"
- access_type: "2"
-
- response:
- type: object
- properties:
- code:
- type: integer
- message:
- type: string
- data:
- type: object
-
- rule:
- type: object
- properties:
- endpoint:
- type: string
- topic:
- type: string
- access_type:
- type: string
- enum: ["2", "1", "3"]
diff --git a/build/package/Dockerfile b/build/package/Dockerfile
new file mode 100644
index 00000000..1f0cfa16
--- /dev/null
+++ b/build/package/Dockerfile
@@ -0,0 +1,27 @@
+FROM golang:1.23-alpine3.20 as builder
+
+# hadolint ignore=DL3018
+RUN apk --no-cache add git
+
+WORKDIR /app
+
+COPY go.mod go.sum ./
+
+RUN go mod download
+
+COPY . .
+
+WORKDIR /app/cmd/soteria
+RUN go build -o /soteria
+
+FROM alpine:3.20
+
+# hadolint ignore=DL3018
+RUN apk --no-cache add ca-certificates tzdata && \
+ mkdir /app
+
+COPY --from=builder /soteria /app
+WORKDIR /app
+
+ENTRYPOINT ["/app/soteria" ]
+CMD ["serve"]
diff --git a/build/package/docker-bake.json b/build/package/docker-bake.json
new file mode 100644
index 00000000..4e9a7c7b
--- /dev/null
+++ b/build/package/docker-bake.json
@@ -0,0 +1,22 @@
+{
+ "group": {
+ "default": {
+ "targets": [
+ "soteria"
+ ]
+ }
+ },
+ "variable": {
+ "TAG": {
+ "default": "latest"
+ }
+ },
+ "target": {
+ "soteria": {
+ "dockerfile": "build/package/Dockerfile",
+ "tags": [
+ "ghcr.io/snapp-incubator/soteria:${TAG}"
+ ]
+ }
+ }
+}
diff --git a/deployments/soteria-init/.helmignore b/charts/soteria/.helmignore
similarity index 100%
rename from deployments/soteria-init/.helmignore
rename to charts/soteria/.helmignore
diff --git a/charts/soteria/Chart.yaml b/charts/soteria/Chart.yaml
new file mode 100644
index 00000000..40c0e4c5
--- /dev/null
+++ b/charts/soteria/Chart.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: v2
+name: soteria
+icon: https://github.com/snapp-incubator/soteria/blob/main/.github/assets/logo.jpg
+description: Soteria provides EMQX authentication using HTTP
+
+maintainers:
+ - name: 1995parham
+ url: https://github.com/1995parham
+ email: parham.alvani@gmail.com
+
+type: application
+
+version: 0.0.0
+appVersion: latest
diff --git a/charts/soteria/templates/NOTES.txt b/charts/soteria/templates/NOTES.txt
new file mode 100644
index 00000000..f7bdfe37
--- /dev/null
+++ b/charts/soteria/templates/NOTES.txt
@@ -0,0 +1 @@
+Soteria does the emq authentication and its serivce must be set in emqx http_auth configuration.
diff --git a/deployments/soteria/templates/_helpers.tpl b/charts/soteria/templates/_helpers.tpl
similarity index 88%
rename from deployments/soteria/templates/_helpers.tpl
rename to charts/soteria/templates/_helpers.tpl
index 183046af..5f6e7f44 100644
--- a/deployments/soteria/templates/_helpers.tpl
+++ b/charts/soteria/templates/_helpers.tpl
@@ -35,11 +35,18 @@ Common labels
*/}}
{{- define "soteria.labels" -}}
helm.sh/chart: {{ include "soteria.chart" . }}
+app: soteria
{{ include "soteria.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
-app.kubernetes.io/managed-by: {{ .Release.Service }}
+app.snappcloud.io/managed-by: {{ .Values.labels.managedby }}
+app.snappcloud.io/created-by: {{ .Values.labels.createdby }}
+{{- end }}
+
+{{- define "soteria.podLabels" -}}
+service: soteria
+owned-by: dispatching
{{- end }}
{{/*
diff --git a/charts/soteria/templates/configmap.yaml b/charts/soteria/templates/configmap.yaml
new file mode 100644
index 00000000..b430d367
--- /dev/null
+++ b/charts/soteria/templates/configmap.yaml
@@ -0,0 +1,17 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ include "soteria.fullname" . }}
+ namespace: {{ $.Release.Namespace }}
+ labels:
+ {{- include "soteria.labels" . | nindent 4 }}
+data:
+ config.yaml: |
+ {{- toYaml .Values.config | nindent 4}}
+ vendors:
+ {{- range $key, $value := .Values.vendors}}
+ {{- if $value}}
+ - {{- toYaml $value | nindent 8}}
+ {{- end}}
+ {{- end}}
diff --git a/deployments/soteria/templates/deployment.yaml b/charts/soteria/templates/deployment.yaml
similarity index 71%
rename from deployments/soteria/templates/deployment.yaml
rename to charts/soteria/templates/deployment.yaml
index 4d70dd49..f3ddfb9c 100644
--- a/deployments/soteria/templates/deployment.yaml
+++ b/charts/soteria/templates/deployment.yaml
@@ -2,9 +2,12 @@ apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "soteria.fullname" . }}
+ namespace: {{ $.Release.Namespace }}
labels: {{- include "soteria.labels" . | nindent 4 }}
spec:
+ {{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
+ {{- end }}
selector:
matchLabels:
{{- include "soteria.selectorLabels" . | nindent 6 }}
@@ -15,16 +18,9 @@ spec:
type: RollingUpdate
template:
metadata:
- annotations:
- prometheus.io/scrape: "true"
- prometheus.io/path: "/metrics"
- {{ range .Values.service.ports }}
- {{ if eq .name "http"}}
- prometheus.io/port: {{ .port | quote }}
- {{ end }}
- {{ end }}
labels:
{{- include "soteria.selectorLabels" . | nindent 8 }}
+ {{- include "soteria.podLabels" . | nindent 8 }}
spec:
containers:
- name: app
@@ -68,23 +64,19 @@ spec:
periodSeconds: 23
successThreshold: 1
timeoutSeconds: 2
- envFrom:
- - configMapRef:
- name: {{ include "soteria.fullname" . }}
- - secretRef:
- name: {{ include "soteria.fullname" . }}-auth-envs
+ env:
+ - name: TZ
+ value: {{ .Values.timezone }}
volumeMounts:
- {{- $root := . -}}
- {{- range $key, $value := .Values.jwtKeys }}
- - name: {{ include "soteria.fullname" $ }}-jwt-keys
- mountPath: "{{ $root.Values.authEnvs.jwt_keys_path }}{{ $key }}"
- subPath: {{ $key }}
+ - name: configuration
+ mountPath: "/app/config.yml"
+ subPath: "config.yaml"
readOnly: true
- {{ end }}
volumes:
- - name: {{ include "soteria.fullname" . }}-jwt-keys
- secret:
- secretName: {{ include "soteria.fullname" . }}-jwt-keys
+ - name: configuration
+ configMap:
+ defaultMode: 0440
+ name: {{ include "soteria.fullname" . }}
dnsPolicy: ClusterFirst
restartPolicy: Always
terminationGracePeriodSeconds: 30
diff --git a/deployments/soteria/templates/hpa.yaml b/charts/soteria/templates/hpa.yaml
similarity index 56%
rename from deployments/soteria/templates/hpa.yaml
rename to charts/soteria/templates/hpa.yaml
index 691e8fb4..198acd6e 100644
--- a/deployments/soteria/templates/hpa.yaml
+++ b/charts/soteria/templates/hpa.yaml
@@ -1,5 +1,5 @@
{{- if .Values.autoscaling.enabled }}
-apiVersion: autoscaling/v2beta1
+apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "soteria.fullname" . }}
@@ -13,10 +13,20 @@ spec:
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
+ {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ - type: Resource
+ resource:
+ name: memory
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+ {{- end }}
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
- targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+ target:
+ type: Utilization
+ averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- end }}
diff --git a/charts/soteria/templates/secret.yaml b/charts/soteria/templates/secret.yaml
new file mode 100644
index 00000000..2ab382f4
--- /dev/null
+++ b/charts/soteria/templates/secret.yaml
@@ -0,0 +1,15 @@
+---
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ include "soteria.fullname" . }}-jwt-keys
+ namespace: {{ $.Release.Namespace }}
+ labels:
+ {{- include "soteria.labels" . | nindent 4 }}
+
+stringData:
+ {{- range $key, $value := .Values.jwtKeys }}
+ {{- if $value }}
+ {{ $key }}: {{ $value | quote }}
+ {{- end }}
+ {{- end }}
diff --git a/deployments/soteria/templates/service.yaml b/charts/soteria/templates/service.yaml
similarity index 91%
rename from deployments/soteria/templates/service.yaml
rename to charts/soteria/templates/service.yaml
index 21da2a01..01a8a229 100644
--- a/deployments/soteria/templates/service.yaml
+++ b/charts/soteria/templates/service.yaml
@@ -2,6 +2,7 @@ apiVersion: v1
kind: Service
metadata:
name: {{ include "soteria.fullname" . }}
+ namespace: {{ $.Release.Namespace }}
labels:
{{- include "soteria.labels" . | nindent 4 }}
spec:
diff --git a/deployments/soteria/templates/servicemonitor.yaml b/charts/soteria/templates/servicemonitor.yaml
similarity index 92%
rename from deployments/soteria/templates/servicemonitor.yaml
rename to charts/soteria/templates/servicemonitor.yaml
index e759e7f5..8b494c7f 100644
--- a/deployments/soteria/templates/servicemonitor.yaml
+++ b/charts/soteria/templates/servicemonitor.yaml
@@ -5,6 +5,8 @@ metadata:
name: {{ template "soteria.fullname" . }}
{{- if .Values.serviceMonitor.namespace }}
namespace: {{ .Values.serviceMonitor.namespace }}
+ {{ else }}
+ namespace: {{ $.Release.Namespace }}
{{- end }}
labels:
{{- include "soteria.labels" . | nindent 4 }}
@@ -29,7 +31,7 @@ spec:
{{- end }}
namespaceSelector:
matchNames:
- - {{ .Release.Namespace }}
+ - {{ $.Release.Namespace }}
selector:
matchLabels:
{{- include "soteria.selectorLabels" . | nindent 6 }}
diff --git a/deployments/soteria/templates/tests/test-connection.yaml b/charts/soteria/templates/tests/test-connection.yaml
similarity index 79%
rename from deployments/soteria/templates/tests/test-connection.yaml
rename to charts/soteria/templates/tests/test-connection.yaml
index 7ecafaf8..fb2635ef 100644
--- a/deployments/soteria/templates/tests/test-connection.yaml
+++ b/charts/soteria/templates/tests/test-connection.yaml
@@ -2,6 +2,7 @@ apiVersion: v1
kind: Pod
metadata:
name: {{ include "soteria.fullname" . }}-test-connection
+ namespace: {{ $.Release.Namespace }}
labels:
{{- include "soteria.labels" . | nindent 4 }}
annotations:
@@ -14,7 +15,7 @@ spec:
{{ range .Values.service.ports }}
{{ if eq .name "http"}}
args:
- - {{ include "soteria.fullname" $ }}:{{ .port }}
+ - {{ include "soteria.fullname" $ }}:{{ .port }}/metrics
{{ end }}
{{ end }}
restartPolicy: Never
diff --git a/charts/soteria/values.yaml b/charts/soteria/values.yaml
new file mode 100644
index 00000000..8e783d57
--- /dev/null
+++ b/charts/soteria/values.yaml
@@ -0,0 +1,111 @@
+---
+replicaCount: 1
+
+labels:
+ managedby: cloud-platform-team
+ createdby: cloud-platform-team
+
+image:
+ registry: ghcr.io
+ repository: snapp-incubator/soteria
+ pullPolicy: Always
+
+timezone: Asia/Tehran
+
+service:
+ type: ClusterIP
+ ports:
+ - name: http
+ port: 9999
+ protocol: tcp
+
+resources:
+ limits:
+ memory: 128Mi
+ cpu: 1
+ requests:
+ memory: 128Mi
+ cpu: 500m
+
+autoscaling:
+ enabled: false
+ minReplicas: 3
+ maxReplicas: 20
+ targetCPUUtilizationPercentage: 65
+ targetMemoryUtilizationPercentage: 80
+
+rollingParams:
+ maxSurge: 5
+ maxUnavailable: 0
+
+serviceMonitor:
+ enabled: false
+
+config:
+ default_vendor: "snapp"
+ http_port: 9999
+ logger:
+ level: "debug"
+ stacktrace: true
+ tracer:
+ enabled: false
+
+black_list_user_logging:
+ iss: 0
+ user_ids: []
+
+vendors:
+ snapp:
+ company: "snapp"
+ type: "manual"
+ hashid_map:
+ 0:
+ salt: "secret"
+ length: 15
+ 1:
+ salt: "secret"
+ length: 15
+ allowed_access_types: ["pub", "sub"]
+ topics:
+ - type: cab_event
+ template: ^{{IssToEntity .iss}}-event-{{ EncodeMD5 (DecodeHashID .sub .iss) }}$
+ hash_type: 1
+ # describe the way issuers (from the jwt token) can interact with topic.
+ # here issuer 1 (mapped to passenger in the iss_entity_map) can subscribe (second element in the allowed_access_types).
+ accesses:
+ 0: '1'
+ 1: '1'
+ keys:
+ 0: |-
+ -----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv
+ va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm
+ X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL
+ C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s
+ ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX
+ J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU
+ DwIDAQAB
+ -----END PUBLIC KEY-----
+ 1: |-
+ -----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl
+ +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK
+ aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8
+ 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI
+ Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN
+ YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS
+ pQIDAQAB
+ -----END PUBLIC KEY-----
+ iss_entity_map:
+ 0: "driver"
+ 1: "passenger"
+ default: "none"
+ iss_peer_map:
+ 0: "passenger"
+ 1: "driver"
+ default: "none"
+ jwt:
+ # provide keys and algorithm to parse JWT token.
+ iss_name: "iss"
+ sub_name: "sub"
+ signing_method: "RS512"
diff --git a/cmd/soteria/soteria.go b/cmd/soteria/soteria.go
index 2d2c3bf6..2608c189 100644
--- a/cmd/soteria/soteria.go
+++ b/cmd/soteria/soteria.go
@@ -1,24 +1,10 @@
package main
import (
- "log"
-
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/commands"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/commands/accounts"
+ "github.com/snapp-incubator/soteria/internal/cmd"
+ _ "go.uber.org/automaxprocs"
)
-var cli = commands.Root
-
-func init() {
- cli.AddCommand(commands.Serve)
-
- accounts.Accounts.AddCommand(accounts.Init)
- cli.AddCommand(accounts.Accounts)
- cli.AddCommand(commands.Token)
-}
-
func main() {
- if err := cli.Execute(); err != nil {
- log.Fatal(err)
- }
+ cmd.Execute()
}
diff --git a/config.example.yml b/config.example.yml
new file mode 100644
index 00000000..0529b832
--- /dev/null
+++ b/config.example.yml
@@ -0,0 +1,130 @@
+# Company name of the vendor to use if the incoming ACL request vendor is not found withing the registered vendors.
+default_vendor: snapp
+# Port of the HTTP server:
+http_port: 9999
+# Application logger config:
+logger:
+ level: debug
+ stacktrace: true
+black_list_user_logging:
+ iss: 0
+ user_ids: []
+ user_hashed_ids: []
+
+# Validator is the upstream backend service that can validate the tokens:
+validator:
+ url: http://validator-lb
+ timeout: "5s"
+# The list of different vendors or companies that Soteria should work with:
+vendors:
+ - allowed_access_types:
+ - pub
+ - sub
+ company: snapp
+ hash_id_map:
+ "0":
+ alphabet: ""
+ length: 15
+ salt: secret
+ "1":
+ alphabet: ""
+ length: 15
+ salt: secret
+ iss_entity_map:
+ "0": driver
+ "1": passenger
+ default: ""
+ iss_peer_map:
+ "0": passenger
+ "1": driver
+ default: ""
+ jwt:
+ iss_name: iss
+ signing_method: RS512
+ sub_name: sub
+ keys:
+ "0": |-
+ -----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv
+ va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm
+ X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL
+ C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s
+ ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX
+ J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU
+ DwIDAQAB
+ -----END PUBLIC KEY-----
+ "1": |-
+ -----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl
+ +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK
+ aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8
+ 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI
+ Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN
+ YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS
+ pQIDAQAB
+ -----END PUBLIC KEY-----
+ # Examples of different use cases of template functions:
+ # Topics are dynamics and their patterns can be defined using some GoTemplate functions.
+ #
+ #
+ # IssToEntity: converts iss claim token to company name.
+ #
+ # EncodeMD5: encode MD5 of the input
+ #
+ # DecodeHashID: runs hashid algorithm on the input. The first argument is the input of hashid and the second argument
+ # is the issuer of id of hash_id_map.
+ topics:
+ - accesses:
+ "0": "1"
+ "1": "1"
+ template: ^{{IssToEntity .iss}}-event-{{ EncodeMD5 (DecodeHashID .sub .iss) }}$
+ type: cab_event
+ - accesses:
+ "0": "2"
+ "1": "-1"
+ template: ^{{.company}}/driver/{{.sub}}/location$
+ type: driver_location
+ - accesses:
+ "0": "2"
+ "1": "2"
+ template: ^{{.company}}/passenger/{{.sub}}/location$
+ type: passenger_location
+ - accesses:
+ "0": "1"
+ "1": "1"
+ template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/superapp$
+ type: superapp_event
+ - accesses:
+ "0": "-1"
+ "1": "-1"
+ template: ^bucks$
+ type: box_event
+ - accesses:
+ "0": "1"
+ "1": "1"
+ template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/{{IssToPeer .iss}}-location$
+ type: shared_location
+ - accesses:
+ "0": "1"
+ "1": "1"
+ template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/chat$
+ type: chat
+ - accesses:
+ "0": "2"
+ "1": "2"
+ template: ^shared/{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/send$
+ type: general_call_entry
+ - accesses:
+ "0": "2"
+ "1": "2"
+ template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/[a-zA-Z0-9-_]+/send$
+ type: node_call_entry
+ - accesses:
+ "0": "1"
+ "1": "1"
+ template: ^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/receive$
+ type: call_outgoing
+tracer:
+ enabled: false
+ endpoint: 127.0.0.1:4317
+ ratio: 0.1
diff --git a/deployments/soteria-init/Chart.yaml b/deployments/soteria-init/Chart.yaml
deleted file mode 100644
index 5c21d685..00000000
--- a/deployments/soteria-init/Chart.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-apiVersion: v2
-name: soteria-init
-description: A Helm chart for Kubernetes
-
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
-type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 0.1.0
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
-appVersion: "1.16.0"
diff --git a/deployments/soteria-init/templates/NOTES.txt b/deployments/soteria-init/templates/NOTES.txt
deleted file mode 100644
index e69de29b..00000000
diff --git a/deployments/soteria-init/templates/_helpers.tpl b/deployments/soteria-init/templates/_helpers.tpl
deleted file mode 100644
index 8b61a174..00000000
--- a/deployments/soteria-init/templates/_helpers.tpl
+++ /dev/null
@@ -1,62 +0,0 @@
-{{/*
-Expand the name of the chart.
-*/}}
-{{- define "soteria-init.name" -}}
-{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
-{{- end }}
-
-{{/*
-Create a default fully qualified app name.
-We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
-If release name contains chart name it will be used as a full name.
-*/}}
-{{- define "soteria-init.fullname" -}}
-{{- if .Values.fullnameOverride }}
-{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
-{{- else }}
-{{- $name := default .Chart.Name .Values.nameOverride }}
-{{- if contains $name .Release.Name }}
-{{- .Release.Name | trunc 63 | trimSuffix "-" }}
-{{- else }}
-{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
-{{- end }}
-{{- end }}
-{{- end }}
-
-{{/*
-Create chart name and version as used by the chart label.
-*/}}
-{{- define "soteria-init.chart" -}}
-{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
-{{- end }}
-
-{{/*
-Common labels
-*/}}
-{{- define "soteria-init.labels" -}}
-helm.sh/chart: {{ include "soteria-init.chart" . }}
-{{ include "soteria-init.selectorLabels" . }}
-{{- if .Chart.AppVersion }}
-app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
-{{- end }}
-app.kubernetes.io/managed-by: {{ .Release.Service }}
-{{- end }}
-
-{{/*
-Selector labels
-*/}}
-{{- define "soteria-init.selectorLabels" -}}
-app.kubernetes.io/name: {{ include "soteria-init.name" . }}
-app.kubernetes.io/instance: {{ .Release.Name }}
-{{- end }}
-
-{{/*
-Create the name of the service account to use
-*/}}
-{{- define "soteria-init.serviceAccountName" -}}
-{{- if .Values.serviceAccount.create }}
-{{- default (include "soteria-init.fullname" .) .Values.serviceAccount.name }}
-{{- else }}
-{{- default "default" .Values.serviceAccount.name }}
-{{- end }}
-{{- end }}
diff --git a/deployments/soteria-init/templates/configmap.yaml b/deployments/soteria-init/templates/configmap.yaml
deleted file mode 100644
index aa44d96c..00000000
--- a/deployments/soteria-init/templates/configmap.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: {{ include "soteria-init.fullname" . }}
- labels:
- {{- include "soteria-init.labels" . | nindent 4 }}
-data:
- {{ range $key, $value := .Values.envs }}
- {{ if $value }}
- SOTERIA_{{ $key | upper }}: {{ $value | quote }}
- {{ end }}
- {{ end }}
diff --git a/deployments/soteria-init/templates/pod.yaml b/deployments/soteria-init/templates/pod.yaml
deleted file mode 100644
index 78aa6607..00000000
--- a/deployments/soteria-init/templates/pod.yaml
+++ /dev/null
@@ -1,31 +0,0 @@
-apiVersion: v1
-kind: Pod
-metadata:
- name: {{ include "soteria-init.fullname" . }}
- labels:
- {{- include "soteria-init.labels" . | nindent 4 }}
-spec:
- containers:
- - name: {{ .Chart.Name }}
- image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}"
- imagePullPolicy: {{ .Values.image.pullPolicy }}
- command: ["/app/soteria"]
- args:
- - "accounts"
- - "init"
- - "db.json"
- envFrom:
- - configMapRef:
- name: {{ include "soteria-init.fullname" . }}
- volumeMounts:
- - name: {{ include "soteria-init.fullname" . }}-db
- mountPath: "/app/db.json"
- subPath: db.json
- readOnly: true
- resources:
- {{- toYaml .Values.resources | nindent 8 }}
- restartPolicy: Never
- volumes:
- - name: {{ include "soteria-init.fullname" . }}-db
- secret:
- secretName: {{ include "soteria-init.fullname" . }}-db
diff --git a/deployments/soteria-init/templates/secret.yaml b/deployments/soteria-init/templates/secret.yaml
deleted file mode 100644
index d0a0e3fa..00000000
--- a/deployments/soteria-init/templates/secret.yaml
+++ /dev/null
@@ -1,10 +0,0 @@
----
-apiVersion: v1
-kind: Secret
-metadata:
- name: {{ include "soteria-init.fullname" . }}-db
- labels:
- {{- include "soteria-init.labels" . | nindent 4 }}
-stringData:
- db.json: |
- {{ toRawJson .Values.db | nindent 4 }}
diff --git a/deployments/soteria-init/values.yaml b/deployments/soteria-init/values.yaml
deleted file mode 100644
index 7f8b1f35..00000000
--- a/deployments/soteria-init/values.yaml
+++ /dev/null
@@ -1,170 +0,0 @@
-# Default values for Soteria.
-# This is a YAML-formatted file.
-# Declare variables to be passed into your templates.
-
-
-image:
- registry: docker-registry.default.svc:5000
- repository: realtime-staging/soteria
- tag: dev
- pullPolicy: Always
-
-resources:
- limits:
- memory: 128Mi
- cpu: 2
- requests:
- memory: 128Mi
- cpu: 1
-
-envs:
- http_port: "9999"
- grpc_port: "50051"
- mode: "debug"
- redis_address: "main-redis-master.dispatching-staging.svc:6379"
- redis_pass: ""
- redis_pool_size: "10"
- redis_max_retries: "0"
- redis_read_timeout: "3s"
- redis_pool_timeout: "4s"
- redis_min_retry_backoff: "8ms"
- redis_max_retry_backoff: "512ms"
- redis_idle_timeout: "300s"
- redis_idle_check_frequency: "60s"
- redis_set_member_exp_time: "300s"
- redis_min_idle_connections: "5"
- logger_level: "debug"
- logger_sentry_enabled: "false"
- logger_sentry_dsn: ""
- logger_sentry_timeout: "500ms"
- allowed_access_types: "sub,pub"
- cache_enabled: "true"
- cache_expiration: "600s"
-
-db:
- - meta_data:
- model_name: user
- date_created: '2020-09-29T10:44:15.106544359+03:30'
- date_modified: '2020-09-29T10:44:15.106544423+03:30'
- username: driver
- password: password
- type: EMQUser
- ips:
- secret: secret
- token_expiration_duration: 2592000000000000
- rules:
- - uuid: 84df6017-a850-42a3-a06f-22306a006a8d
- endpoint: ''
- topic: cab_event
- access_type: '1'
- - uuid: df0b884e-41dc-444e-81d4-67f62ae2174c
- endpoint: ''
- topic: driver_location
- access_type: '2'
- - uuid: 1375192a-6fc5-4409-abd8-0d21ef106bee
- endpoint: ''
- topic: shared_location
- access_type: '1'
- - uuid: d7aeb294-6271-470a-987a-01ea4f035e5d
- endpoint: ''
- topic: superapp_event
- access_type: '1'
- - meta_data:
- model_name: user
- date_created: '2020-09-29T10:44:15.106560757+03:30'
- date_modified: '2020-09-29T10:44:15.106560814+03:30'
- username: passenger
- password: password
- type: EMQUser
- ips:
- secret: secret
- token_expiration_duration: 2592000000000000
- rules:
- - uuid: a51f1898-3381-456f-a5fe-0e9d80b5bee0
- endpoint: ''
- topic: cab_event
- access_type: '1'
- - uuid: 8de97e3f-e772-4da4-8f15-4dc048f5c06e
- endpoint: ''
- topic: superapp_event
- access_type: '1'
- - uuid: c41260ed-70eb-4554-bc15-5714fc1bf3f8
- endpoint: ''
- topic: passenger_location
- access_type: '2'
- - uuid: 1375192a-6fc5-4409-abd8-0d21ef106bee
- endpoint: ''
- topic: shared_location
- access_type: '1'
- - meta_data:
- model_name: user
- date_created: '2020-09-29T10:44:15.106567252+03:30'
- date_modified: '2020-09-29T10:44:15.106567311+03:30'
- username: box
- password: password
- type: EMQUser
- ips:
- secret: secret
- token_expiration_duration: 2592000000000000
- rules:
- - uuid: e591af90-8191-4118-941a-7ef4434414ee
- endpoint: ''
- topic: box_event
- access_type: '1'
- - meta_data:
- model_name: user
- date_created: '2020-09-29T10:44:15.106569957+03:30'
- date_modified: '2020-09-29T10:44:15.106570022+03:30'
- username: colony-subscriber
- password: password
- type: EMQUser
- ips:
- secret: secret
- token_expiration_duration: 2592000000000000
- rules:
- - uuid: bcbe0a57-1706-4de7-a948-71c56c9700c4
- endpoint: ''
- topic: driver_location
- access_type: '1'
- - meta_data:
- model_name: user
- date_created: '2020-09-29T10:44:15.106573104+03:30'
- date_modified: '2020-09-29T10:44:15.106573162+03:30'
- username: gabriel
- password: password
- type: HeraldUser
- ips:
- secret: secret
- token_expiration_duration: 2592000000000000
- rules:
- - uuid: 305dc844-5df0-42dc-849b-deccfe2dab96
- endpoint: "/notification"
- topic: ''
- access_type: '2'
- - uuid: 5795227f-10a8-4822-ba5e-a1fc057a0a95
- endpoint: "/event"
- topic: ''
- access_type: '2'
- - meta_data:
- model_name: user
- date_created: '2021-06-23T08:42:24.722521722Z'
- date_modified: '2021-07-06T18:40:10.074710788Z'
- username: gossiper
- password: password
- type: EMQUser
- ips:
- secret: secret
- token_expiration_duration: 30758400000000000
- rules:
- - uuid: ec6f659e-fe73-41cd-805e-c5285bd92a8d
- endpoint: ''
- topic: shared_location
- access_type: '2'
- - uuid: a8dc06c4-bb11-4cb9-8da7-84ae34d2d14a
- endpoint: ''
- topic: driver_location
- access_type: '1'
- - uuid: cda59530-d412-4c6d-bb3b-7a89c6bf58c5
- endpoint: ''
- topic: passenger_location
- access_type: '1'
diff --git a/deployments/soteria/.helmignore b/deployments/soteria/.helmignore
deleted file mode 100644
index 0e8a0eb3..00000000
--- a/deployments/soteria/.helmignore
+++ /dev/null
@@ -1,23 +0,0 @@
-# Patterns to ignore when building packages.
-# This supports shell glob matching, relative path matching, and
-# negation (prefixed with !). Only one pattern per line.
-.DS_Store
-# Common VCS dirs
-.git/
-.gitignore
-.bzr/
-.bzrignore
-.hg/
-.hgignore
-.svn/
-# Common backup files
-*.swp
-*.bak
-*.tmp
-*.orig
-*~
-# Various IDEs
-.project
-.idea/
-*.tmproj
-.vscode/
diff --git a/deployments/soteria/Chart.yaml b/deployments/soteria/Chart.yaml
deleted file mode 100644
index aed4a1bf..00000000
--- a/deployments/soteria/Chart.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-apiVersion: v2
-name: soteria
-description: Soteria Helm Chart
-
-# A chart can be either an 'application' or a 'library' chart.
-#
-# Application charts are a collection of templates that can be packaged into versioned archives
-# to be deployed.
-#
-# Library charts provide useful utilities or functions for the chart developer. They're included as
-# a dependency of application charts to inject those utilities and functions into the rendering
-# pipeline. Library charts do not define any templates and therefore cannot be deployed.
-type: application
-
-# This is the chart version. This version number should be incremented each time you make changes
-# to the chart and its templates, including the app version.
-# Versions are expected to follow Semantic Versioning (https://semver.org/)
-version: 3.8.4
-
-# This is the version number of the application being deployed. This version number should be
-# incremented each time you make changes to the application. Versions are not expected to
-# follow Semantic Versioning. They should reflect the version the application is using.
-# It is recommended to use it with quotes.
-appVersion: "v3-8-4"
diff --git a/deployments/soteria/templates/NOTES.txt b/deployments/soteria/templates/NOTES.txt
deleted file mode 100644
index 786ac34a..00000000
--- a/deployments/soteria/templates/NOTES.txt
+++ /dev/null
@@ -1,22 +0,0 @@
-1. Get the application URL by running these commands:
-{{- if .Values.ingress.enabled }}
-{{- range $host := .Values.ingress.hosts }}
- {{- range .paths }}
- http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
- {{- end }}
-{{- end }}
-{{- else if contains "NodePort" .Values.service.type }}
- export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "soteria.fullname" . }})
- export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
- echo http://$NODE_IP:$NODE_PORT
-{{- else if contains "LoadBalancer" .Values.service.type }}
- NOTE: It may take a few minutes for the LoadBalancer IP to be available.
- You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "soteria.fullname" . }}'
- export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "soteria.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
- echo http://$SERVICE_IP:{{ .Values.service.port }}
-{{- else if contains "ClusterIP" .Values.service.type }}
- export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "soteria.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
- export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
- echo "Visit http://127.0.0.1:8080 to use your application"
- kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
-{{- end }}
diff --git a/deployments/soteria/templates/configmap.yaml b/deployments/soteria/templates/configmap.yaml
deleted file mode 100644
index 3d3a971a..00000000
--- a/deployments/soteria/templates/configmap.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: {{ include "soteria.fullname" . }}
- labels:
- {{- include "soteria.labels" . | nindent 4 }}
-data:
- {{ range $key, $value := .Values.envs }}
- {{ if $value }}
- SOTERIA_{{ $key | upper }}: {{ $value | quote }}
- {{ end }}
- {{ end }}
diff --git a/deployments/soteria/templates/ingress.yaml b/deployments/soteria/templates/ingress.yaml
deleted file mode 100644
index b259cd16..00000000
--- a/deployments/soteria/templates/ingress.yaml
+++ /dev/null
@@ -1,41 +0,0 @@
-{{- if .Values.ingress.enabled -}}
-{{- $fullName := include "soteria.fullname" . -}}
-{{- $svcPort := .Values.service.port -}}
-{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
-apiVersion: networking.k8s.io/v1beta1
-{{- else -}}
-apiVersion: extensions/v1beta1
-{{- end }}
-kind: Ingress
-metadata:
- name: {{ $fullName }}
- labels:
- {{- include "soteria.labels" . | nindent 4 }}
- {{- with .Values.ingress.annotations }}
- annotations:
- {{- toYaml . | nindent 4 }}
- {{- end }}
-spec:
- {{- if .Values.ingress.tls }}
- tls:
- {{- range .Values.ingress.tls }}
- - hosts:
- {{- range .hosts }}
- - {{ . | quote }}
- {{- end }}
- secretName: {{ .secretName }}
- {{- end }}
- {{- end }}
- rules:
- {{- range .Values.ingress.hosts }}
- - host: {{ .host | quote }}
- http:
- paths:
- {{- range .paths }}
- - path: {{ .path }}
- backend:
- serviceName: {{ $fullName }}
- servicePort: {{ $svcPort }}
- {{- end }}
- {{- end }}
-{{- end }}
diff --git a/deployments/soteria/templates/secret.yaml b/deployments/soteria/templates/secret.yaml
deleted file mode 100644
index 3bf55f59..00000000
--- a/deployments/soteria/templates/secret.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-apiVersion: v1
-kind: Secret
-metadata:
- name: {{ include "soteria.fullname" . }}-auth-envs
- labels:
- {{- include "soteria.labels" . | nindent 4 }}
-stringData:
- {{- range $key, $value := .Values.authEnvs }}
- {{- if $value }}
- SOTERIA_{{ $key | upper }}: {{ $value | quote }}
- {{- end }}
- {{- end }}
-
----
-apiVersion: v1
-kind: Secret
-metadata:
- name: {{ include "soteria.fullname" . }}-jwt-keys
- labels:
- {{- include "soteria.labels" . | nindent 4 }}
-stringData:
- {{- range $key, $value := .Values.jwtKeys }}
- {{- if $value }}
- {{ $key }}: {{ $value | quote }}
- {{- end }}
- {{- end }}
diff --git a/deployments/soteria/values.yaml b/deployments/soteria/values.yaml
deleted file mode 100644
index ff79e2c9..00000000
--- a/deployments/soteria/values.yaml
+++ /dev/null
@@ -1,131 +0,0 @@
-# Default values for Soteria.
-# This is a YAML-formatted file.
-# Declare variables to be passed into your templates.
-
-replicaCount: 1
-
-namespace: dispatching-staging
-region: teh-1
-
-image:
- registry: docker-registry.default.svc:5000
- repository: realtime-staging/soteria
- tag: dev
- pullPolicy: Always
-
-service:
- type: ClusterIP
- ports:
- - name: http
- port: 9999
- protocol: tcp
- - name: grpc
- port: 50051
- protocol: tcp
-
-resources:
- limits:
- memory: 128Mi
- cpu: 2
- requests:
- memory: 128Mi
- cpu: 1
-
-autoscaling:
- enabled: false
- minReplicas: 3
- maxReplicas: 20
- targetCPUUtilizationPercentage: 65
-
-rollingParams:
- maxSurge: 5
- maxUnavailable: 0
-
-ingress:
- enabled: false
- annotations: { }
- # kubernetes.io/ingress.class: nginDeploymentConfigx
- # kubernetes.io/tls-acme: "true"
- hosts:
- - host: chart-example.local
- paths: [ ]
- tls: [ ]
-
-serviceMonitor:
- enabled: false
-
-jwtKeys:
- 0.pem: |-
- -----BEGIN PUBLIC KEY-----
- MIIBITANBgkqhkiG9w0BAQEFAAOCAQ4AMIIBCQKCAQBk7O6M5p4eYNAwtVU2beGa
- W4mhFG94OtYUWDl1E7UUrhUNGf97Eb/45NjQszu0YPERnApJc2RUm2TrS7iq0mHz
- Xbwf+CbNF54Q5mjuHcpBKgvFwUUSCCYBftmRc4xbFIH4Oh3nHC2GeukUS9TmJwjM
- tJKyU0Ve8BK5BgjhagM7XSs+scE2mxemoWtcs6mJLtBuEgRGMgHW00mSdOcLp/+l
- oHpSzRYN92/DomwmmjGVy8Ji0faeHx+r79ZzE0E8Rcc29Yhrg1ymrjfkXg98WjAb
- TSv4UAN20lsBDejpnGEZKJrxHZ56gHgaJn6PKKCD6ItJA7y7iraCdBhCfAIUIz/z
- AgMBAAE=
- -----END PUBLIC KEY-----
- 1.pem: |-
- -----BEGIN PUBLIC KEY-----
- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1lNRwyNsDieWs6LvHOJ+
- GyehhRC4Pn5yL5edKP3565F3LtRDMrkzwDRsQbqnUtTea9HCdTdBv+lI8vE17qRi
- RQn10IMaIH6e4Aa3OWNClFhuqNOag7VmffsjTOgxHgHpfGAKVF/4BwqOHrdHFbAD
- VOiWB1hv9Uc0C5laffGAub7fj+EAI02zlrsNDxYW8vyF2H47N7VWcvgd3RhZpxlG
- 8bq9phl7Ja55YmQiT2Ic3/K5tsazg5z9lz6OTrx+JvWbefHFlJpjCLz5yefEaRmX
- 9L/zyDMi4jgFTZEWNXC2vIrxwZMFwFhBXEp0PcCbuHJgJIucbRrbwukQC16uHJwP
- zQIDAQAB
- -----END PUBLIC KEY-----
- 100.pem: |-
- -----BEGIN PUBLIC KEY-----
- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8
- OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu
- GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o
- Wsupn2HVhMSjFbE6dwIDAQAB
- -----END PUBLIC KEY-----
- 100.private.pem: |-
- -----BEGIN RSA PRIVATE KEY-----
- MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n
- gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs
- AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB
- AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1
- j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP
- hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs
- hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh
- 1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E
- pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg
- FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT
- 6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r
- m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ
- l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g==
- -----END RSA PRIVATE KEY-----
-
-authEnvs:
- jwt_keys_path: "/jwt_pems/"
- driver_salt: "12345678901234567890123456789012"
- passenger_salt: "12345678901234567890123456789012"
- driver_hash_length: "15"
- passenger_hash_length: "15"
-
-envs:
- http_port: "9999"
- grpc_port: "50051"
- mode: "debug"
- redis_address: "main-redis-master.dispatching-staging.svc:6379"
- redis_pass: ""
- redis_pool_size: "10"
- redis_max_retries: "0"
- redis_read_timeout: "3s"
- redis_pool_timeout: "4s"
- redis_min_retry_backoff: "8ms"
- redis_max_retry_backoff: "512ms"
- redis_idle_timeout: "300s"
- redis_idle_check_frequency: "60s"
- redis_set_member_exp_time: "300s"
- redis_min_idle_connections: "5"
- logger_level: "debug"
- logger_sentry_enabled: "false"
- logger_sentry_dsn: ""
- logger_sentry_timeout: "500ms"
- allowed_access_types: "sub,pub"
- cache_enabled: "true"
- cache_expiration: "600s"
diff --git a/docker-compose.yml b/docker-compose.yml
index 09463618..98454bea 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,30 +1,30 @@
-version: '3.7'
services:
soteria:
- container_name: soteria
- image: soteria:latest
- working_dir: /go/src/app
- ports:
- - 9999:9999
- - 50051:50051
- volumes:
- - .:/go/src/app
- depends_on:
- - redis
+ build:
+ dockerfile: ./build/package/Dockerfile
+ context: .
+ command: serve
+ emqx:
+ image: emqx/emqx
environment:
- SOTERIA_LOGGER_LEVEL: 'DEBUG'
- SOTERIA_JWT_KEYS_PATH: '/go/src/app/test/'
- SOTERIA_HTTP_PORT: '9999'
- SOTERIA_GRPC_PORT: '50051'
- SOTERIA_REDIS_ADDRESS: 'redis:6379'
- redis:
- image: redis
- container_name: soteria-redis
- ports:
- - 6379:6379
- jaeger:
- image: jaegertracing/all-in-one:latest
+ EMQX_DASHBOARD__DEFAULT_PASSWORD: public
+ EMQX_DASHBOARD__DEFAULT_USERNAME: admin
+ EMQX_AUTHENTICATION__1__MECHANISM: "password_based"
+ EMQX_AUTHENTICATION__1__BACKEND: "http"
+ EMQX_AUTHENTICATION__1__ENABLE: "true"
+ EMQX_AUTHENTICATION__1__METHOD: "post"
+ EMQX_AUTHENTICATION__1__URL: "http://soteria:9999/v2/auth"
+ EMQX_AUTHENTICATION__1__BODY: '{"username" = "$${username}", "password" = "$${password}", "token" = "$${username}"}'
+ EMQX_AUTHORIZATION__NO_MATCH: deny
+ EMQX_AUTHORIZATION__DENY_ACTION: disconnect
+ EMQX_AUTHORIZATION__SOURCES__1__TYPE: http
+ EMQX_AUTHORIZATION__SOURCES__1__METHOD: post
+ EMQX_AUTHORIZATION__SOURCES__1__URL: "http://soteria:9999/v2/acl"
+ EMQX_AUTHORIZATION__SOURCES__1__BODY: '{"username" = "$${username}", "password" = "$${password}", "token" = "$${username}"}'
+ EMQX_LISTENERS__TCP__DEFAULT__ENABLE_AUTHN: "quick_deny_anonymous"
+ EMQX_LISTENERS__TCP__INTERNAL__ENABLE: "true"
+ EMQX_LISTENERS__TCP__INTERNAL__BIND: 11883
+ EMQX_LISTENERS__TCP__INTERNAL__ENABLE_AUTHN: "false"
ports:
- - 16686:16686
- - "6831:6831/udp"
- - 14250:14250
+ - 1883:1883
+ - 18083:18083
diff --git a/docs/arch.png b/docs/arch.png
new file mode 100644
index 00000000..1a5250ef
Binary files /dev/null and b/docs/arch.png differ
diff --git a/go.mod b/go.mod
index 5c0559a3..794a1df8 100644
--- a/go.mod
+++ b/go.mod
@@ -1,32 +1,67 @@
-module gitlab.snapp.ir/dispatching/soteria/v3
+module github.com/snapp-incubator/soteria
-go 1.15
+go 1.23
require (
- github.com/HdrHistogram/hdrhistogram-go v1.1.0 // indirect
- github.com/alicebob/miniredis/v2 v2.13.1
- github.com/dgrijalva/jwt-go v3.2.0+incompatible
- github.com/gin-contrib/zap v0.0.1
- github.com/gin-gonic/gin v1.6.3
- github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
- github.com/go-playground/validator/v10 v10.3.0 // indirect
- github.com/go-redis/redis/v8 v8.3.2
- github.com/golang/protobuf v1.4.2
- github.com/google/uuid v1.1.2
- github.com/kelseyhightower/envconfig v1.4.0
- github.com/opentracing/opentracing-go v1.2.0
- github.com/patrickmn/go-cache v2.1.0+incompatible
- github.com/prometheus/client_golang v1.7.1
- github.com/speps/go-hashids v2.0.0+incompatible // indirect
- github.com/spf13/cobra v1.0.0
- github.com/stretchr/testify v1.7.0
- github.com/uber/jaeger-client-go v2.25.0+incompatible
- github.com/uber/jaeger-lib v2.4.0+incompatible // indirect
- gitlab.snapp.ir/dispatching/snappids/v2 v2.6.1
- go.uber.org/atomic v1.7.0 // indirect
- go.uber.org/automaxprocs v1.3.0
- go.uber.org/zap v1.16.0
- golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899
- google.golang.org/grpc v1.27.0
- google.golang.org/protobuf v1.25.0 // indirect
+ github.com/ansrivas/fiberprometheus/v2 v2.7.0
+ github.com/gofiber/contrib/fiberzap v1.0.2
+ github.com/gofiber/fiber/v2 v2.52.5
+ github.com/golang-jwt/jwt/v5 v5.2.1
+ github.com/knadh/koanf v1.5.0
+ github.com/knadh/koanf/v2 v2.1.1
+ github.com/prometheus/client_golang v1.20.3
+ github.com/speps/go-hashids/v2 v2.0.1
+ github.com/spf13/cobra v1.8.1
+ github.com/stretchr/testify v1.9.0
+ github.com/tidwall/pretty v1.2.1
+ go.opentelemetry.io/otel v1.29.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0
+ go.opentelemetry.io/otel/sdk v1.29.0
+ go.opentelemetry.io/otel/trace v1.29.0
+ go.uber.org/automaxprocs v1.5.3
+ go.uber.org/zap v1.27.0
+)
+
+require (
+ github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/beorn7/perks v1.0.1 // indirect
+ github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cespare/xxhash/v2 v2.3.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/fatih/structs v1.1.0 // indirect
+ github.com/fsnotify/fsnotify v1.7.0 // indirect
+ github.com/go-logr/logr v1.4.2 // indirect
+ github.com/go-logr/stdr v1.2.2 // indirect
+ github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
+ github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/mitchellh/copystructure v1.2.0 // indirect
+ github.com/mitchellh/reflectwalk v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/prometheus/client_model v0.6.1 // indirect
+ github.com/prometheus/common v0.59.1 // indirect
+ github.com/prometheus/procfs v0.15.1 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasthttp v1.55.0 // indirect
+ github.com/valyala/tcplisten v1.0.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect
+ go.opentelemetry.io/otel/metric v1.29.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.3.1 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ golang.org/x/net v0.29.0 // indirect
+ golang.org/x/sys v0.25.0 // indirect
+ golang.org/x/text v0.18.0 // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
+ google.golang.org/grpc v1.66.0 // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 6a496d2c..955908f7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,126 +1,92 @@
-bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI=
-bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA=
-cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I=
-github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
-github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af h1:wVe6/Ea46ZMeNkQjjBW6xcqyQA/j5e0D6GytH95g0gQ=
-github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
-github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
-github.com/alicebob/miniredis/v2 v2.13.1 h1:twArGFW9kCdxe0K1Ga+iWrZc9RdWalf7V5bQerNoPK0=
-github.com/alicebob/miniredis/v2 v2.13.1/go.mod h1:0UIBNuf97uxrWhdVBpJvPtafKyGpL2NS2pYe0tYM97k=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA=
-github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
-github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+github.com/ansrivas/fiberprometheus/v2 v2.7.0 h1:09XiSzG0J7aZp7RviklngdWdDbSybKjhuWAstp003Gg=
+github.com/ansrivas/fiberprometheus/v2 v2.7.0/go.mod h1:hSJdO65lfnWW70Qn9uGdXXsUUSkckbhuw5r/KesygpU=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
+github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
+github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
+github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
+github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
+github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
+github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
+github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
+github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
+github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
-github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
+github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
-github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
-github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
-github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
-github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
-github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
-github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
-github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
-github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
-github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
-github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
-github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
-github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
-github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
-github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
-github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
-github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954 h1:RMLoZVzv4GliuWafOuPuQDKSm1SJph7uCRnnS61JAn4=
-github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90 h1:WXb3TSNmHp2vHoCroCIB1foO/yQ36swABL8aOVeDpgg=
-github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
-github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
-github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
-github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-contrib/zap v0.0.1 h1:wsX/ahRftxPiXpiUw0YqyHj+TQTKtv+DAFWH84G1Uvg=
-github.com/gin-contrib/zap v0.0.1/go.mod h1:vJJndZ8f44gsTHQrDPIB4YOZzwOwiEIdE0mMrZLOogk=
-github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
-github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
-github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
-github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
-github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
-github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
-github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
-github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
-github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
-github.com/go-playground/validator/v10 v10.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o=
-github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
-github.com/go-redis/redis/v8 v8.3.2 h1:1bJscgN2yGtKLW6MsTRosa2LHyeq94j0hnNAgRZzj/M=
-github.com/go-redis/redis/v8 v8.3.2/go.mod h1:jszGxBCez8QA1HWSmQxJO9Y82kNibbUmeYhKWrBejTU=
-github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
+github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc=
+github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/go-viper/mapstructure/v2 v2.1.0 h1:gHnMa2Y/pIxElCH2GlZZ1lZSsn6XMtufpGyP1XxdC/w=
+github.com/go-viper/mapstructure/v2 v2.1.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gofiber/contrib/fiberzap v1.0.2 h1:EQwhggtszVfIdBeXxN9Xrmld71es34Ufs+ef8VMqZxc=
+github.com/gofiber/contrib/fiberzap v1.0.2/go.mod h1:jGO8BHU4gRI9U0JtM6zj2CIhYfgVmW5JxziN8NTgVwE=
+github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
+github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
-github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
-github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
-github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
-github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -132,340 +98,429 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8=
-github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
-github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
-github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
-github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
-github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI=
-github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
+github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
+github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
+github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
+github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
+github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
+github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
+github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
-github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
-github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
-github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
-github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
-github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
+github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
+github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
+github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
+github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
+github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
+github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
+github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
+github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5 h1:PJr+ZMXIecYc1Ey2zucXdR73SMBtgjPgwa31099IMv0=
-github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
-github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
-github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
-github.com/kisielk/errcheck v1.1.0 h1:ZqfnKyx9KGpRcW04j5nnPDgRgoXUeLh2YFBeFzphcA0=
-github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
-github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
+github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
+github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
+github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
+github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
-github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
-github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
-github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
-github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
-github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
-github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
+github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
-github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
+github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
+github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
-github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
+github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
-github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
-github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
-github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
-github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M=
-github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
-github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
-github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
-github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
-github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
-github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
-github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
-github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
-github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
-github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
-github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
+github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
+github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
+github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
+github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
+github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
+github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI=
+github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_golang v1.20.2 h1:5ctymQzZlyOON1666svgwn3s6IKWgfbjsejTMiXIyjg=
+github.com/prometheus/client_golang v1.20.2/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
+github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4=
+github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
-github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
+github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
+github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
+github.com/prometheus/common v0.57.0 h1:Ro/rKjwdq9mZn1K5QPctzh+MA4Lp0BuYk5ZZEVhoNcY=
+github.com/prometheus/common v0.57.0/go.mod h1:7uRPFSUTbfZWsJ7MHY56sqt7hLQu3bxXHDnNhl8E9qI=
+github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0=
+github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/tsdb v0.7.1 h1:YZcsG11NqnK4czYLrWd9mpEuAJIHVQLwdrleYfszMAA=
-github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af h1:gu+uRPtBe88sKxUCEXRoeCvVG90TJmwhiqRpvdhQFng=
-github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
-github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
-github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
-github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
+github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
+github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
+github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
-github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
-github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
-github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
-github.com/speps/go-hashids v2.0.0+incompatible/go.mod h1:P7hqPzMdnZOfyIk+xrlG1QaSMw+gCBdHKsBDnhpaZvc=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/speps/go-hashids/v2 v2.0.1 h1:ViWOEqWES/pdOSq+C1SLVa8/Tnsd52XC34RY7lt7m4g=
github.com/speps/go-hashids/v2 v2.0.1/go.mod h1:47LKunwvDZki/uRVD6NImtyk712yFzIs3UF3KlHohGw=
-github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
-github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
-github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
-github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
-github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
-github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
-github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
-github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
-github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
-github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
+github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
+github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
-github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
-github.com/uber/jaeger-client-go v2.25.0+incompatible h1:IxcNZ7WRY1Y3G4poYlx24szfsn/3LvK9QHCq9oQw8+U=
-github.com/uber/jaeger-client-go v2.25.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
-github.com/uber/jaeger-lib v2.4.0+incompatible h1:fY7QsGQWiCt8pajv4r7JEvmATdCVaWxXbjwyYwsNaLQ=
-github.com/uber/jaeger-lib v2.4.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
-github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
-github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
-github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
-github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
-github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
-github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow=
-github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
-github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
-github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.4.0 h1:8D/t3baH3OCNXoAtWFb23p9jh3kyJM+wdRkmkc5/1RE=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.4.0/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.5.0 h1:z3s9L1kcNBGpmTI//BTElJ6EAaq1lOH8tOxU2urgpdI=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.5.0/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.5.1 h1:jK9/w2o7+5qlQWwss7MqZfH5XGL9mJDfE/q+GXlPluM=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.5.1/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.5.2 h1:P+PNVwppAee0XzYP9jXPevCyMJO9ESxH+XdoA2IztJY=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.5.2/go.mod h1:p9EBmjqBXuImZ5NeoN3SXV8kZ1/6QYAeUu0e7oQkGQc=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.6.0-beta h1:ztJ35NxA0desQqH8ZRPqQSL/iPuvqqO2HV6XWjepKGA=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.6.0-beta/go.mod h1:U7lxAuqPifdXkUNcCXhtk9+6HFAyw5qy8LrF1DQDMuQ=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.6.1 h1:G782ZLXcCTI5Kby/s3YPur9S6cJxtLds1NVemWrgzi8=
-gitlab.snapp.ir/dispatching/snappids/v2 v2.6.1/go.mod h1:U7lxAuqPifdXkUNcCXhtk9+6HFAyw5qy8LrF1DQDMuQ=
-go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
-go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
-go.opentelemetry.io/otel v0.13.0 h1:2isEnyzjjJZq6r2EKMsFj4TxiQiexsM04AVhwbR/oBA=
-go.opentelemetry.io/otel v0.13.0/go.mod h1:dlSNewoRYikTkotEnxdmuBHgzT+k/idJSfDv/FxEnOY=
-go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
-go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tinylib/msgp v1.1.9 h1:SHf3yoO2sGA0veCJeCBYLHuttAVFHGm2RHgNodW7wQU=
+github.com/tinylib/msgp v1.1.9/go.mod h1:BCXGB54lDD8qUEPmiG0cQQUANC4IUQyB2ItS2UDlO/k=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8=
+github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM=
+github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
+go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
+go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
+go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
+go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw=
+go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0=
+go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
+go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
+go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc=
+go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8=
+go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
+go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
+go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo=
+go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok=
+go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
+go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
+go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4=
+go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/automaxprocs v1.3.0 h1:II28aZoGdaglS5vVNnspf28lnZpXScxtIozx1lAjdb0=
-go.uber.org/automaxprocs v1.3.0/go.mod h1:9CWT6lKIep8U41DDaPiH6eFscnTyjfTANNQNx6LrIcA=
-go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
-go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
-go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
-go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
-go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
-go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM=
-go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
+go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
+go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
-golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0 h1:sfUMP1Gu8qASkorDVjnMuvgJzwFbTZSeXFiGBYAVdl4=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
-golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
+golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
+golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
+golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
+golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
+golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
+golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
+golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
+golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f h1:kDxGY2VmgABOe55qheT/TFqUMtcTHnomIPS1iv3G4Ms=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo=
-gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM=
-gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc=
-gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
-gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b h1:Qh4dB5D/WpoUUp3lSod7qgoyEHbDGPUWjIbnqdqqe1k=
-gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw=
+google.golang.org/genproto/googleapis/api v0.0.0-20240823204242-4ba0660f739c h1:e0zB268kOca6FbuJkYUGxfwG4DKFZG/8DLyv9Zv66cE=
+google.golang.org/genproto/googleapis/api v0.0.0-20240823204242-4ba0660f739c/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g=
+google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed h1:3RgNmBoI9MZhsj3QxC+AP/qQhNwpCLOvYDYYsFrhFt0=
+google.golang.org/genproto/googleapis/api v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c h1:Kqjm4WpoWvwhMPcrAczoTyMySQmYa9Wy2iL6Con4zn8=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240823204242-4ba0660f739c/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
+google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
+google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
+google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
+google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c=
+google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -474,40 +529,30 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
-gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
-gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
-gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
-gopkg.in/resty.v1 v1.12.0 h1:CuXP0Pjfw9rOuY6EP+UvtNvt5DSqHpIxILZKT/quCZI=
-gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
-gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
-gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
-rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
diff --git a/internal/accounts/accounts.go b/internal/accounts/accounts.go
deleted file mode 100644
index cb65e0dc..00000000
--- a/internal/accounts/accounts.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package accounts
-
-import "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
-
-// Service is responsible for all things related to account handling.
-type Service struct {
- Handler db.ModelHandler
-}
diff --git a/internal/accounts/delete.go b/internal/accounts/delete.go
deleted file mode 100644
index dca61387..00000000
--- a/internal/accounts/delete.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package accounts
-
-import (
- "context"
-
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
-)
-
-// Delete removes a user by given username and password from database
-func (s Service) Delete(ctx context.Context, username string) *errors.Error {
- if err := s.Handler.Delete(ctx, "user", username); err != nil {
- return errors.CreateError(errors.DatabaseDeleteFailure, err.Error())
- }
-
- return nil
-}
diff --git a/internal/accounts/info.go b/internal/accounts/info.go
deleted file mode 100644
index 5cbdf3b1..00000000
--- a/internal/accounts/info.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package accounts
-
-import (
- "context"
-
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- "golang.org/x/crypto/bcrypt"
-)
-
-// Info returns a user based on given username and password.
-func (s Service) Info(ctx context.Context, username, password string) (*user.User, *errors.Error) {
- var u user.User
- if err := s.Handler.Get(ctx, "user", username, &u); err != nil {
- return nil, errors.CreateError(errors.DatabaseGetFailure, err.Error())
- }
-
- if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)); err != nil {
- return nil, errors.CreateError(errors.WrongUsernameOrPassword, "")
- }
-
- return &u, nil
-}
diff --git a/internal/accounts/rules.go b/internal/accounts/rules.go
deleted file mode 100644
index 2bfbffad..00000000
--- a/internal/accounts/rules.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package accounts
-
-import (
- "context"
- "time"
-
- "github.com/google/uuid"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
-)
-
-// CreateRule will add a new rule with given information to a user
-func (s Service) CreateRule(ctx context.Context, username, endpoint string, topicPattern topics.Type, accessType acl.AccessType) (*user.Rule, *errors.Error) {
- if err := validateRuleInfo(endpoint, topicPattern, accessType); err != nil {
- return nil, err
- }
-
- var u user.User
- if err := s.Handler.Get(ctx, "user", username, &u); err != nil {
- return nil, errors.CreateError(errors.DatabaseGetFailure, err.Error())
- }
-
- rule := &user.Rule{
- UUID: uuid.New(),
- Endpoint: endpoint,
- Topic: topicPattern,
- AccessType: accessType,
- }
-
- u.Rules = append(u.Rules, *rule)
-
- u.MetaData.DateModified = time.Now()
-
- if err := s.Handler.Update(ctx, u); err != nil {
- return nil, errors.CreateError(errors.DatabaseUpdateFailure, err.Error())
- }
-
- return rule, nil
-}
-
-// GetRule returns a user rule based on given username, password and rule UUID
-func (s Service) GetRule(ctx context.Context, username string, id uuid.UUID) (*user.Rule, *errors.Error) {
- var u user.User
- if err := s.Handler.Get(ctx, "user", username, &u); err != nil {
- return nil, errors.CreateError(errors.DatabaseGetFailure, err.Error())
- }
-
- for _, r := range u.Rules {
- if r.UUID == id {
- return &r, nil
- }
- }
-
- return nil, errors.CreateError(errors.RuleNotFound, "")
-}
-
-// UpdateRule updates an account's rule based on given username, UUID and information
-func (s Service) UpdateRule(ctx context.Context, username string, id uuid.UUID, endpoint string, topicPattern topics.Type, accessType acl.AccessType) *errors.Error {
- if err := validateRuleInfo(endpoint, topicPattern, accessType); err != nil {
- return err
- }
-
- var u user.User
- if err := s.Handler.Get(ctx, "user", username, &u); err != nil {
- return errors.CreateError(errors.DatabaseGetFailure, err.Error())
- }
-
- var ruleIndex *int
- for i, r := range u.Rules {
- if r.UUID == id {
- ruleIndex = &i
- }
- }
- if ruleIndex == nil {
- return errors.CreateError(errors.RuleNotFound, "")
- }
-
- u.Rules[*ruleIndex].Endpoint = endpoint
- u.Rules[*ruleIndex].Topic = topicPattern
- u.Rules[*ruleIndex].AccessType = accessType
-
- u.MetaData.DateModified = time.Now()
-
- if err := s.Handler.Update(ctx, u); err != nil {
- return errors.CreateError(errors.DatabaseUpdateFailure, err.Error())
- }
-
- return nil
-}
-
-// DeleteRule deletes an account's rule based on given username and UUID
-func (s Service) DeleteRule(ctx context.Context, username string, id uuid.UUID) *errors.Error {
- var u user.User
- if err := s.Handler.Get(ctx, "user", username, &u); err != nil {
- return errors.CreateError(errors.DatabaseGetFailure, err.Error())
- }
-
- var ruleIndex *int
- for i, r := range u.Rules {
- if r.UUID == id {
- ruleIndex = &i
- }
- }
- if ruleIndex == nil {
- return errors.CreateError(errors.RuleNotFound, "")
- }
-
- u.Rules = append(u.Rules[:*ruleIndex], u.Rules[*ruleIndex+1:]...)
-
- u.MetaData.DateModified = time.Now()
-
- if err := s.Handler.Update(ctx, u); err != nil {
- return errors.CreateError(errors.DatabaseUpdateFailure, err.Error())
- }
-
- return nil
-}
-
-// validateRuleInfo will validate a rule info
-func validateRuleInfo(endpoint string, topicPattern topics.Type, accessType acl.AccessType) *errors.Error {
- if endpoint == "" && topicPattern == "" && accessType == "" {
- return errors.CreateError(errors.InvalidRule, "all rule information is empty")
- }
-
- if accessType == "" {
- return errors.CreateError(errors.InvalidRule, "access type is necessary")
- }
-
- if (endpoint != "" && topicPattern != "") || (endpoint == "" && topicPattern == "") {
- return errors.CreateError(errors.InvalidRule, "either endpoint or topic pattern should be present")
- }
-
- return nil
-}
diff --git a/internal/accounts/rules_test.go b/internal/accounts/rules_test.go
deleted file mode 100644
index a9c4c72b..00000000
--- a/internal/accounts/rules_test.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package accounts
-
-import (
- "github.com/stretchr/testify/assert"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "testing"
-)
-
-func TestValidateRuleInfo(t *testing.T) {
- t.Run("test with with invalid rule info", func(t *testing.T) {
- endpoint := ""
- topicPattern := topics.Type("")
- accessType := acl.AccessType("")
-
- err := validateRuleInfo(endpoint, topicPattern, accessType)
- assert.NotNil(t, err)
- assert.Equal(t, errors.InvalidRule, err.Code)
-
- endpoint = "/notification"
- topicPattern = topics.DriverLocation
- accessType = ""
-
- err = validateRuleInfo(endpoint, topicPattern, accessType)
- assert.NotNil(t, err)
- assert.Equal(t, errors.InvalidRule, err.Code)
-
- endpoint = "/notification"
- topicPattern = topics.DriverLocation
- accessType = ""
-
- err = validateRuleInfo(endpoint, topicPattern, accessType)
- assert.NotNil(t, err)
- assert.Equal(t, errors.InvalidRule, err.Code)
-
- endpoint = "/notification"
- topicPattern = topics.CabEvent
- accessType = acl.Pub
-
- err = validateRuleInfo(endpoint, topicPattern, accessType)
- assert.NotNil(t, err)
- assert.Equal(t, errors.InvalidRule, err.Code)
-
- endpoint = ""
- topicPattern = topics.DriverLocation
- accessType = ""
-
- err = validateRuleInfo(endpoint, topicPattern, accessType)
- assert.NotNil(t, err)
- assert.Equal(t, errors.InvalidRule, err.Code)
-
- endpoint = ""
- topicPattern = ""
- accessType = acl.Sub
-
- err = validateRuleInfo(endpoint, topicPattern, accessType)
- assert.NotNil(t, err)
- assert.Equal(t, errors.InvalidRule, err.Code)
- })
-
- t.Run("test with with valid rule info", func(t *testing.T) {
- endpoint := "/notification"
- topicPattern := topics.Type("")
- accessType := acl.Pub
-
- err := validateRuleInfo(endpoint, topicPattern, accessType)
- assert.Nil(t, err)
-
- endpoint = ""
- topicPattern = topics.DriverLocation
- accessType = acl.Pub
-
- err = validateRuleInfo(endpoint, topicPattern, accessType)
- assert.Nil(t, err)
- })
-}
diff --git a/internal/accounts/signup.go b/internal/accounts/signup.go
deleted file mode 100644
index f6109dbd..00000000
--- a/internal/accounts/signup.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package accounts
-
-import (
- "context"
- "time"
-
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- "golang.org/x/crypto/bcrypt"
-)
-
-// SignUp creates a user with the given information in database
-func (s Service) SignUp(ctx context.Context, username, password string, userType user.Type) *errors.Error {
- hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
- if err != nil {
- return errors.CreateError(errors.PasswordHashGenerationFailure, err.Error())
- }
-
- u := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: username,
- Password: string(hash),
- Type: userType,
- }
- if err := s.Handler.Save(context.Background(), u); err != nil {
- return errors.CreateError(errors.DatabaseSaveFailure, err.Error())
- }
-
- return nil
-}
diff --git a/internal/accounts/update.go b/internal/accounts/update.go
deleted file mode 100644
index 99b82668..00000000
--- a/internal/accounts/update.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package accounts
-
-import (
- "context"
- "time"
-
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- "golang.org/x/crypto/bcrypt"
-)
-
-// Update updates given username with given new data in `newInfo` in database
-func (s Service) Update(
- ctx context.Context,
- username string,
- newPassword string,
- newType user.Type,
- newIPs []string,
- newSecret string,
- newTokenExpiration time.Duration) *errors.Error {
- var u user.User
- if err := s.Handler.Get(context.Background(), "user", username, &u); err != nil {
- return errors.CreateError(errors.DatabaseGetFailure, err.Error())
- }
-
- if newPassword != "" {
- hash, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
- if err != nil {
- return errors.CreateError(errors.PasswordHashGenerationFailure, err.Error())
- }
- u.Password = string(hash)
- }
-
- if newType != "" {
- u.Type = newType
- }
-
- if newSecret != "" {
- u.Secret = newSecret
- }
-
- if newIPs != nil && len(newIPs) != 0 {
- u.IPs = newIPs
- }
-
- if newTokenExpiration != 0 {
- u.TokenExpirationDuration = newTokenExpiration
- }
-
- u.MetaData.DateModified = time.Now()
-
- if err := s.Handler.Update(context.Background(), u); err != nil {
- return errors.CreateError(errors.DatabaseUpdateFailure, err.Error())
- }
-
- return nil
-}
diff --git a/internal/api/acl.go b/internal/api/acl.go
new file mode 100644
index 00000000..222bc177
--- /dev/null
+++ b/internal/api/acl.go
@@ -0,0 +1,221 @@
+package api
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+ "go.opentelemetry.io/otel/attribute"
+ "go.uber.org/zap"
+)
+
+type ACLResponse struct {
+ Result string `json:"result,omitempty"`
+}
+
+// AclRequest is the body payload structure of the ACL endpoint.
+type ACLRequest struct {
+ Access acl.AccessType `form:"access"`
+ Token string `form:"token"`
+ Username string `from:"username"`
+ Password string `form:"password"`
+ Topic string `form:"topic"`
+}
+
+// ACL is the handler responsible for ACL requests.
+// nolint: wrapcheck, funlen
+func (a API) ACLv1(c *fiber.Ctx) error {
+ _, span := a.Tracer.Start(c.Context(), "api.v1.acl")
+ defer span.End()
+
+ request := new(ACLRequest)
+ if err := c.BodyParser(request); err != nil {
+ a.Logger.
+ Warn("acl bad request",
+ zap.Error(err),
+ zap.String("access", request.Access.String()),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ )
+ authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err)
+
+ return c.Status(http.StatusBadRequest).SendString("bad request")
+ }
+
+ vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password)
+
+ topic := request.Topic
+ auth := a.Authenticator(vendor)
+
+ span.SetAttributes(
+ attribute.String("access", request.Access.String()),
+ attribute.String("topic", request.Topic),
+ attribute.String("token", request.Token),
+ attribute.String("username", request.Username),
+ attribute.String("password", request.Password),
+ attribute.String("authenticator", auth.GetCompany()),
+ )
+
+ ok, err := auth.ACL(request.Access, token, topic)
+ if err != nil || !ok {
+ if err != nil {
+ span.RecordError(err)
+ }
+
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+
+ var tnaErr authenticator.TopicNotAllowedError
+
+ if errors.As(err, &tnaErr) {
+ a.Logger.
+ Warn("acl request is not authorized",
+ zap.Error(tnaErr),
+ zap.String("access", request.Access.String()),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()))
+ } else if !errors.Is(err, jwt.ErrTokenExpired) {
+ a.Logger.
+ Error("acl request is not authorized",
+ zap.Error(err),
+ zap.String("access", request.Access.String()),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()))
+ }
+
+ return c.Status(http.StatusUnauthorized).SendString("request is not authorized")
+ }
+
+ a.Logger.
+ Info("acl ok",
+ zap.String("access", request.Access.String()),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()),
+ )
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+
+ return c.Status(http.StatusOK).SendString("ok")
+}
+
+// ACLv2Request is the body payload structure of the ACL endpoint.
+type ACLv2Request struct {
+ Token string `json:"token"`
+ Username string `json:"username"`
+ Password string `json:"password"`
+ Topic string `json:"topic"`
+ Action string `json:"action"`
+}
+
+// ACLv2 is the handler responsible for ACL requests coming from EMQv5.
+// https://www.emqx.io/docs/en/latest/access-control/authz/http.html
+// nolint: wrapcheck, funlen
+func (a API) ACLv2(c *fiber.Ctx) error {
+ _, span := a.Tracer.Start(c.Context(), "api.v2.acl")
+ defer span.End()
+
+ request := new(ACLv2Request)
+ if err := c.BodyParser(request); err != nil {
+ a.Logger.
+ Warn("acl bad request",
+ zap.Error(err),
+ zap.String("access", request.Action),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ )
+ authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err)
+
+ return c.Status(http.StatusOK).JSON(ACLResponse{
+ Result: "deny",
+ })
+ }
+
+ vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password)
+
+ topic := request.Topic
+ auth := a.Authenticator(vendor)
+
+ span.SetAttributes(
+ attribute.String("access", request.Action),
+ attribute.String("topic", request.Topic),
+ attribute.String("token", request.Token),
+ attribute.String("username", request.Username),
+ attribute.String("password", request.Password),
+ attribute.String("authenticator", auth.GetCompany()),
+ )
+
+ var access acl.AccessType
+
+ switch request.Action {
+ case "publish":
+ access = acl.Pub
+ case "subscribe":
+ access = acl.Sub
+ }
+
+ ok, err := auth.ACL(access, token, topic)
+ if err != nil || !ok {
+ if err != nil {
+ span.RecordError(err)
+ }
+
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+
+ var tnaErr authenticator.TopicNotAllowedError
+
+ if errors.As(err, &tnaErr) {
+ a.Logger.
+ Warn("acl request is not authorized",
+ zap.Error(tnaErr),
+ zap.String("access", request.Action),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()))
+ } else if !errors.Is(err, jwt.ErrTokenExpired) {
+ a.Logger.
+ Error("acl request is not authorized",
+ zap.Error(err),
+ zap.String("access", request.Action),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()))
+ }
+
+ return c.Status(http.StatusOK).JSON(ACLResponse{
+ Result: "deny",
+ })
+ }
+
+ a.Logger.
+ Info("acl ok",
+ zap.String("access", request.Action),
+ zap.String("topic", request.Topic),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()),
+ )
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+
+ return c.Status(http.StatusOK).JSON(ACLResponse{
+ Result: "allow",
+ })
+}
diff --git a/internal/api/api.go b/internal/api/api.go
new file mode 100644
index 00000000..0cce488e
--- /dev/null
+++ b/internal/api/api.go
@@ -0,0 +1,87 @@
+package api
+
+import (
+ "strings"
+
+ "github.com/ansrivas/fiberprometheus/v2"
+ "github.com/gofiber/contrib/fiberzap"
+ "github.com/gofiber/fiber/v2"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/zap"
+)
+
+const VendorTokenSeparator = ":"
+
+type API struct {
+ Authenticators map[string]authenticator.Authenticator
+ DefaultVendor string
+ Tracer trace.Tracer
+ Logger *zap.Logger
+}
+
+// MetricLogSkipper check if route is equal "metric" disable log.
+func MetricLogSkipper(ctx *fiber.Ctx) bool {
+ route := string(ctx.Request().URI().Path())
+
+ return route == "/metrics"
+}
+
+// ReSTServer will return fiber app.
+func (a API) ReSTServer() *fiber.App {
+ app := fiber.New()
+
+ //nolint: exhaustruct
+ app.Use(fiberzap.New(fiberzap.Config{
+ Next: MetricLogSkipper,
+ Logger: a.Logger.Named("fiber"),
+ }))
+
+ prometheus := fiberprometheus.NewWith("http", "dispatching", "soteria")
+ prometheus.RegisterAt(app, "/metrics")
+ app.Use(prometheus.Middleware)
+
+ app.Post("/auth", a.Authv1)
+ app.Post("/acl", a.ACLv1)
+
+ app.Post("/v2/auth", a.Authv2)
+ app.Post("/v2/acl", a.ACLv2)
+
+ return app
+}
+
+func (a API) Authenticator(vendor string) authenticator.Authenticator {
+ auth, ok := a.Authenticators[vendor]
+
+ if ok {
+ return auth
+ }
+
+ return a.Authenticators[a.DefaultVendor]
+}
+
+func ExtractVendorToken(rawToken, username, password string) (string, string) {
+ tokenString := rawToken
+
+ if len(tokenString) == 0 {
+ tokenString = username
+ }
+
+ if len(tokenString) == 0 {
+ tokenString = password
+ }
+
+ split := strings.Split(tokenString, VendorTokenSeparator)
+
+ var vendor, token string
+
+ if len(split) == 2 { //nolint:mnd
+ vendor = split[0]
+ token = split[1]
+ } else {
+ vendor = ""
+ token = split[0]
+ }
+
+ return vendor, token
+}
diff --git a/internal/api/api_test.go b/internal/api/api_test.go
new file mode 100644
index 00000000..f5d895c6
--- /dev/null
+++ b/internal/api/api_test.go
@@ -0,0 +1,205 @@
+package api_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/api"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/stretchr/testify/suite"
+ "go.opentelemetry.io/otel/trace/noop"
+ "go.uber.org/zap"
+)
+
+func getSampleToken(key string) (string, error) {
+ exp := time.Now().Add(time.Hour * 24 * 365 * 10)
+ sub := "DXKgaNQa7N5Y7bo"
+
+ // nolint: exhaustruct
+ claims := jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(exp),
+ Issuer: "Colony",
+ Subject: sub,
+ }
+ token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
+
+ tokenString, err := token.SignedString([]byte(key))
+ if err != nil {
+ return "", fmt.Errorf("cannot generate a signed string %w", err)
+ }
+
+ return tokenString, nil
+}
+
+// nolint: funlen
+func TestExtractVendorToken(t *testing.T) {
+ t.Parallel()
+
+ type fields struct {
+ Token string
+ Username string
+ Password string
+ }
+
+ tests := []struct {
+ name string
+ fields fields
+ vendor string
+ token string
+ }{
+ {
+ name: "token field as token",
+ fields: fields{
+ Token: "vendor:token",
+ Username: "vendor:username",
+ Password: "password",
+ },
+ vendor: "vendor",
+ token: "token",
+ },
+ {
+ name: "username as token without vendor",
+ fields: fields{
+ Token: "",
+ Username: "username",
+ Password: "password",
+ },
+ vendor: "",
+ token: "username",
+ },
+ {
+ name: "username as token with vendor",
+ fields: fields{
+ Token: "",
+ Username: "vendor:username",
+ Password: "",
+ },
+ vendor: "vendor",
+ token: "username",
+ },
+ {
+ name: "password as token",
+ fields: fields{
+ Token: "",
+ Username: "",
+ Password: "vendor:password",
+ },
+ vendor: "vendor",
+ token: "password",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ vendor, token := api.ExtractVendorToken(tt.fields.Token, tt.fields.Username, tt.fields.Password)
+ if vendor != tt.vendor || token != tt.token {
+ t.Errorf("ExtractVendorToken() vendor = %v, vendor %v", vendor, tt.vendor)
+ }
+ })
+ }
+}
+
+type APITestSuite struct {
+ suite.Suite
+
+ app *fiber.App
+ key string
+}
+
+func (suite *APITestSuite) SetupSuite() {
+ suite.key = "secret"
+
+ app := fiber.New()
+
+ a := api.API{
+ Authenticators: map[string]authenticator.Authenticator{
+ "snapp-admin": authenticator.AdminAuthenticator{
+ Key: []byte(suite.key),
+ Company: "snapp-admin",
+ JwtConfig: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "HS512",
+ },
+ Parser: jwt.NewParser(),
+ },
+ },
+ DefaultVendor: "snapp",
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Logger: zap.NewExample(),
+ }
+
+ app.Post("/v2/auth", a.Authv2)
+
+ suite.app = app
+}
+
+func (suite *APITestSuite) BadRequest() {
+ require := suite.Require()
+
+ body, err := json.Marshal(api.AuthRequest{
+ Token: "",
+ Username: "not-found:token",
+ Password: "",
+ })
+ require.NoError(err)
+
+ req := httptest.NewRequest(http.MethodPost, "/v2/auth", bytes.NewReader(body))
+
+ resp, err := suite.app.Test(req)
+ require.NoError(err)
+
+ defer resp.Body.Close()
+
+ require.Equal(http.StatusBadRequest, resp.StatusCode)
+}
+
+func (suite *APITestSuite) ValidToken() {
+ require := suite.Require()
+
+ token, err := getSampleToken(suite.key)
+ require.NoError(err)
+
+ body, err := json.Marshal(api.AuthRequest{
+ Token: "",
+ Username: "snapp-admin:" + token,
+ Password: "",
+ })
+ require.NoError(err)
+
+ req := httptest.NewRequest(http.MethodPost, "/v2/auth", bytes.NewReader(body))
+ req.Header.Add("Content-Type", "application/json")
+
+ resp, err := suite.app.Test(req)
+ require.NoError(err)
+
+ defer resp.Body.Close()
+
+ require.Equal(http.StatusOK, resp.StatusCode)
+
+ data, err := io.ReadAll(resp.Body)
+ require.NoError(err)
+
+ var authResp api.AuthResponse
+
+ require.NoError(json.Unmarshal(data, &authResp))
+
+ require.Equal("allow", authResp.Result)
+ require.True(authResp.IsSuperuser)
+}
+
+func TestAPITestSuite(t *testing.T) {
+ t.Parallel()
+
+ suite.Run(t, new(APITestSuite))
+}
diff --git a/internal/api/auth.go b/internal/api/auth.go
new file mode 100644
index 00000000..d85f88d6
--- /dev/null
+++ b/internal/api/auth.go
@@ -0,0 +1,159 @@
+package api
+
+import (
+ "errors"
+ "net/http"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "go.opentelemetry.io/otel/attribute"
+ "go.uber.org/zap"
+)
+
+// AuthRequest is the body payload structure of the auth endpoint.
+type AuthRequest struct {
+ Token string `form:"token" json:"token,omitempty"`
+ Username string `from:"username" json:"username,omitempty"`
+ Password string `form:"password" json:"password,omitempty"`
+}
+
+type AuthResponse struct {
+ Result string `json:"result,omitempty"`
+ IsSuperuser bool `json:"is_superuser,omitempty"`
+}
+
+// Auth is the handler responsible for authentication.
+// nolint: wrapcheck
+func (a API) Authv1(c *fiber.Ctx) error {
+ _, span := a.Tracer.Start(c.Context(), "api.v1.auth")
+ defer span.End()
+
+ request := new(AuthRequest)
+
+ if err := c.BodyParser(request); err != nil {
+ span.RecordError(err)
+
+ a.Logger.
+ Warn("bad request",
+ zap.Error(err),
+ )
+ authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err)
+
+ return c.Status(http.StatusBadRequest).SendString("bad request")
+ }
+
+ vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password)
+
+ auth := a.Authenticator(vendor)
+
+ span.SetAttributes(
+ attribute.String("token", request.Token),
+ attribute.String("username", request.Username),
+ attribute.String("password", request.Password),
+ attribute.String("authenticator", auth.GetCompany()),
+ )
+
+ err := auth.Auth(token)
+ if err != nil {
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+ span.RecordError(err)
+
+ if !errors.Is(err, jwt.ErrTokenExpired) {
+ a.Logger.
+ Debug("auth request is not authorized",
+ zap.Error(err),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()),
+ )
+ }
+
+ return c.Status(http.StatusUnauthorized).SendString("request is not authorized")
+ }
+
+ a.Logger.
+ Info("auth ok",
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()),
+ )
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+
+ return c.Status(http.StatusOK).SendString("ok")
+}
+
+// Auth is the handler responsible for authentication.
+// Endpoint will be used by EMQ version 5 which supports JSON on both request and response.
+// https://www.emqx.io/docs/en/latest/access-control/authn/http.html
+// nolint: wrapcheck, funlen
+func (a API) Authv2(c *fiber.Ctx) error {
+ _, span := a.Tracer.Start(c.Context(), "api.v2.auth")
+ defer span.End()
+
+ request := new(AuthRequest)
+
+ if err := c.BodyParser(request); err != nil {
+ span.RecordError(err)
+
+ a.Logger.
+ Warn("bad request",
+ zap.Error(err),
+ )
+ authenticator.IncrementWithErrorAuthCounter("unknown_company_before_parse_body", err)
+
+ return c.Status(http.StatusOK).JSON(AuthResponse{
+ Result: "deny",
+ IsSuperuser: false,
+ })
+ }
+
+ vendor, token := ExtractVendorToken(request.Token, request.Username, request.Password)
+
+ auth := a.Authenticator(vendor)
+
+ span.SetAttributes(
+ attribute.String("token", request.Token),
+ attribute.String("username", request.Username),
+ attribute.String("password", request.Password),
+ attribute.String("authenticator", auth.GetCompany()),
+ )
+
+ err := auth.Auth(token)
+ if err != nil {
+ span.RecordError(err)
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+
+ if !errors.Is(err, jwt.ErrTokenExpired) {
+ a.Logger.
+ Debug("auth request is not authorized",
+ zap.Error(err),
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()),
+ )
+ }
+
+ return c.Status(http.StatusOK).JSON(AuthResponse{
+ Result: "deny",
+ IsSuperuser: false,
+ })
+ }
+
+ a.Logger.
+ Info("auth ok",
+ zap.String("token", request.Token),
+ zap.String("username", request.Username),
+ zap.String("password", request.Password),
+ zap.String("authenticator", auth.GetCompany()),
+ )
+ authenticator.IncrementWithErrorAuthCounter(vendor, err)
+
+ return c.Status(http.StatusOK).JSON(AuthResponse{
+ Result: "allow",
+ IsSuperuser: auth.IsSuperuser(),
+ })
+}
diff --git a/internal/app/a.go b/internal/app/a.go
deleted file mode 100644
index 761ecb9a..00000000
--- a/internal/app/a.go
+++ /dev/null
@@ -1,55 +0,0 @@
-package app
-
-import (
- "io"
- "sync"
-
- "github.com/opentracing/opentracing-go"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/metrics"
-)
-
-type app struct {
- Metrics metrics.Metrics
- Authenticator *authenticator.Authenticator
- AccountsService *accounts.Service
- EMQStore emq.Store
- Tracer opentracing.Tracer
- TracerCloser io.Closer
-}
-
-var (
- singleton *app
- once sync.Once
-)
-
-func GetInstance() *app {
- once.Do(func() {
- singleton = &app{}
- })
-
- return singleton
-}
-
-func (a *app) SetEMQStore(store emq.Store) {
- a.EMQStore = store
-}
-
-func (a *app) SetAuthenticator(authenticator *authenticator.Authenticator) {
- a.Authenticator = authenticator
-}
-
-func (a *app) SetMetrics(m metrics.Metrics) {
- a.Metrics = m
-}
-
-func (a *app) SetAccountsService(service *accounts.Service) {
- a.AccountsService = service
-}
-
-func (a *app) SetTracer(tracer opentracing.Tracer, closer io.Closer) {
- a.Tracer = tracer
- a.TracerCloser = closer
-}
diff --git a/internal/authenticator/admin_authenticator.go b/internal/authenticator/admin_authenticator.go
new file mode 100644
index 00000000..8b606c26
--- /dev/null
+++ b/internal/authenticator/admin_authenticator.go
@@ -0,0 +1,65 @@
+package authenticator
+
+import (
+ "fmt"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+)
+
+// AdminAuthenticator is responsible for Acl/Auth/Token of the internal system userIDs,
+// these userIDs have admin access.
+type AdminAuthenticator struct {
+ Key any
+ Company string
+ JwtConfig config.JWT
+ Parser *jwt.Parser
+}
+
+// Auth check user authentication by checking the user's token
+// isSuperuser is a flag that authenticator set it true when credentials is related to a superuser.
+func (a AdminAuthenticator) Auth(tokenString string) error {
+ _, err := a.Parser.Parse(tokenString, func(
+ token *jwt.Token,
+ ) (interface{}, error) {
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok {
+ return nil, ErrInvalidClaims
+ }
+
+ if claims[a.JwtConfig.IssName] == nil {
+ return nil, ErrIssNotFound
+ }
+
+ return a.Key, nil
+ })
+ if err != nil {
+ return fmt.Errorf("token is invalid: %w", err)
+ }
+
+ return nil
+}
+
+// ACL check a system user access to a topic.
+// because we returns is-admin: true, this endpoint shouldn't
+// be called.
+func (a AdminAuthenticator) ACL(
+ _ acl.AccessType,
+ _ string,
+ _ string,
+) (bool, error) {
+ return true, nil
+}
+
+func (a AdminAuthenticator) ValidateAccessType(_ acl.AccessType) bool {
+ return true
+}
+
+func (a AdminAuthenticator) GetCompany() string {
+ return a.Company
+}
+
+func (a AdminAuthenticator) IsSuperuser() bool {
+ return true
+}
diff --git a/internal/authenticator/admin_authenticator_test.go b/internal/authenticator/admin_authenticator_test.go
new file mode 100644
index 00000000..8b1992e0
--- /dev/null
+++ b/internal/authenticator/admin_authenticator_test.go
@@ -0,0 +1,65 @@
+package authenticator_test
+
+import (
+ "testing"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/stretchr/testify/require"
+ "github.com/stretchr/testify/suite"
+)
+
+type AdminAuthenticatorTestSuite struct {
+ suite.Suite
+
+ AdminToken string
+
+ Authenticator authenticator.Authenticator
+}
+
+func TestAdminAuthenticator_suite(t *testing.T) {
+ t.Parallel()
+
+ st := new(AdminAuthenticatorTestSuite)
+
+ pkey0, err := getPublicKey("admin")
+ require.NoError(t, err)
+
+ st.Authenticator = authenticator.AdminAuthenticator{
+ Key: pkey0,
+ Company: "snapp-admin",
+ Parser: jwt.NewParser(),
+ JwtConfig: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "rsa256",
+ },
+ }
+
+ suite.Run(t, st)
+}
+
+func (suite *AdminAuthenticatorTestSuite) SetupSuite() {
+ require := suite.Require()
+
+ key, err := getPrivateKey("admin")
+ require.NoError(err)
+
+ adminToken, err := getSampleToken("admin", key)
+ require.NoError(err)
+
+ suite.AdminToken = adminToken
+}
+
+func (suite *AdminAuthenticatorTestSuite) TestAuth() {
+ require := suite.Require()
+
+ suite.Run("testing admin token auth", func() {
+ require.NoError(suite.Authenticator.Auth(suite.AdminToken))
+ })
+
+ suite.Run("testing invalid token auth", func() {
+ require.Error(suite.Authenticator.Auth(invalidToken))
+ })
+}
diff --git a/internal/authenticator/authenticator.go b/internal/authenticator/authenticator.go
index b7a5505a..21805220 100644
--- a/internal/authenticator/authenticator.go
+++ b/internal/authenticator/authenticator.go
@@ -1,343 +1,28 @@
package authenticator
import (
- "context"
- "crypto/rsa"
- "errors"
- "fmt"
- "time"
-
- "github.com/dgrijalva/jwt-go"
- "gitlab.snapp.ir/dispatching/snappids/v2"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
-)
-
-var (
- ErrInvalidSigningMethod = errors.New("token is not valid, signing method is not RSA")
- ErrIssNotFound = errors.New("could not found iss in token claims")
- ErrSubNotFound = errors.New("could not found sub in token claims")
- ErrInvalidClaims = errors.New("invalid claims")
- ErrInvalidIP = errors.New("IP is not valid")
- ErrInvalidAccessType = errors.New("requested access type is invalid")
- ErrDecodeHashID = errors.New("could not decode hash id")
- ErrInvalidSecret = errors.New("invalid secret")
- ErrIncorrectPassword = errors.New("username or password is worng")
+ "github.com/snapp-incubator/soteria/pkg/acl"
)
-type ErrTopicNotAllowed struct {
- Issuer user.Issuer
- Sub string
- AccessType acl.AccessType
- Topic topics.Topic
-}
-
-func (err ErrTopicNotAllowed) Error() string {
- return fmt.Sprintf("issuer %s with sub %s is not allowed to %s on topic %s (%s)",
- err.Issuer, err.Sub, err.AccessType, err.Topic, err.Topic.GetType(),
- )
-}
-
-type ErrPublicKeyNotFound struct {
- Issuer user.Issuer
-}
-
-func (err ErrPublicKeyNotFound) Error() string {
- return fmt.Sprintf("cannot find issuer %s public key", err.Issuer)
-}
-
-type ErrInvalidTopic struct {
- Topic topics.Topic
-}
-
-func (err ErrInvalidTopic) Error() string {
- return fmt.Sprintf("provided topic %s is not valid", err.Topic)
-}
-
-// Authenticator is responsible for Acl/Auth/Token of users.
-type Authenticator struct {
- PrivateKeys map[user.Issuer]*rsa.PrivateKey
- PublicKeys map[user.Issuer]*rsa.PublicKey
- AllowedAccessTypes []acl.AccessType
- ModelHandler db.ModelHandler
- EMQTopicManager *snappids.EMQTopicManager
- HashIDSManager *snappids.HashIDSManager
- CompareHashAndPassword func([]byte, []byte) error
- Company string
-}
-
-// Auth check user authentication by checking the user's token
-// isSuperuser is a flag that authenticator set it true when credentials is related to a superuser.
-func (a Authenticator) Auth(ctx context.Context, tokenString string) (isSuperuser bool, err error) {
- _, err = jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
- if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
- return nil, ErrInvalidSigningMethod
- }
-
- claims, ok := token.Claims.(jwt.MapClaims)
- if !ok {
- return nil, ErrInvalidClaims
- }
- if claims["iss"] == nil {
- return nil, ErrIssNotFound
- }
-
- _, isSuperuser = claims["is_superuser"]
-
- issuer := user.Issuer(fmt.Sprintf("%v", claims["iss"]))
-
- key := a.PublicKeys[issuer]
- if key == nil {
- return nil, ErrPublicKeyNotFound{Issuer: issuer}
- }
-
- return key, nil
- })
-
- if err != nil {
- return false, fmt.Errorf("token is invalid: %w", err)
- }
-
- return isSuperuser, nil
-}
-
-// ACL check a user access to a topic.
-func (a Authenticator) ACL(ctx context.Context, accessType acl.AccessType,
- tokenString string, topic topics.Topic) (bool, error) {
- if !a.validateAccessType(accessType) {
- return false, ErrInvalidAccessType
- }
-
- token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
- if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
- return nil, ErrInvalidSigningMethod
- }
-
- claims, ok := token.Claims.(jwt.MapClaims)
- if !ok {
- return nil, ErrInvalidClaims
- }
- if claims["iss"] == nil {
- return nil, ErrIssNotFound
- }
- if claims["sub"] == nil {
- return nil, ErrSubNotFound
- }
-
- issuer := user.Issuer(fmt.Sprintf("%v", claims["iss"]))
- key := a.PublicKeys[issuer]
- if key == nil {
- return nil, ErrPublicKeyNotFound{Issuer: issuer}
- }
-
- return key, nil
- })
- if err != nil {
- return false, fmt.Errorf("token is invalid %w", err)
- }
-
- claims, ok := token.Claims.(jwt.MapClaims)
- if !ok {
- return false, ErrInvalidClaims
- }
-
- if claims["iss"] == nil {
- return false, ErrIssNotFound
- }
-
- issuer := user.Issuer(fmt.Sprintf("%v", claims["iss"]))
-
- if claims["sub"] == nil {
- return false, ErrSubNotFound
- }
-
- sub := fmt.Sprintf("%v", claims["sub"])
-
- pk := primaryKey(issuer, sub)
-
- var u user.User
- if err := a.ModelHandler.Get(ctx, "user", pk, &u); err != nil {
- return false, fmt.Errorf("error getting user %s from db err: %w", pk, err)
- }
+type Authenticator interface {
+ // Auth check user authentication by checking the user's token.
+ // it retruns error in case of any issue with the user token.
+ Auth(tokenString string) error
- // validate passenger and driver topics.
- if issuer != user.ThirdParty {
- id, err := a.HashIDSManager.DecodeHashID(sub, issuerToAudience(issuer))
- if err != nil {
- return false, ErrDecodeHashID
- }
+ // ACL check a user access to a topic.
+ ACL(
+ accessType acl.AccessType,
+ tokenString string,
+ topic string,
+ ) (bool, error)
- ok := a.ValidateTopicBySender(topic, issuerToAudience(issuer), id)
- if !ok {
- return false, ErrInvalidTopic{Topic: topic}
- }
- }
+ // ValidateAccessType checks access type for specific topic
+ ValidateAccessType(accessType acl.AccessType) bool
- if ok := u.CheckTopicAllowance(topic.GetTypeWithCompany(a.Company), accessType); !ok {
- return false,
- ErrTopicNotAllowed{issuer, sub, accessType, topic}
- }
-
- return true, nil
-}
-
-// Token function issues JWT token by taking client credentials.
-func (a Authenticator) Token(ctx context.Context, _ acl.AccessType,
- username, secret string) (string, error) {
- var u user.User
- if err := a.ModelHandler.Get(ctx, "user", username, &u); err != nil {
- return "", fmt.Errorf("could not get user %s. %w", username, err)
- }
-
- if u.Secret != secret {
- return "", ErrInvalidSecret
- }
-
- // nolint: exhaustivestruct
- claims := jwt.StandardClaims{
- ExpiresAt: time.Now().Add(u.TokenExpirationDuration).Unix(),
- Issuer: string(user.ThirdParty),
- Subject: username,
- }
-
- token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
-
- tokenString, err := token.SignedString(a.PrivateKeys[user.ThirdParty])
- if err != nil {
- return "", fmt.Errorf("could not sign the token %w", err)
- }
-
- return tokenString, nil
-}
-
-func (a Authenticator) HeraldToken(
- username string,
- endpoints []acl.Endpoint,
- topics []acl.Topic,
- duration time.Duration,
-) (string, error) {
- // nolint: exhaustivestruct
- claims := acl.Claims{
- StandardClaims: jwt.StandardClaims{
- ExpiresAt: time.Now().Add(duration).Unix(),
- Issuer: string(user.ThirdParty),
- Subject: username,
- },
- Topics: topics,
- Endpoints: endpoints,
- }
-
- token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
-
- tokenString, err := token.SignedString(a.PrivateKeys[user.ThirdParty])
- if err != nil {
- return "", fmt.Errorf("could not sign the token. %w", err)
- }
-
- return tokenString, nil
-}
-
-func (a Authenticator) SuperuserToken(username string, duration time.Duration) (string, error) {
- // nolint: exhaustivestruct
- claims := acl.SuperuserClaims{
- StandardClaims: jwt.StandardClaims{
- ExpiresAt: time.Now().Add(duration).Unix(),
- Issuer: string(user.ThirdParty),
- Subject: username,
- },
- IsSuperuser: true,
- }
-
- token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
-
- tokenString, err := token.SignedString(a.PrivateKeys[user.ThirdParty])
- if err != nil {
- return "", fmt.Errorf("could not sign the token. %w", err)
- }
-
- return tokenString, nil
-}
-
-func (a Authenticator) EndPointBasicAuth(ctx context.Context, username, password, endpoint string) (bool, error) {
- var u user.User
- if err := a.ModelHandler.Get(ctx, "user", username, &u); err != nil {
- return false, fmt.Errorf("could not get user from db: %w", err)
- }
-
- if err := a.CompareHashAndPassword([]byte(u.Password), []byte(password)); err != nil {
- return false, ErrIncorrectPassword
- }
-
- return u.CheckEndpointAllowance(endpoint), nil
-}
-
-func (a Authenticator) EndpointIPAuth(ctx context.Context, username string, ip string, endpoint string) (bool, error) {
- var u user.User
- if err := a.ModelHandler.Get(ctx, "user", username, &u); err != nil {
- return false, fmt.Errorf("could not get user from db: %w", err)
- }
-
- if ok := acl.ValidateIP(ip, u.IPs, []string{}); !ok {
- return false, ErrInvalidIP
- }
-
- return u.CheckEndpointAllowance(endpoint), nil
-}
-
-func (a Authenticator) validateAccessType(accessType acl.AccessType) bool {
- for _, allowedAccessType := range a.AllowedAccessTypes {
- if allowedAccessType == accessType {
- return true
- }
- }
-
- return false
-}
-
-func primaryKey(issuer user.Issuer, sub string) string {
- if issuer == user.Passenger {
- return "passenger"
- } else if issuer == user.Driver {
- return "driver"
- }
-
- return sub
-}
-
-func (a Authenticator) ValidateTopicBySender(topic topics.Topic, audience snappids.Audience, id int) bool {
- var ch snappids.Topic
-
- switch topic.GetType() {
- case topics.CabEvent:
- ch, _ = a.EMQTopicManager.CreateCabEventTopic(id, audience)
- case topics.DriverLocation, topics.PassengerLocation:
- ch, _ = a.EMQTopicManager.CreateLocationTopic(id, audience)
- case topics.SuperappEvent:
- ch, _ = a.EMQTopicManager.CreateSuperAppEventTopic(id, audience)
- case topics.SharedLocation:
- ch, _ = a.EMQTopicManager.CreateSharedLocationTopic(id, audience)
- case topics.Chat:
- ch, _ = a.EMQTopicManager.CreateChatTopic(id, audience)
- case topics.DaghighSys:
- return true
- case topics.BoxEvent:
- return true
- }
-
- return string(ch) == string(topic)
-}
+ // GetCompany Return the Company Field of The Inherited Objects
+ GetCompany() string
-func issuerToAudience(issuer user.Issuer) snappids.Audience {
- switch issuer {
- case user.Passenger:
- return snappids.PassengerAudience
- case user.Driver:
- return snappids.DriverAudience
- case user.ThirdParty:
- return snappids.ThirdPartyAudience
- default:
- return -1
- }
+ // IsSuperuser changes the Auth response in case of successful authentication
+ // and shows user as superuser which disables the ACL.
+ IsSuperuser() bool
}
diff --git a/internal/authenticator/authenticator_test.go b/internal/authenticator/authenticator_test.go
index 5cc52db8..91faa21a 100644
--- a/internal/authenticator/authenticator_test.go
+++ b/internal/authenticator/authenticator_test.go
@@ -1,27 +1,19 @@
-package authenticator
+package authenticator_test
import (
- "context"
"crypto/rsa"
+ "errors"
"fmt"
- "io/ioutil"
- "testing"
+ "os"
"time"
- "github.com/dgrijalva/jwt-go"
- "github.com/google/uuid"
- "github.com/stretchr/testify/assert"
- snappids "gitlab.snapp.ir/dispatching/snappids/v2"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/memoize"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- "golang.org/x/crypto/bcrypt"
+ "github.com/golang-jwt/jwt/v5"
)
const (
- invalidToken = "ey1JhbGciOiJSUzI1NiIsInR5cCI56kpXVCJ9.eyJzdWIiOiJCRzdScDFkcnpWRE5RcjYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaXNzIjowLCJpYXQiOjE1MTYyMzkwMjJ9.1cYXFEhcewOYFjGJYhB8dsaFO9uKEXwlM8954rkt4Tsu0lWMITbRf_hHh1l9QD4MFqD-0LwRPUYaiaemy0OClMu00G2sujLCWaquYDEP37iIt8RoOQAh8Jb5vT8LX5C3PEKvbW_i98u8HHJoFUR9CXJmzrKi48sAcOYvXVYamN0S9KoY38H-Ze37Mdu3o6B58i73krk7QHecsc2_PkCJisvUVAzb0tiInIalBc8-zI3QZSxwNLr_hjlBg1sUxTUvH5SCcRR7hxI8TxJzkOHqAHWDRO84NC_DSAoO2p04vrHpqglN9XPJ8RC2YWpfefvD2ttH554RJWu_0RlR2kAYvQ"
+ // nolint: gosec, lll
+ invalidToken = "ey1JhbGciOiJSUzI1NiIsInR5cCI56kpXVCJ9.eyJzdWIiOiJCRzdScDFkcnpWRE5RcjYiLCJuYW1lIjoiSm9obiBEb2UiLCJhZG1pbiI6dHJ1ZSwiaXNzIjowLCJpYXQiOjE1MTYyMzkwMjJ9.1cYXFEhcewOYFjGJYhB8dsaFO9uKEXwlM8954rkt4Tsu0lWMITbRf_hHh1l9QD4MFqD-0LwRPUYaiaemy0OClMu00G2sujLCWaquYDEP37iIt8RoOQAh8Jb5vT8LX5C3PEKvbW_i98u8HHJoFUR9CXJmzrKi48sAcOYvXVYamN0S9KoY38H-Ze37Mdu3o6B58i73krk7QHecsc2_PkCJisvUVAzb0tiInIalBc8-zI3QZSxwNLr_hjlBg1sUxTUvH5SCcRR7hxI8TxJzkOHqAHWDRO84NC_DSAoO2p04vrHpqglN9XPJ8RC2YWpfefvD2ttH554RJWu_0RlR2kAYvQ"
+
validPassengerCabEventTopic = "passenger-event-152384980615c2bd16143cff29038b67"
invalidPassengerCabEventTopic = "passenger-event-152384980615c2bd16156cff29038b67"
@@ -31,11 +23,11 @@ const (
validDriverLocationTopic = "snapp/driver/DXKgaNQa7N5Y7bo/location"
invalidDriverLocationTopic = "snapp/driver/DXKgaNQa9Q5Y7bo/location"
- validPassengerSuperappEventTopic = "snapp/passenger/0956923be632d673560af9adadd2f78a/superapp"
- invalidPassengerSuperappEventTopic = "snapp/passenger/0959623be632d673560af9adadd2f78a/superapp"
+ validPassengerSuperappEventTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/superapp"
+ invalidPassengerSuperappEventTopic = "snapp/passenger/DXKgaNQa9Q5Y7bo/superapp"
- validDriverSuperappEventTopic = "snapp/driver/0956923be632d673560af9adadd2f78a/superapp"
- invalidDriverSuperappEventTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/superapp"
+ validDriverSuperappEventTopic = "snapp/driver/DXKgaNQa7N5Y7bo/superapp"
+ invalidDriverSuperappEventTopic = "snapp/driver/DXKgaNQa9Q5Y7bo/superapp"
validDriverSharedTopic = "snapp/driver/DXKgaNQa7N5Y7bo/passenger-location"
validPassengerSharedTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/driver-location"
@@ -46,724 +38,94 @@ const (
validPassengerChatTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/chat"
invalidDriverChatTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/chat"
invalidPassengerChatTopic = "snapp/passenger/0596923be632d673560af9adadd2f78a/chat"
-)
-
-func TestAuthenticator_Auth(t *testing.T) {
- driverToken, err := getSampleToken(user.Driver, false)
- if err != nil {
- t.Fatal(err)
- }
- passengerToken, err := getSampleToken(user.Passenger, false)
- if err != nil {
- t.Fatal(err)
- }
- thirdPartyToken, err := getSampleToken(user.ThirdParty, false)
- if err != nil {
- t.Fatal(err)
- }
- superuserToken, err := getSampleToken(user.ThirdParty, true)
- if err != nil {
- t.Fatal(err)
- }
- pkey0, err := getPublicKey(user.Driver)
- if err != nil {
- t.Fatal(err)
- }
- pkey1, err := getPublicKey(user.Passenger)
- if err != nil {
- t.Fatal(err)
- }
- pkey100, err := getPublicKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- key100, err := getPrivateKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- passwordChecker := memoize.MemoizedCompareHashAndPassword()
- authenticator := Authenticator{
- PrivateKeys: map[user.Issuer]*rsa.PrivateKey{
- user.ThirdParty: key100,
- },
- PublicKeys: map[user.Issuer]*rsa.PublicKey{
- user.Driver: pkey0,
- user.Passenger: pkey1,
- user.ThirdParty: pkey100,
- },
- ModelHandler: MockModelHandler{},
- CompareHashAndPassword: passwordChecker,
- }
- t.Run("testing driver token auth", func(t *testing.T) {
- ok, err := authenticator.Auth(context.Background(), driverToken)
- assert.NoError(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing passenger token auth", func(t *testing.T) {
- ok, err := authenticator.Auth(context.Background(), passengerToken)
- assert.NoError(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing third party token auth", func(t *testing.T) {
- ok, err := authenticator.Auth(context.Background(), thirdPartyToken)
- assert.NoError(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing superuser token auth", func(t *testing.T) {
- ok, err := authenticator.Auth(context.Background(), superuserToken)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing invalid token auth", func(t *testing.T) {
- ok, err := authenticator.Auth(context.Background(), invalidToken)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-}
-
-func TestAuthenticator_Token(t *testing.T) {
- key, err := getPrivateKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- pk, err := getPublicKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- passwordChecker := memoize.MemoizedCompareHashAndPassword()
-
- authenticator := Authenticator{
- PrivateKeys: map[user.Issuer]*rsa.PrivateKey{
- user.ThirdParty: key,
- },
- ModelHandler: MockModelHandler{},
- CompareHashAndPassword: passwordChecker,
- }
- t.Run("testing getting token with valid inputs", func(t *testing.T) {
- tokenString, err := authenticator.Token(context.Background(), acl.ClientCredentials, "snappbox", "KJIikjIKbIYVGj)YihYUGIB&")
- token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
- return pk, nil
- })
- assert.NoError(t, err)
- claims := token.Claims.(jwt.MapClaims)
- assert.Equal(t, "snappbox", claims["sub"].(string))
- assert.Equal(t, "100", claims["iss"].(string))
-
- })
- t.Run("testing getting token with valid inputs", func(t *testing.T) {
- tokenString, err := authenticator.Token(context.Background(), acl.ClientCredentials, "snappbox", "invalid secret")
- token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
- return pk, nil
- })
- assert.Error(t, err)
- assert.Nil(t, token)
- })
-}
-func TestAuthenticator_HeraldToken(t *testing.T) {
- key, err := getPrivateKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- pk, err := getPublicKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
-
- authenticator := Authenticator{
- PrivateKeys: map[user.Issuer]*rsa.PrivateKey{
- user.ThirdParty: key,
- },
- }
-
- t.Run("testing issuing valid herald token", func(t *testing.T) {
- allowedTopics := []acl.Topic{
- {
- Type: topics.BoxEvent,
- },
- }
-
- allowedEndpoints := []acl.Endpoint{
- {
- Name: "event",
- },
- }
-
- tokenString, err := authenticator.HeraldToken(
- "snappbox", allowedEndpoints, allowedTopics, time.Hour*24)
- assert.NoError(t, err)
- token, err := jwt.ParseWithClaims(tokenString, &acl.Claims{}, func(token *jwt.Token) (interface{}, error) {
- return pk, nil
- })
- assert.NoError(t, err)
- actual := token.Claims.(*acl.Claims)
-
- expected := acl.Claims{
- StandardClaims: jwt.StandardClaims{
- Issuer: "100",
- Subject: "snappbox",
- },
- Topics: []acl.Topic{
- {
- Type: topics.BoxEvent,
- },
- },
- Endpoints: []acl.Endpoint{
- {
- Name: "event",
- },
- },
- }
- assert.Equal(t, expected.StandardClaims.Issuer, actual.StandardClaims.Issuer)
- assert.Equal(t, expected.StandardClaims.Subject, actual.StandardClaims.Subject)
- assert.EqualValues(t, expected.Endpoints, actual.Endpoints)
- assert.EqualValues(t, expected.Topics, actual.Topics)
- })
-}
-
-func TestAuthenticator_SuperuserToken(t *testing.T) {
- key, err := getPrivateKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- pk, err := getPublicKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- authenticator := Authenticator{
- PrivateKeys: map[user.Issuer]*rsa.PrivateKey{
- user.ThirdParty: key,
- },
- }
-
- tokenString, err := authenticator.SuperuserToken("herald", time.Hour*24)
- assert.NoError(t, err)
-
- token, err := jwt.ParseWithClaims(tokenString, &acl.SuperuserClaims{}, func(token *jwt.Token) (interface{}, error) {
- return pk, nil
- })
- assert.NoError(t, err)
-
- actual := token.Claims.(*acl.SuperuserClaims)
- expected := acl.SuperuserClaims{
- StandardClaims: jwt.StandardClaims{
- Issuer: "100",
- Subject: "herald",
- },
- IsSuperuser: true,
- }
-
- assert.Equal(t, expected.StandardClaims.Issuer, actual.StandardClaims.Issuer)
- assert.Equal(t, expected.StandardClaims.Subject, actual.StandardClaims.Subject)
- assert.Equal(t, expected.IsSuperuser, actual.IsSuperuser)
-}
-
-func TestAuthenticator_Acl(t *testing.T) {
- pkey0, err := getPublicKey(user.Driver)
- if err != nil {
- t.Fatal(err)
- }
- pkey1, err := getPublicKey(user.Passenger)
- if err != nil {
- t.Fatal(err)
- }
- pkey100, err := getPublicKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- key100, err := getPrivateKey(user.ThirdParty)
- if err != nil {
- t.Fatal(err)
- }
- passengerToken, err := getSampleToken(user.Passenger, false)
- if err != nil {
- t.Fatal(err)
- }
- driverToken, err := getSampleToken(user.Driver, false)
- if err != nil {
- t.Fatal(err)
- }
- thirdPartyToken, err := getSampleToken(user.ThirdParty, false)
- if err != nil {
- t.Fatal(err)
- }
-
- hid := &snappids.HashIDSManager{
- Salts: map[snappids.Audience]string{
- snappids.PassengerAudience: "secret",
- snappids.DriverAudience: "secret",
- snappids.ThirdPartyAudience: "secret",
- },
- Lengths: map[snappids.Audience]int{
- snappids.PassengerAudience: 15,
- snappids.DriverAudience: 15,
- snappids.ThirdPartyAudience: 15,
- },
- }
-
- passwordChecker := memoize.MemoizedCompareHashAndPassword()
- authenticator := Authenticator{
- PrivateKeys: map[user.Issuer]*rsa.PrivateKey{
- user.ThirdParty: key100,
- },
- PublicKeys: map[user.Issuer]*rsa.PublicKey{
- user.Driver: pkey0,
- user.Passenger: pkey1,
- user.ThirdParty: pkey100,
- },
- AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub},
- ModelHandler: MockModelHandler{},
- EMQTopicManager: snappids.NewEMQManager(hid),
- HashIDSManager: hid,
- CompareHashAndPassword: passwordChecker,
- }
- t.Run("testing acl with invalid access type", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.PubSub, passengerToken, "test")
- assert.Error(t, err)
- assert.False(t, ok)
- assert.Equal(t, ErrInvalidAccessType.Error(), err.Error())
- })
- t.Run("testing acl with invalid token", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Pub, invalidToken, validDriverCabEventTopic)
- assert.False(t, ok)
- assert.Error(t, err)
- assert.Equal(t, "token is invalid illegal base64 data at input byte 37", err.Error())
- })
- t.Run("testing acl with valid inputs", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerCabEventTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
- t.Run("testing acl with invalid topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerCabEventTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
- t.Run("testing acl with invalid access type", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Pub, passengerToken, validPassengerCabEventTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing acl with third party token", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, thirdPartyToken, validDriverLocationTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing driver publish on its location topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Pub, driverToken, validDriverLocationTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing driver publish on invalid location topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Pub, driverToken, invalidDriverLocationTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing driver subscribe on invalid cab event topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverCabEventTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing passenger subscribe on valid superapp event topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerSuperappEventTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing passenger subscribe on invalid superapp event topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerSuperappEventTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing driver subscribe on valid superapp event topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, validDriverSuperappEventTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing driver subscribe on invalid superapp event topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverSuperappEventTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing driver subscribe on valid shared location topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, validDriverSharedTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing passenger subscribe on valid shared location topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerSharedTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing driver subscribe on invalid shared location topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverSharedTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing passenger subscribe on invalid shared location topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerSharedTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing driver subscribe on valid chat topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, validDriverChatTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing passenger subscribe on valid chat topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, validPassengerChatTopic)
- assert.NoError(t, err)
- assert.True(t, ok)
- })
-
- t.Run("testing driver subscribe on invalid chat topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, driverToken, invalidDriverChatTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-
- t.Run("testing passenger subscribe on invalid chat topic", func(t *testing.T) {
- ok, err := authenticator.ACL(context.Background(), acl.Sub, passengerToken, invalidPassengerChatTopic)
- assert.Error(t, err)
- assert.False(t, ok)
- })
-}
-
-func TestAuthenticator_ValidateTopicBySender(t *testing.T) {
- hid := &snappids.HashIDSManager{
- Salts: map[snappids.Audience]string{
- snappids.DriverAudience: "secret",
- },
- Lengths: map[snappids.Audience]int{
- snappids.DriverAudience: 15,
- },
- }
-
- passwordChecker := memoize.MemoizedCompareHashAndPassword()
- authenticator := Authenticator{
- AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub},
- ModelHandler: MockModelHandler{},
- EMQTopicManager: snappids.NewEMQManager(hid),
- HashIDSManager: hid,
- CompareHashAndPassword: passwordChecker,
- }
-
- t.Run("testing valid driver cab event", func(t *testing.T) {
- ok := authenticator.ValidateTopicBySender(validDriverCabEventTopic, snappids.DriverAudience, 123)
- assert.True(t, ok)
- })
-
-}
-
-func TestAuthenticator_validateAccessType(t *testing.T) {
- type fields struct {
- AllowedAccessTypes []acl.AccessType
- }
- type args struct {
- accessType acl.AccessType
- }
- tests := []struct {
- name string
- fields fields
- args args
- want bool
- }{
- {
- name: "#1 testing with no allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{}},
- args: args{accessType: acl.Sub},
- want: false,
- },
- {
- name: "#2 testing with no allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{}},
- args: args{accessType: acl.Pub},
- want: false,
- },
- {
- name: "#3 testing with no allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{}},
- args: args{accessType: acl.PubSub},
- want: false,
- },
- {
- name: "#4 testing with one allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}},
- args: args{accessType: acl.Pub},
- want: true,
- },
- {
- name: "#5 testing with one allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}},
- args: args{accessType: acl.Sub},
- want: false,
- },
- {
- name: "#6 testing with two allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}},
- args: args{accessType: acl.Sub},
- want: true,
- },
- {
- name: "#7 testing with two allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}},
- args: args{accessType: acl.Pub},
- want: true,
- },
- {
- name: "#8 testing with two allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}},
- args: args{accessType: acl.PubSub},
- want: false,
- },
- {
- name: "#9 testing with three allowed access type",
- fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub}},
- args: args{accessType: acl.PubSub},
- want: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- a := Authenticator{
- AllowedAccessTypes: tt.fields.AllowedAccessTypes,
- }
- if got := a.validateAccessType(tt.args.accessType); got != tt.want {
- t.Errorf("validateAccessType() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-type MockModelHandler struct{}
-
-func (rmh MockModelHandler) Save(ctx context.Context, model db.Model) error {
- return nil
-}
-
-func (rmh MockModelHandler) Delete(ctx context.Context, modelName, pk string) error {
- return nil
-}
-
-func (rmh MockModelHandler) Get(ctx context.Context, modelName, pk string, v db.Model) error {
- switch pk {
- case "passenger":
- *v.(*user.User) = user.User{
- MetaData: db.MetaData{},
- Username: string(user.Passenger),
- Type: user.EMQUser,
- Rules: []user.Rule{
- {
- UUID: uuid.New(),
- Topic: topics.CabEvent,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.SuperappEvent,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.PassengerLocation,
- AccessType: acl.Pub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.SharedLocation,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.Chat,
- AccessType: acl.Sub,
- },
- },
- }
- case "driver":
- *v.(*user.User) = user.User{
- MetaData: db.MetaData{},
- Username: string(user.Driver),
- Type: user.EMQUser,
- Rules: []user.Rule{
- {
- UUID: uuid.Nil,
- Endpoint: "",
- Topic: topics.DriverLocation,
- AccessType: acl.Pub,
- },
- {
- UUID: uuid.Nil,
- Endpoint: "",
- Topic: topics.CabEvent,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.SuperappEvent,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.PassengerLocation,
- AccessType: acl.Pub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.SharedLocation,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.Chat,
- AccessType: acl.Sub,
- },
- },
- }
- case "snappbox":
- *v.(*user.User) = user.User{
- MetaData: db.MetaData{},
- Username: "snapp-box",
- Password: getSamplePassword(),
- Type: user.HeraldUser,
- Secret: "KJIikjIKbIYVGj)YihYUGIB&",
- TokenExpirationDuration: time.Hour * 72,
- Rules: []user.Rule{
- {
- UUID: uuid.New(),
- Topic: topics.BoxEvent,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Endpoint: "/notification",
- AccessType: acl.Pub,
- },
- },
- }
- case "colony-subscriber":
- *v.(*user.User) = user.User{
- MetaData: db.MetaData{},
- Username: "colony-subscriber",
- Password: "password",
- Type: user.EMQUser,
- Secret: "secret",
- TokenExpirationDuration: 0,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.DriverLocation,
- AccessType: acl.Sub,
- },
- },
- }
- }
- return nil
-}
+ validDriverCallEntryTopic = "shared/snapp/driver/DXKgaNQa7N5Y7bo/call/send"
+ validPassengerCallEntryTopic = "shared/snapp/passenger/DXKgaNQa7N5Y7bo/call/send"
+ validDriverNodeCallEntryTopic = "snapp/driver/DXKgaNQa7N5Y7bo/call/heliograph-0/send"
+ validPassengerNodeCallEntryTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/call/heliograph-0/send"
+ invalidDriverCallEntryTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/call/send"
+ invalidPassengerCallEntryTopic = "snapp/passenger/0596923be632d673560af9adadd2f78a/call/send"
+ validDriverCallOutgoingTopic = "snapp/driver/DXKgaNQa7N5Y7bo/call/receive"
+ validPassengerCallOutgoingTopic = "snapp/passenger/DXKgaNQa7N5Y7bo/call/receive"
+ invalidDriverCallOutgoingTopic = "snapp/driver/0596923be632d673560af9adadd2f78a/call/receive"
+ invalidPassengerCallOutgoingTopic = "snapp/passenger/0596923be632d673560af9adadd2f78a/call/receive"
+)
-func (rmh MockModelHandler) Update(ctx context.Context, model db.Model) error {
- return nil
-}
+var (
+ ErrPrivateKeyNotFound = errors.New("invalid user, private key not found")
+ ErrPublicKeyNotFound = errors.New("invalid user, public key not found")
+)
-func getPublicKey(u user.Issuer) (*rsa.PublicKey, error) {
+func getPublicKey(u string) (*rsa.PublicKey, error) {
var fileName string
+
switch u {
- case user.Passenger:
- fileName = "../../test/1.pem"
- case user.Driver:
- fileName = "../../test/0.pem"
- case user.ThirdParty:
- fileName = "../../test/100.pem"
+ case "1":
+ fileName = "../../test/snapp-1.pem"
+ case "0":
+ fileName = "../../test/snapp-0.pem"
+ case "admin":
+ fileName = "../../test/snapp-admin.pem"
default:
- return nil, fmt.Errorf("invalid user, public key not found")
+ return nil, ErrPublicKeyNotFound
}
- pem, err := ioutil.ReadFile(fileName)
+
+ pem, err := os.ReadFile(fileName)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("reading public key failed %w", err)
}
+
publicKey, err := jwt.ParseRSAPublicKeyFromPEM(pem)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("paring public key failed %w", err)
}
+
return publicKey, nil
}
-func getPrivateKey(u user.Issuer) (*rsa.PrivateKey, error) {
+func getPrivateKey(u string) (*rsa.PrivateKey, error) {
var fileName string
+
switch u {
- case user.Driver:
- fileName = "../../test/0.private.pem"
- case user.Passenger:
- fileName = "../../test/1.private.pem"
- case user.ThirdParty:
- fileName = "../../test/100.private.pem"
+ case "0":
+ fileName = "../../test/snapp-0.private.pem"
+ case "1":
+ fileName = "../../test/snapp-1.private.pem"
+ case "admin":
+ fileName = "../../test/snapp-admin.private.pem"
default:
- return nil, fmt.Errorf("invalid user, private key not found")
+ return nil, ErrPrivateKeyNotFound
}
- pem, err := ioutil.ReadFile(fileName)
+
+ pem, err := os.ReadFile(fileName)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("reading private key failed %w", err)
}
+
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(pem)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("paring private key failed %w", err)
}
+
return privateKey, nil
}
-func getSampleToken(issuer user.Issuer, isSuperuser bool) (string, error) {
- key, err := getPrivateKey(issuer)
- if err != nil {
- panic(err)
- }
-
- exp := time.Now().Add(time.Hour * 24 * 365 * 10).Unix()
+func getSampleToken(issuer string, key *rsa.PrivateKey) (string, error) {
+ exp := time.Now().Add(time.Hour * 24 * 365 * 10)
sub := "DXKgaNQa7N5Y7bo"
- if issuer == user.ThirdParty {
- sub = "colony-subscriber"
- }
- var claims jwt.Claims
- if isSuperuser {
- claims = jwt.MapClaims{
- "exp": exp,
- "iss": string(issuer),
- "sub": sub,
- "is_superuser": true,
- }
- } else {
- claims = jwt.StandardClaims{
- ExpiresAt: exp,
- Issuer: string(issuer),
- Subject: sub,
- }
+ // nolint: exhaustruct
+ claims := jwt.RegisteredClaims{
+ ExpiresAt: jwt.NewNumericDate(exp),
+ Issuer: issuer,
+ Subject: sub,
}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
+
tokenString, err := token.SignedString(key)
if err != nil {
- panic(err)
+ return "", fmt.Errorf("cannot generate a signed string %w", err)
}
- return tokenString, nil
-}
-func getSamplePassword() string {
- hash, _ := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost)
- return string(hash)
+ return tokenString, nil
}
diff --git a/internal/authenticator/auto_authenticator.go b/internal/authenticator/auto_authenticator.go
new file mode 100644
index 00000000..41a668c7
--- /dev/null
+++ b/internal/authenticator/auto_authenticator.go
@@ -0,0 +1,184 @@
+package authenticator
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "strconv"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+ "github.com/snapp-incubator/soteria/pkg/validator"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/propagation"
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/zap"
+)
+
+// AutoAuthenticator is responsible for Acl/Auth/Token of userIDs.
+type AutoAuthenticator struct {
+ AllowedAccessTypes []acl.AccessType
+ TopicManager *topics.Manager
+ Company string
+ JWTConfig config.JWT
+ Validator validator.Client
+ Parser *jwt.Parser
+ Tracer trace.Tracer
+ Logger *zap.Logger
+ blackList autoBlackListChecker
+}
+
+// Auth check user authentication by checking the user's token
+// isSuperuser is a flag that authenticator set it true when credentials is related to a superuser.
+func (a AutoAuthenticator) Auth(tokenString string) error {
+ ctx, span := a.Tracer.Start(context.Background(), "auto-authenticator.auth")
+ span.End()
+
+ headers := http.Header{
+ validator.ServiceNameHeader: []string{"soteria"},
+ "user-agent": []string{},
+ "X-APP-Version-Code": []string{""},
+ "X-APP-Version": []string{""},
+ "X-APP-Name": []string{"soteria"},
+ "locale": []string{"en-US"},
+ }
+
+ otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(headers))
+
+ payload, err := a.Validator.Validate(ctx, headers, "bearer "+tokenString)
+ if err != nil {
+ return fmt.Errorf("token is invalid: %w", err)
+ }
+
+ if a.blackList.isBlackListByUserID(payload.UserID, payload.Iss) {
+ a.Logger.Warn("blacklisted user is requesting!",
+ zap.Int("iat", payload.IAT),
+ zap.String("aud", payload.Aud),
+ zap.Int("iss", payload.Iss),
+ zap.String("sub", payload.Sub),
+ zap.Int("user_id", payload.UserID),
+ zap.String("email", payload.Email),
+ zap.Int("exp", payload.Exp),
+ zap.String("locale", payload.Locale),
+ zap.String("sid", payload.Sid),
+ )
+ }
+
+ return nil
+}
+
+// ACL check a user access to a topic.
+// nolint: funlen, cyclop, dupl
+func (a AutoAuthenticator) ACL(
+ accessType acl.AccessType,
+ tokenString string,
+ topic string,
+) (bool, error) {
+ if !a.ValidateAccessType(accessType) {
+ return false, ErrInvalidAccessType
+ }
+
+ var claims jwt.MapClaims
+
+ if _, _, err := a.Parser.ParseUnverified(tokenString, &claims); err != nil {
+ return false, ErrInvalidClaims
+ }
+
+ if claims[a.JWTConfig.IssName] == nil {
+ return false, ErrIssNotFound
+ }
+
+ issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName])
+
+ if claims[a.JWTConfig.SubName] == nil {
+ return false, ErrSubNotFound
+ }
+
+ issuerInt, _ := strconv.Atoi(issuer)
+ sub, _ := claims[a.JWTConfig.SubName].(string)
+
+ if a.blackList.isBlackListByUserHashedID(sub, issuerInt) {
+ a.Logger.Warn("blacklisted user is requesting!", zap.Any("claims", claims))
+ }
+
+ topicTemplate := a.TopicManager.ParseTopic(topic, issuer, sub, map[string]any(claims))
+ if topicTemplate == nil {
+ return false, InvalidTopicError{Topic: topic}
+ }
+
+ if !topicTemplate.HasAccess(issuer, accessType) {
+ return false, TopicNotAllowedError{
+ issuer,
+ sub,
+ accessType,
+ topic,
+ topicTemplate.Type,
+ }
+ }
+
+ return true, nil
+}
+
+func (a AutoAuthenticator) ValidateAccessType(accessType acl.AccessType) bool {
+ for _, allowedAccessType := range a.AllowedAccessTypes {
+ if allowedAccessType == accessType {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (a AutoAuthenticator) GetCompany() string {
+ return a.Company
+}
+
+func (a AutoAuthenticator) IsSuperuser() bool {
+ return false
+}
+
+type autoBlackListChecker struct {
+ userIDs map[int]struct{}
+ userHashedIDs map[string]struct{}
+ iss int
+}
+
+func newAutoBlackListChecker(cfg config.BlackListUserLogging) autoBlackListChecker {
+ users := make(map[int]struct{})
+ for _, userID := range cfg.UserIDs {
+ users[userID] = struct{}{}
+ }
+
+ userHashedIDs := make(map[string]struct{})
+ for _, u := range cfg.UserHashedIDs {
+ userHashedIDs[u] = struct{}{}
+ }
+
+ return autoBlackListChecker{
+ userIDs: users,
+ userHashedIDs: userHashedIDs,
+ iss: cfg.Iss,
+ }
+}
+
+func (a autoBlackListChecker) isBlackListByUserID(userID, iss int) bool {
+ if iss != a.iss {
+ return false
+ }
+
+ _, ok := a.userIDs[userID]
+
+ return ok
+}
+
+func (a autoBlackListChecker) isBlackListByUserHashedID(userHashedID string, iss int) bool {
+ if iss != a.iss {
+ return false
+ }
+
+ _, ok := a.userHashedIDs[userHashedID]
+
+ return ok
+}
diff --git a/internal/authenticator/auto_authenticator_test.go b/internal/authenticator/auto_authenticator_test.go
new file mode 100644
index 00000000..71c78801
--- /dev/null
+++ b/internal/authenticator/auto_authenticator_test.go
@@ -0,0 +1,150 @@
+package authenticator_test
+
+import (
+ "crypto/rsa"
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+ "github.com/snapp-incubator/soteria/pkg/validator"
+ "github.com/stretchr/testify/require"
+ "github.com/stretchr/testify/suite"
+ "go.opentelemetry.io/otel/trace/noop"
+ "go.uber.org/zap"
+)
+
+type AutoAuthenticatorTestSuite struct {
+ suite.Suite
+
+ Token string
+ PublicKey *rsa.PublicKey
+
+ Server *httptest.Server
+
+ Authenticator authenticator.Authenticator
+}
+
+func TestAutoAuthenticator_suite(t *testing.T) {
+ t.Parallel()
+
+ suite.Run(t, new(AutoAuthenticatorTestSuite))
+}
+
+// nolint: funlen
+func (suite *AutoAuthenticatorTestSuite) SetupSuite() {
+ cfg := config.SnappVendor()
+
+ require := suite.Require()
+
+ pkey0, err := getPublicKey("0")
+ require.NoError(err)
+
+ suite.PublicKey = pkey0
+
+ key0, err := getPrivateKey("0")
+ require.NoError(err)
+
+ token, err := getSampleToken("0", key0)
+ require.NoError(err)
+
+ suite.Token = token
+
+ hid, err := topics.NewHashIDManager(cfg.HashIDMap)
+ require.NoError(err)
+
+ testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+ authHeader := req.Header.Get("Authorization")
+ tokenString := strings.TrimPrefix(authHeader, "bearer ")
+
+ _, err := jwt.Parse(tokenString, func(
+ _ *jwt.Token,
+ ) (interface{}, error) {
+ return pkey0, nil
+ })
+ if err != nil {
+ res.WriteHeader(http.StatusUnauthorized)
+
+ return
+ }
+
+ userData, err := json.Marshal(map[string]any{})
+ if err != nil {
+ res.WriteHeader(http.StatusInternalServerError)
+
+ return
+ }
+
+ res.Header().Add("X-User-Data", string(userData))
+
+ res.WriteHeader(http.StatusOK)
+ }))
+ suite.Server = testServer
+
+ suite.Authenticator = authenticator.AutoAuthenticator{
+ Validator: validator.New(testServer.URL, time.Second),
+ AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub},
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Company: "snapp",
+ Parser: jwt.NewParser(),
+ TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()),
+ JWTConfig: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "rsa256",
+ },
+ Logger: zap.NewNop(),
+ }
+}
+
+func (suite *AutoAuthenticatorTestSuite) TestAuth() {
+ require := suite.Require()
+
+ suite.Run("testing valid token auth", func() {
+ require.NoError(suite.Authenticator.Auth(suite.Token))
+ })
+
+ suite.Run("testing invalid token auth", func() {
+ require.Error(suite.Authenticator.Auth(invalidToken))
+ })
+}
+
+func (suite *AutoAuthenticatorTestSuite) TearDownSuite() {
+ suite.Server.Close()
+}
+
+func TestAutoAuthenticator_ValidateTopicBySender(t *testing.T) {
+ t.Parallel()
+
+ cfg := config.SnappVendor()
+
+ hid, err := topics.NewHashIDManager(cfg.HashIDMap)
+ require.NoError(t, err)
+
+ // nolint: exhaustruct
+ authenticator := authenticator.AutoAuthenticator{
+ AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub},
+ Company: "snapp",
+ TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()),
+ Logger: zap.NewNop(),
+ }
+
+ t.Run("testing valid driver cab event", func(t *testing.T) {
+ t.Parallel()
+
+ topicTemplate := authenticator.TopicManager.ParseTopic(
+ validDriverCabEventTopic,
+ topics.DriverIss,
+ "DXKgaNQa7N5Y7bo",
+ nil,
+ )
+ require.NotNil(t, topicTemplate)
+ })
+}
diff --git a/internal/authenticator/builder.go b/internal/authenticator/builder.go
new file mode 100644
index 00000000..dc1c2f8c
--- /dev/null
+++ b/internal/authenticator/builder.go
@@ -0,0 +1,201 @@
+package authenticator
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+ "github.com/snapp-incubator/soteria/pkg/validator"
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/zap"
+)
+
+var (
+ ErrAdminAuthenticatorSystemKey = errors.New("admin authenticator supports only one key named system")
+ ErrNoAuthenticator = errors.New("at least one vendor should be enable to have soteria")
+ ErrNoDefaultCaseIssEntity = errors.New("default case for iss-entity map is required")
+ ErrNoDefaultCaseIssPeer = errors.New("default case for iss-peer map is required")
+ ErrInvalidAuthenticator = errors.New("there is no authenticator to support your request")
+)
+
+type Builder struct {
+ Vendors []config.Vendor
+ Logger *zap.Logger
+ ValidatorConfig config.Validator
+ Tracer trace.Tracer
+ BlackListUserLoggingConfig config.BlackListUserLogging
+}
+
+func (b Builder) Authenticators() (map[string]Authenticator, error) {
+ all := make(map[string]Authenticator)
+
+ for _, vendor := range b.Vendors {
+ var (
+ auth Authenticator
+ err error
+ )
+
+ switch vendor.Type {
+ case "auto", "validator", "validator-based", "using-validator":
+ auth, err = b.autoAuthenticator(vendor, b.BlackListUserLoggingConfig)
+ if err != nil {
+ return nil, fmt.Errorf("cannot build auto authenticator %w", err)
+ }
+ case "admin", "internal":
+ auth, err = b.adminAuthenticator(vendor)
+ if err != nil {
+ return nil, fmt.Errorf("cannot build admin authenticator %w", err)
+ }
+ case "manual":
+ auth, err = b.manualAuthenticator(vendor)
+ if err != nil {
+ return nil, fmt.Errorf("cannot build manual authenticator %w", err)
+ }
+ default:
+ return nil, ErrInvalidAuthenticator
+ }
+
+ all[vendor.Company] = auth
+ }
+
+ if len(all) == 0 {
+ return nil, ErrNoAuthenticator
+ }
+
+ return all, nil
+}
+
+func (b Builder) adminAuthenticator(vendor config.Vendor) (*AdminAuthenticator, error) {
+ if _, ok := vendor.Keys["system"]; !ok || len(vendor.Keys) != 1 {
+ return nil, ErrAdminAuthenticatorSystemKey
+ }
+
+ keys, err := b.GenerateKeys(vendor.Jwt.SigningMethod, vendor.Keys)
+ if err != nil {
+ return nil, fmt.Errorf("loading keys failed %w", err)
+ }
+
+ return &AdminAuthenticator{
+ Key: keys["system"],
+ Company: vendor.Company,
+ JwtConfig: vendor.Jwt,
+ Parser: jwt.NewParser(),
+ }, nil
+}
+
+func (b Builder) manualAuthenticator(vendor config.Vendor) (*ManualAuthenticator, error) {
+ if err := b.ValidateMappers(vendor.IssEntityMap, vendor.IssPeerMap); err != nil {
+ return nil, fmt.Errorf("failed to validate mappers %w", err)
+ }
+
+ allowedAccessTypes, err := b.GetAllowedAccessTypes(vendor.AllowedAccessTypes)
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse allowed access types %w", err)
+ }
+
+ hid, err := topics.NewHashIDManager(vendor.HashIDMap)
+ if err != nil {
+ return nil, fmt.Errorf("cannot create hash-id manager %w", err)
+ }
+
+ keys, err := b.GenerateKeys(vendor.Jwt.SigningMethod, vendor.Keys)
+ if err != nil {
+ return nil, fmt.Errorf("loading keys failed %w", err)
+ }
+
+ return &ManualAuthenticator{
+ Keys: keys,
+ AllowedAccessTypes: allowedAccessTypes,
+ Company: vendor.Company,
+ TopicManager: topics.NewTopicManager(
+ vendor.Topics,
+ hid,
+ vendor.Company,
+ vendor.IssEntityMap,
+ vendor.IssPeerMap,
+ b.Logger.Named("topic-manager"),
+ ),
+ JWTConfig: vendor.Jwt,
+ Parser: jwt.NewParser(jwt.WithValidMethods([]string{vendor.Jwt.SigningMethod})),
+ }, nil
+}
+
+func (b Builder) autoAuthenticator(vendorCfg config.Vendor, blackListUserLoggingCfg config.BlackListUserLogging) (
+ *AutoAuthenticator, error,
+) {
+ allowedAccessTypes, err := b.GetAllowedAccessTypes(vendorCfg.AllowedAccessTypes)
+ if err != nil {
+ return nil, fmt.Errorf("cannot parse allowed access types %w", err)
+ }
+
+ hid, err := topics.NewHashIDManager(vendorCfg.HashIDMap)
+ if err != nil {
+ return nil, fmt.Errorf("cannot create hash-id manager %w", err)
+ }
+
+ client := validator.New(b.ValidatorConfig.URL, b.ValidatorConfig.Timeout)
+
+ return &AutoAuthenticator{
+ AllowedAccessTypes: allowedAccessTypes,
+ Company: vendorCfg.Company,
+ TopicManager: topics.NewTopicManager(
+ vendorCfg.Topics,
+ hid,
+ vendorCfg.Company,
+ vendorCfg.IssEntityMap,
+ vendorCfg.IssPeerMap,
+ b.Logger.Named("topic-manager"),
+ ),
+ Tracer: b.Tracer,
+ JWTConfig: vendorCfg.Jwt,
+ Validator: client,
+ Parser: jwt.NewParser(),
+ Logger: b.Logger.Named("auto-authenticator"),
+ blackList: newAutoBlackListChecker(blackListUserLoggingCfg),
+ }, nil
+}
+
+// GetAllowedAccessTypes will return all allowed access types in Soteria.
+func (b Builder) GetAllowedAccessTypes(accessTypes []string) ([]acl.AccessType, error) {
+ allowedAccessTypes := make([]acl.AccessType, 0, len(accessTypes))
+
+ for _, a := range accessTypes {
+ at, err := toUserAccessType(a)
+ if err != nil {
+ return nil, fmt.Errorf("could not convert %s: %w", at, err)
+ }
+
+ allowedAccessTypes = append(allowedAccessTypes, at)
+ }
+
+ return allowedAccessTypes, nil
+}
+
+// toUserAccessType will convert string access type to it's own type.
+func toUserAccessType(access string) (acl.AccessType, error) {
+ switch access {
+ case "pub", "publish":
+ return acl.Pub, nil
+ case "sub", "subscribe":
+ return acl.Sub, nil
+ case "pubsub", "subpub":
+ return acl.PubSub, nil
+ }
+
+ return "", ErrInvalidAccessType
+}
+
+func (b Builder) ValidateMappers(issEntityMap, issPeerMap map[string]string) error {
+ if _, ok := issEntityMap[topics.Default]; !ok {
+ return ErrNoDefaultCaseIssEntity
+ }
+
+ if _, ok := issPeerMap[topics.Default]; !ok {
+ return ErrNoDefaultCaseIssPeer
+ }
+
+ return nil
+}
diff --git a/internal/authenticator/builder_internal_test.go b/internal/authenticator/builder_internal_test.go
new file mode 100644
index 00000000..35f7f66a
--- /dev/null
+++ b/internal/authenticator/builder_internal_test.go
@@ -0,0 +1,75 @@
+package authenticator
+
+import (
+ "testing"
+
+ "github.com/snapp-incubator/soteria/pkg/acl"
+ "github.com/stretchr/testify/require"
+)
+
+// nolint: funlen
+func TestToUserAccessType(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ cases := []struct {
+ name string
+ input string
+ expected acl.AccessType
+ expectedErr error
+ }{
+ {
+ name: "success",
+ input: "pub",
+ expected: acl.Pub,
+ expectedErr: nil,
+ },
+ {
+ name: "success",
+ input: "publish",
+ expected: acl.Pub,
+ expectedErr: nil,
+ },
+ {
+ name: "success",
+ input: "sub",
+ expected: acl.Sub,
+ expectedErr: nil,
+ },
+ {
+ name: "success",
+ input: "subscribe",
+ expected: acl.Sub,
+ expectedErr: nil,
+ },
+ {
+ name: "success",
+ input: "pubsub",
+ expected: acl.PubSub,
+ expectedErr: nil,
+ },
+ {
+ name: "success",
+ input: "subpub",
+ expected: acl.PubSub,
+ expectedErr: nil,
+ },
+ {
+ name: "failed",
+ input: "-",
+ expected: "",
+ expectedErr: ErrInvalidAccessType,
+ },
+ }
+
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ t.Parallel()
+
+ v, err := toUserAccessType(c.input)
+ require.ErrorIs(c.expectedErr, err)
+ require.Equal(c.expected, v)
+ })
+ }
+}
diff --git a/internal/authenticator/builder_test.go b/internal/authenticator/builder_test.go
new file mode 100644
index 00000000..142fea1c
--- /dev/null
+++ b/internal/authenticator/builder_test.go
@@ -0,0 +1,542 @@
+package authenticator_test
+
+import (
+ "testing"
+
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/stretchr/testify/require"
+ "go.opentelemetry.io/otel/trace/noop"
+ "go.uber.org/zap"
+)
+
+func TestBuilderWithoutAuthenticator(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ b := authenticator.Builder{
+ Vendors: []config.Vendor{},
+ Logger: zap.NewNop(),
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ _, err := b.Authenticators()
+ require.ErrorIs(err, authenticator.ErrNoAuthenticator)
+}
+
+func TestBuilderInvalidAuthenticator(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "internal",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "HS512",
+ },
+ Type: "invalid",
+ AllowedAccessTypes: nil,
+ Topics: nil,
+ HashIDMap: nil,
+ IssEntityMap: nil,
+ IssPeerMap: nil,
+ Keys: map[string]string{
+ "system": "c2VjcmV0",
+ },
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ _, err := b.Authenticators()
+ require.ErrorIs(err, authenticator.ErrInvalidAuthenticator)
+}
+
+func TestBuilderInternalAuthenticator(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "internal",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "HS512",
+ },
+ Type: "internal",
+ AllowedAccessTypes: nil,
+ Topics: nil,
+ HashIDMap: nil,
+ IssEntityMap: nil,
+ IssPeerMap: nil,
+ Keys: map[string]string{
+ "system": "c2VjcmV0",
+ },
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ vendors, err := b.Authenticators()
+ require.NoError(err)
+ require.Len(vendors, 1)
+ require.Contains(vendors, "internal")
+}
+
+func TestBuilderInternalAuthenticatorWithInvalidKey(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "admin",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "HS512",
+ },
+ Type: "internal",
+ AllowedAccessTypes: nil,
+ Topics: nil,
+ HashIDMap: nil,
+ IssEntityMap: nil,
+ IssPeerMap: nil,
+ Keys: map[string]string{
+ "not-system": "c2VjcmV0",
+ },
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ _, err := b.Authenticators()
+ require.ErrorIs(err, authenticator.ErrAdminAuthenticatorSystemKey)
+}
+
+func TestBuilderManualAuthenticatorWithoutKey(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "snapp",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "HS512",
+ },
+ Type: "manual",
+ AllowedAccessTypes: []string{"pub", "sub"},
+ Topics: nil,
+ HashIDMap: map[string]topics.HashData{
+ "0": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ "1": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ },
+ IssEntityMap: map[string]string{
+ "0": "driver",
+ "1": "passenger",
+ "default": "",
+ },
+ IssPeerMap: map[string]string{
+ "0": "passenger",
+ "1": "driver",
+ "default": "",
+ },
+ Keys: nil,
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ _, err := b.Authenticators()
+ require.ErrorIs(err, authenticator.ErrNoKeys)
+}
+
+// nolint: funlen
+func TestBuilderManualAuthenticator(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ // nolint: dupl
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "snapp",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "RSA512",
+ },
+ Type: "manual",
+ AllowedAccessTypes: []string{"pub", "sub"},
+ Topics: nil,
+ HashIDMap: map[string]topics.HashData{
+ "0": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ "1": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ },
+ IssEntityMap: map[string]string{
+ "0": "driver",
+ "1": "passenger",
+ "default": "",
+ },
+ IssPeerMap: map[string]string{
+ "0": "passenger",
+ "1": "driver",
+ "default": "",
+ },
+ Keys: map[string]string{
+ "0": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv
+ va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm
+ X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL
+ C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s
+ ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX
+ J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU
+ DwIDAQAB
+-----END PUBLIC KEY-----`,
+
+ "1": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl
+ +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK
+ aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8
+ 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI
+ Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN
+ YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS
+ pQIDAQAB
+-----END PUBLIC KEY-----`,
+ },
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ vendors, err := b.Authenticators()
+ require.NoError(err)
+ require.Len(vendors, 1)
+ require.Contains(vendors, "snapp")
+}
+
+// nolint: funlen
+func TestBuilderManualAuthenticatorInvalidMapping_1(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "snapp",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "RSA512",
+ },
+ Type: "manual",
+ AllowedAccessTypes: []string{"pub", "sub"},
+ Topics: nil,
+ HashIDMap: map[string]topics.HashData{
+ "0": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ "1": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ },
+ IssEntityMap: map[string]string{
+ "0": "driver",
+ "1": "passenger",
+ "default": "",
+ },
+ IssPeerMap: nil,
+ Keys: map[string]string{
+ "0": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv
+ va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm
+ X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL
+ C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s
+ ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX
+ J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU
+ DwIDAQAB
+-----END PUBLIC KEY-----`,
+
+ "1": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl
+ +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK
+ aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8
+ 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI
+ Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN
+ YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS
+ pQIDAQAB
+-----END PUBLIC KEY-----`,
+ },
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ _, err := b.Authenticators()
+ require.ErrorIs(err, authenticator.ErrNoDefaultCaseIssPeer)
+}
+
+// nolint: funlen
+func TestBuilderManualAuthenticatorInvalidMapping_2(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "snapp",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "RSA512",
+ },
+ Type: "manual",
+ AllowedAccessTypes: []string{"pub", "sub"},
+ Topics: nil,
+ HashIDMap: map[string]topics.HashData{
+ "0": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ "1": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ },
+ IssEntityMap: map[string]string{
+ "0": "driver",
+ "1": "passenger",
+ },
+ IssPeerMap: map[string]string{
+ "0": "passenger",
+ "1": "driver",
+ "default": "",
+ },
+ Keys: map[string]string{
+ "0": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv
+ va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm
+ X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL
+ C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s
+ ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX
+ J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU
+ DwIDAQAB
+-----END PUBLIC KEY-----`,
+
+ "1": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl
+ +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK
+ aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8
+ 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI
+ Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN
+ YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS
+ pQIDAQAB
+-----END PUBLIC KEY-----`,
+ },
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ _, err := b.Authenticators()
+ require.ErrorIs(err, authenticator.ErrNoDefaultCaseIssEntity)
+}
+
+// nolint: funlen
+func TestBuilderManualAuthenticatorInvalidAccess(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ // nolint: dupl
+ b := authenticator.Builder{
+ Tracer: noop.NewTracerProvider().Tracer(""),
+ Vendors: []config.Vendor{
+ {
+ Company: "snapp",
+ Jwt: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "RSA512",
+ },
+ Type: "manual",
+ AllowedAccessTypes: []string{"pub", "superuser"},
+ Topics: nil,
+ HashIDMap: map[string]topics.HashData{
+ "0": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ "1": {
+ Alphabet: "",
+ Length: 15,
+ Salt: "secret",
+ },
+ },
+ IssEntityMap: map[string]string{
+ "0": "driver",
+ "1": "passenger",
+ "default": "",
+ },
+ IssPeerMap: map[string]string{
+ "0": "passenger",
+ "1": "driver",
+ "default": "",
+ },
+ Keys: map[string]string{
+ "0": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv
+ va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm
+ X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL
+ C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s
+ ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX
+ J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU
+ DwIDAQAB
+-----END PUBLIC KEY-----`,
+
+ "1": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl
+ +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK
+ aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8
+ 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI
+ Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN
+ YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS
+ pQIDAQAB
+-----END PUBLIC KEY-----`,
+ },
+ },
+ },
+ Logger: zap.NewNop(),
+ ValidatorConfig: config.Validator{
+ URL: "",
+ Timeout: 0,
+ },
+ BlackListUserLoggingConfig: config.BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+
+ _, err := b.Authenticators()
+ require.ErrorIs(err, authenticator.ErrInvalidAccessType)
+}
diff --git a/internal/authenticator/errors.go b/internal/authenticator/errors.go
new file mode 100644
index 00000000..b5118266
--- /dev/null
+++ b/internal/authenticator/errors.go
@@ -0,0 +1,50 @@
+package authenticator
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/snapp-incubator/soteria/pkg/acl"
+)
+
+var (
+ ErrInvalidSigningMethod = errors.New("signing method does not match with authenticator signing method")
+ ErrIssNotFound = errors.New("could not found iss in token claims")
+ ErrSubNotFound = errors.New("could not found sub in token claims")
+ ErrInvalidClaims = errors.New("invalid claims")
+ ErrInvalidIP = errors.New("IP is not valid")
+ ErrInvalidAccessType = errors.New("requested access type is invalid")
+ ErrDecodeHashID = errors.New("could not decode hash id")
+ ErrInvalidSecret = errors.New("invalid secret")
+ ErrIncorrectPassword = errors.New("username or password is worng")
+)
+
+type TopicNotAllowedError struct {
+ Issuer string
+ Sub string
+ AccessType acl.AccessType
+ Topic string
+ TopicType string
+}
+
+func (err TopicNotAllowedError) Error() string {
+ return fmt.Sprintf("issuer %s with sub %s is not allowed to %s on topic %s (%s)",
+ err.Issuer, err.Sub, err.AccessType, err.Topic, err.TopicType,
+ )
+}
+
+type KeyNotFoundError struct {
+ Issuer string
+}
+
+func (err KeyNotFoundError) Error() string {
+ return fmt.Sprintf("cannot find issuer %s key", err.Issuer)
+}
+
+type InvalidTopicError struct {
+ Topic string
+}
+
+func (err InvalidTopicError) Error() string {
+ return fmt.Sprintf("provided topic %s is not valid", err.Topic)
+}
diff --git a/internal/authenticator/key.go b/internal/authenticator/key.go
new file mode 100644
index 00000000..b794f4d1
--- /dev/null
+++ b/internal/authenticator/key.go
@@ -0,0 +1,90 @@
+package authenticator
+
+import (
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/golang-jwt/jwt/v5"
+ "go.uber.org/zap"
+)
+
+var (
+ ErrInvalidKeyType = errors.New("cannot determine the key type")
+ ErrNoKeys = errors.New("at least one key required")
+)
+
+func (b Builder) GenerateKeys(method string, keys map[string]string) (map[string]any, error) {
+ var (
+ keyList map[string]any
+ err error
+ )
+
+ // https://jwt.io/
+ switch {
+ case strings.HasPrefix(method, "RS"):
+ keyList, err = b.GenerateRSAKeys(keys)
+ case strings.HasPrefix(method, "HS"):
+ keyList, err = b.GenerateHMACKeys(keys)
+ case strings.HasPrefix(method, "ES"):
+ keyList, err = b.GenerateECDSAKeys(keys)
+ default:
+ return nil, ErrInvalidKeyType
+ }
+
+ if err != nil {
+ return nil, fmt.Errorf("reading keys failed %w", err)
+ }
+
+ if len(keyList) == 0 {
+ return nil, ErrNoKeys
+ }
+
+ return keyList, nil
+}
+
+func (b Builder) GenerateRSAKeys(raw map[string]string) (map[string]any, error) {
+ keys := make(map[string]any)
+
+ for iss, publicKey := range raw {
+ bytes, err := jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
+ if err != nil {
+ b.Logger.Fatal("could not read public key", zap.String("issuer", iss), zap.Error(err))
+ }
+
+ keys[iss] = bytes
+ }
+
+ return keys, nil
+}
+
+func (b Builder) GenerateECDSAKeys(raw map[string]string) (map[string]any, error) {
+ keys := make(map[string]any)
+
+ for iss, publicKey := range raw {
+ bytes, err := jwt.ParseECPublicKeyFromPEM([]byte(publicKey))
+ if err != nil {
+ b.Logger.Fatal("could not read public key", zap.String("issuer", iss), zap.Error(err))
+ }
+
+ keys[iss] = bytes
+ }
+
+ return keys, nil
+}
+
+func (b Builder) GenerateHMACKeys(raw map[string]string) (map[string]any, error) {
+ keys := make(map[string]any)
+
+ for iss, key := range raw {
+ bytes, err := base64.StdEncoding.DecodeString(key)
+ if err != nil {
+ return nil, fmt.Errorf("failed to generate hmac key from base64 %w", err)
+ }
+
+ keys[iss] = bytes
+ }
+
+ return keys, nil
+}
diff --git a/internal/authenticator/manual_authenticator.go b/internal/authenticator/manual_authenticator.go
new file mode 100644
index 00000000..2babb003
--- /dev/null
+++ b/internal/authenticator/manual_authenticator.go
@@ -0,0 +1,142 @@
+package authenticator
+
+import (
+ "fmt"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+)
+
+// ManualAuthenticator is responsible for Acl/Auth/Token of userIDs without calling
+// any http client, etc.
+type ManualAuthenticator struct {
+ Keys map[string]any
+ AllowedAccessTypes []acl.AccessType
+ TopicManager *topics.Manager
+ Company string
+ JWTConfig config.JWT
+ Parser *jwt.Parser
+}
+
+// Auth check user authentication by checking the user's token.
+func (a ManualAuthenticator) Auth(tokenString string) error {
+ _, err := a.Parser.Parse(tokenString, func(
+ token *jwt.Token,
+ ) (interface{}, error) {
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok {
+ return nil, ErrInvalidClaims
+ }
+
+ if claims[a.JWTConfig.IssName] == nil {
+ return nil, ErrIssNotFound
+ }
+
+ issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName])
+
+ key := a.Keys[issuer]
+ if key == nil {
+ return nil, KeyNotFoundError{Issuer: issuer}
+ }
+
+ return key, nil
+ })
+ if err != nil {
+ return fmt.Errorf("token is invalid: %w", err)
+ }
+
+ return nil
+}
+
+// ACL check a user access to a topic.
+// nolint: funlen, cyclop, dupl
+func (a ManualAuthenticator) ACL(
+ accessType acl.AccessType,
+ tokenString string,
+ topic string,
+) (bool, error) {
+ if !a.ValidateAccessType(accessType) {
+ return false, ErrInvalidAccessType
+ }
+
+ token, err := a.Parser.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok {
+ return nil, ErrInvalidClaims
+ }
+
+ if claims[a.JWTConfig.IssName] == nil {
+ return nil, ErrIssNotFound
+ }
+
+ if claims[a.JWTConfig.SubName] == nil {
+ return nil, ErrSubNotFound
+ }
+
+ issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName])
+
+ key := a.Keys[issuer]
+ if key == nil {
+ return nil, KeyNotFoundError{Issuer: issuer}
+ }
+
+ return key, nil
+ })
+ if err != nil {
+ return false, fmt.Errorf("token is invalid: %w", err)
+ }
+
+ claims, ok := token.Claims.(jwt.MapClaims)
+ if !ok {
+ return false, ErrInvalidClaims
+ }
+
+ if claims[a.JWTConfig.IssName] == nil {
+ return false, ErrIssNotFound
+ }
+
+ issuer := fmt.Sprintf("%v", claims[a.JWTConfig.IssName])
+
+ if claims[a.JWTConfig.SubName] == nil {
+ return false, ErrSubNotFound
+ }
+
+ sub, _ := claims[a.JWTConfig.SubName].(string)
+
+ topicTemplate := a.TopicManager.ParseTopic(topic, issuer, sub, map[string]any(claims))
+ if topicTemplate == nil {
+ return false, InvalidTopicError{Topic: topic}
+ }
+
+ if !topicTemplate.HasAccess(issuer, accessType) {
+ return false, TopicNotAllowedError{
+ issuer,
+ sub,
+ accessType,
+ topic,
+ topicTemplate.Type,
+ }
+ }
+
+ return true, nil
+}
+
+func (a ManualAuthenticator) ValidateAccessType(accessType acl.AccessType) bool {
+ for _, allowedAccessType := range a.AllowedAccessTypes {
+ if allowedAccessType == accessType {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (a ManualAuthenticator) GetCompany() string {
+ return a.Company
+}
+
+func (a ManualAuthenticator) IsSuperuser() bool {
+ return false
+}
diff --git a/internal/authenticator/manual_authenticator_test.go b/internal/authenticator/manual_authenticator_test.go
new file mode 100644
index 00000000..c3795963
--- /dev/null
+++ b/internal/authenticator/manual_authenticator_test.go
@@ -0,0 +1,479 @@
+package authenticator_test
+
+import (
+ "crypto/rsa"
+ "testing"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+ "github.com/stretchr/testify/require"
+ "github.com/stretchr/testify/suite"
+ "go.uber.org/zap"
+)
+
+type ManualAuthenticatorSnappTestSuite struct {
+ suite.Suite
+
+ Tokens struct {
+ Passenger string
+ Driver string
+ }
+
+ PublicKeys struct {
+ Passenger *rsa.PublicKey
+ Driver *rsa.PublicKey
+ }
+
+ PrivateKeys struct {
+ Passenger *rsa.PrivateKey
+ Driver *rsa.PrivateKey
+ }
+
+ Authenticator authenticator.Authenticator
+}
+
+func TestManualAuthenticator_suite(t *testing.T) {
+ t.Parallel()
+
+ suite.Run(t, new(ManualAuthenticatorSnappTestSuite))
+}
+
+func (suite *ManualAuthenticatorSnappTestSuite) SetupSuite() {
+ cfg := config.SnappVendor()
+
+ require := suite.Require()
+
+ pkey0, err := getPublicKey("0")
+ require.NoError(err)
+
+ suite.PublicKeys.Driver = pkey0
+
+ pkey1, err := getPublicKey("1")
+ require.NoError(err)
+
+ suite.PublicKeys.Passenger = pkey1
+
+ key0, err := getPrivateKey("0")
+ require.NoError(err)
+
+ suite.PrivateKeys.Driver = key0
+
+ key1, err := getPrivateKey("1")
+ require.NoError(err)
+
+ suite.PrivateKeys.Passenger = key1
+
+ driverToken, err := getSampleToken("0", key0)
+ require.NoError(err)
+
+ suite.Tokens.Driver = driverToken
+
+ passengerToken, err := getSampleToken("1", key1)
+ require.NoError(err)
+
+ suite.Tokens.Passenger = passengerToken
+
+ hid, err := topics.NewHashIDManager(cfg.HashIDMap)
+ require.NoError(err)
+
+ suite.Authenticator = authenticator.ManualAuthenticator{
+ Keys: map[string]any{
+ topics.DriverIss: pkey0,
+ topics.PassengerIss: pkey1,
+ },
+ AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub},
+ Company: "snapp",
+ Parser: jwt.NewParser(),
+ TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()),
+ JWTConfig: config.JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "rsa256",
+ },
+ }
+}
+
+func (suite *ManualAuthenticatorSnappTestSuite) TestAuth() {
+ require := suite.Require()
+
+ suite.Run("testing driver token auth", func() {
+ require.NoError(suite.Authenticator.Auth(suite.Tokens.Driver))
+ })
+
+ suite.Run("testing passenger token auth", func() {
+ require.NoError(suite.Authenticator.Auth(suite.Tokens.Passenger))
+ })
+
+ suite.Run("testing invalid token auth", func() {
+ require.Error(suite.Authenticator.Auth(invalidToken))
+ })
+
+ suite.Run("testing token with invalid iss", func() {
+ token, err := getSampleToken("-1", suite.PrivateKeys.Passenger)
+ require.NoError(err)
+
+ require.ErrorIs(suite.Authenticator.Auth(token), authenticator.KeyNotFoundError{
+ Issuer: "-1",
+ })
+ })
+}
+
+func (suite *ManualAuthenticatorSnappTestSuite) TestACLBasics() {
+ require := suite.Require()
+
+ suite.Run("testing acl with invalid access type", func() {
+ ok, err := suite.Authenticator.ACL("invalid-access", suite.Tokens.Passenger, "test")
+ require.False(ok)
+ require.ErrorIs(err, authenticator.ErrInvalidAccessType)
+ })
+
+ suite.Run("testing acl with invalid token", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, invalidToken, validDriverCabEventTopic)
+ require.False(ok)
+ require.ErrorIs(err, jwt.ErrTokenMalformed)
+ })
+
+ suite.Run("testing acl with valid inputs", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, suite.Tokens.Passenger, validPassengerCabEventTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing acl with invalid topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, suite.Tokens.Passenger, invalidPassengerCabEventTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing acl with invalid access type", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, suite.Tokens.Passenger, validPassengerCabEventTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+}
+
+// nolint: funlen
+func (suite *ManualAuthenticatorSnappTestSuite) TestACLPassenger() {
+ require := suite.Require()
+ token := suite.Tokens.Passenger
+
+ suite.Run("testing passenger subscribe on valid superapp event topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerSuperappEventTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing passenger subscribe on invalid superapp event topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerSuperappEventTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing passenger subscribe on valid shared location topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerSharedTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing passenger subscribe on invalid shared location topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerSharedTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing passenger subscribe on valid chat topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerChatTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing passenger subscribe on invalid chat topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerChatTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing passenger subscribe on valid entry call topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, validPassengerCallEntryTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing passenger subscribe on invalid call entry topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, invalidPassengerCallEntryTopic)
+ require.ErrorIs(err, authenticator.InvalidTopicError{
+ Topic: invalidPassengerCallEntryTopic,
+ })
+ require.False(ok)
+ })
+
+ suite.Run("testing passenger subscribe on valid outgoing call topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validPassengerCallOutgoingTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing passenger subscribe on valid outgoing call node topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, validPassengerNodeCallEntryTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing passenger subscribe on invalid call outgoing topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidPassengerCallOutgoingTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+}
+
+// nolint: funlen
+func (suite *ManualAuthenticatorSnappTestSuite) TestACLDriver() {
+ require := suite.Require()
+ token := suite.Tokens.Driver
+
+ suite.Run("testing driver publish on its location topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, validDriverLocationTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing driver publish on invalid location topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, invalidDriverLocationTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing driver subscribe on invalid cab event topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverCabEventTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing driver subscribe on valid superapp event topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverSuperappEventTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing driver subscribe on invalid superapp event topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverSuperappEventTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing driver subscribe on valid shared location topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverSharedTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing driver subscribe on invalid shared location topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverSharedTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing driver subscribe on valid chat topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverChatTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing driver subscribe on invalid chat topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverChatTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing driver subscribe on valid call entry topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, validDriverCallEntryTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing driver subscribe on invalid call entry topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, invalidDriverCallEntryTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+
+ suite.Run("testing driver subscribe on valid call outgoing topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, validDriverCallOutgoingTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing driver subscribe on valid call outgoing node topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Pub, token, validDriverNodeCallEntryTopic)
+ require.NoError(err)
+ require.True(ok)
+ })
+
+ suite.Run("testing driver subscribe on invalid call outgoing topic", func() {
+ ok, err := suite.Authenticator.ACL(acl.Sub, token, invalidDriverCallOutgoingTopic)
+ require.Error(err)
+ require.False(ok)
+ })
+}
+
+func TestManualAuthenticator_ValidateTopicBySender(t *testing.T) {
+ t.Parallel()
+
+ cfg := config.SnappVendor()
+
+ hid, err := topics.NewHashIDManager(cfg.HashIDMap)
+ require.NoError(t, err)
+
+ // nolint: exhaustruct
+ authenticator := authenticator.ManualAuthenticator{
+ AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub},
+ Company: "snapp",
+ TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()),
+ }
+
+ t.Run("testing valid driver cab event", func(t *testing.T) {
+ t.Parallel()
+
+ topicTemplate := authenticator.TopicManager.ParseTopic(
+ validDriverCabEventTopic,
+ topics.DriverIss,
+ "DXKgaNQa7N5Y7bo",
+ nil,
+ )
+ require.NotNil(t, topicTemplate)
+ })
+}
+
+// Provide a topic which we don't have at snapp to test functions and fields that
+// are used during the templating.
+func TestManualAuthenticator_ValidateTopicBySenderAndClaims(t *testing.T) {
+ t.Parallel()
+
+ cfg := config.SnappVendor()
+
+ cfg.Topics = append(cfg.Topics, topics.Topic{
+ Type: topics.SharedLocation,
+ Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/{{.uid}}/{{DecodeHashID (EncodeHashID .sub .iss) .iss}}$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Sub,
+ topics.PassengerIss: acl.Sub,
+ },
+ },
+ )
+
+ hid, err := topics.NewHashIDManager(cfg.HashIDMap)
+ require.NoError(t, err)
+
+ // nolint: exhaustruct
+ authenticator := authenticator.ManualAuthenticator{
+ AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub},
+ Company: "snapp",
+ TopicManager: topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop()),
+ }
+
+ t.Run("testing valid driver cab event", func(t *testing.T) {
+ t.Parallel()
+
+ topicTemplate := authenticator.TopicManager.ParseTopic(
+ "snapp/driver/123/456/123",
+ topics.DriverIss,
+ "123",
+ map[string]any{
+ "uid": "456",
+ },
+ )
+ require.NotNil(t, topicTemplate)
+ })
+}
+
+// nolint: funlen
+func TestManualAuthenticator_validateAccessType(t *testing.T) {
+ t.Parallel()
+
+ type fields struct {
+ AllowedAccessTypes []acl.AccessType
+ }
+
+ type args struct {
+ accessType acl.AccessType
+ }
+
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ want bool
+ }{
+ {
+ name: "#1 testing with no allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{}},
+ args: args{accessType: acl.Sub},
+ want: false,
+ },
+ {
+ name: "#2 testing with no allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{}},
+ args: args{accessType: acl.Pub},
+ want: false,
+ },
+ {
+ name: "#3 testing with no allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{}},
+ args: args{accessType: acl.PubSub},
+ want: false,
+ },
+ {
+ name: "#4 testing with one allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}},
+ args: args{accessType: acl.Pub},
+ want: true,
+ },
+ {
+ name: "#5 testing with one allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub}},
+ args: args{accessType: acl.Sub},
+ want: false,
+ },
+ {
+ name: "#6 testing with two allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}},
+ args: args{accessType: acl.Sub},
+ want: true,
+ },
+ {
+ name: "#7 testing with two allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}},
+ args: args{accessType: acl.Pub},
+ want: true,
+ },
+ {
+ name: "#8 testing with two allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub}},
+ args: args{accessType: acl.PubSub},
+ want: false,
+ },
+ {
+ name: "#9 testing with three allowed access type",
+ fields: fields{AllowedAccessTypes: []acl.AccessType{acl.Pub, acl.Sub, acl.PubSub}},
+ args: args{accessType: acl.PubSub},
+ want: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ // nolint: exhaustruct
+ a := authenticator.ManualAuthenticator{
+ AllowedAccessTypes: tt.fields.AllowedAccessTypes,
+ }
+ if got := a.ValidateAccessType(tt.args.accessType); got != tt.want {
+ t.Errorf("validateAccessType() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/internal/authenticator/metrics.go b/internal/authenticator/metrics.go
new file mode 100644
index 00000000..0ce11f3b
--- /dev/null
+++ b/internal/authenticator/metrics.go
@@ -0,0 +1,56 @@
+package authenticator
+
+import (
+ "errors"
+
+ "github.com/prometheus/client_golang/prometheus"
+ "github.com/prometheus/client_golang/prometheus/promauto"
+)
+
+// nolint:exhaustruct,gochecknoglobals
+var AuthenticateCounterMetric = promauto.NewCounterVec(prometheus.CounterOpts{
+ Namespace: "dispatching",
+ Subsystem: "soteria",
+ Name: "auth_total",
+ Help: "Total number of authentication attempts",
+}, []string{"company", "status"})
+
+// nolint:cyclop
+func IncrementWithErrorAuthCounter(company string, err error) {
+ var (
+ status string
+ topicNotAllowedErrorTarget *TopicNotAllowedError
+ keyNotFoundErrorTarget *KeyNotFoundError
+ )
+
+ switch {
+ case err == nil:
+ status = "success"
+ case errors.Is(err, ErrInvalidSigningMethod):
+ status = "err_invalid_signing_method"
+ case errors.Is(err, ErrIssNotFound):
+ status = "err_iss_not_found"
+ case errors.Is(err, ErrSubNotFound):
+ status = "err_sub_not_found"
+ case errors.Is(err, ErrInvalidClaims):
+ status = "err_invalid_claims"
+ case errors.Is(err, ErrInvalidIP):
+ status = "err_invalid_ip"
+ case errors.Is(err, ErrInvalidAccessType):
+ status = "err_invalid_access_type"
+ case errors.Is(err, ErrDecodeHashID):
+ status = "err_decode_hash_id"
+ case errors.Is(err, ErrInvalidSecret):
+ status = "err_invalid_secret"
+ case errors.Is(err, ErrIncorrectPassword):
+ status = "err_incorrect_password"
+ case errors.As(err, &topicNotAllowedErrorTarget):
+ status = "topic_not_allowed_error"
+ case errors.As(err, &keyNotFoundErrorTarget):
+ status = "key_not_found_error"
+ default:
+ status = "unknown_error"
+ }
+
+ AuthenticateCounterMetric.WithLabelValues(company, status).Inc()
+}
diff --git a/internal/authenticator/metrics_test.go b/internal/authenticator/metrics_test.go
new file mode 100644
index 00000000..08687c95
--- /dev/null
+++ b/internal/authenticator/metrics_test.go
@@ -0,0 +1,78 @@
+//nolint:testpackage
+package authenticator
+
+import (
+ "errors"
+ "testing"
+)
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_NoError(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", nil)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrInvalidSigningMethod(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrInvalidSigningMethod)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrIssNotFound(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrIssNotFound)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrSubNotFound(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrSubNotFound)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrInvalidClaims(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrInvalidClaims)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrInvalidIP(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrInvalidIP)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrInvalidAccessType(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrInvalidAccessType)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrDecodeHashID(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrDecodeHashID)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrInvalidSecret(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrInvalidSecret)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrIncorrectPassword(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", ErrIncorrectPassword)
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrTopicNotAllowed(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", &TopicNotAllowedError{
+ Issuer: "issuer",
+ Sub: "subject",
+ AccessType: "1",
+ Topic: "topic",
+ TopicType: "pub",
+ })
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_ErrKeyNotFound(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", &KeyNotFoundError{Issuer: "iss"})
+}
+
+//nolint:paralleltest
+func TestIncrementWithErrorAuthCounter_UnknowError(_ *testing.T) {
+ IncrementWithErrorAuthCounter("snapp", errors.ErrUnsupported)
+}
diff --git a/internal/cmd/root.go b/internal/cmd/root.go
new file mode 100644
index 00000000..f7eb3564
--- /dev/null
+++ b/internal/cmd/root.go
@@ -0,0 +1,47 @@
+package cmd
+
+import (
+ "os"
+
+ "github.com/snapp-incubator/soteria/internal/cmd/serve"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/logger"
+ "github.com/snapp-incubator/soteria/internal/tracing"
+ "github.com/spf13/cobra"
+ "go.uber.org/zap"
+)
+
+// ExitFailure status code.
+const ExitFailure = 1
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+ cfg := config.New()
+
+ logger := logger.New(cfg.Logger).Named("root")
+
+ tracer := tracing.New(cfg.Tracer, logger.Named("tracer"))
+
+ //nolint: exhaustruct
+ root := &cobra.Command{
+ Use: "soteria",
+ Short: "Soteria is the authentication service.",
+ Long: `Soteria is responsible for Authentication and Authorization of every request witch send to EMQ Server.`,
+ Run: func(cmd *cobra.Command, _ []string) {
+ cmd.Println("Run `soteria serve` to start serving requests")
+ },
+ }
+
+ serve.Serve{
+ Cfg: cfg,
+ Logger: logger.Named("serve"),
+ Tracer: tracer,
+ }.Register(root)
+
+ if err := root.Execute(); err != nil {
+ logger.Error("failed to execute root command", zap.Error(err))
+
+ os.Exit(ExitFailure)
+ }
+}
diff --git a/internal/cmd/serve/main.go b/internal/cmd/serve/main.go
new file mode 100644
index 00000000..747b0d9f
--- /dev/null
+++ b/internal/cmd/serve/main.go
@@ -0,0 +1,77 @@
+package serve
+
+import (
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+ "os/signal"
+
+ "github.com/snapp-incubator/soteria/internal/api"
+ "github.com/snapp-incubator/soteria/internal/authenticator"
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/spf13/cobra"
+ "go.opentelemetry.io/otel/trace"
+ "go.uber.org/zap"
+)
+
+type Serve struct {
+ Cfg config.Config
+ Logger *zap.Logger
+ Tracer trace.Tracer
+}
+
+func (s Serve) main() {
+ auth, err := authenticator.Builder{
+ Vendors: s.Cfg.Vendors,
+ Logger: s.Logger,
+ ValidatorConfig: s.Cfg.Validator,
+ Tracer: s.Tracer,
+ BlackListUserLoggingConfig: s.Cfg.BlackListUserLogging,
+ }.Authenticators()
+ if err != nil {
+ s.Logger.Fatal("authenticator building failed", zap.Error(err))
+ }
+
+ api := api.API{
+ DefaultVendor: s.Cfg.DefaultVendor,
+ Authenticators: auth,
+ Tracer: s.Tracer,
+ Logger: s.Logger.Named("api"),
+ }
+
+ if _, ok := api.Authenticators[s.Cfg.DefaultVendor]; !ok {
+ s.Logger.Fatal("default vendor shouldn't be nil, please set it")
+ }
+
+ rest := api.ReSTServer()
+
+ go func() {
+ if err := rest.Listen(fmt.Sprintf(":%d", s.Cfg.HTTPPort)); err != nil && !errors.Is(err, http.ErrServerClosed) {
+ s.Logger.Fatal("failed to run REST HTTP server", zap.Error(err))
+ }
+ }()
+
+ c := make(chan os.Signal, 1)
+ signal.Notify(c, os.Interrupt)
+ <-c
+
+ if err := rest.Shutdown(); err != nil {
+ s.Logger.Error("error happened during REST API shutdown", zap.Error(err))
+ }
+}
+
+// Register serve command.
+func (s Serve) Register(root *cobra.Command) {
+ root.AddCommand(
+ //nolint: exhaustruct
+ &cobra.Command{
+ Use: "serve",
+ Short: "serve runs the application",
+ Long: `serve will run Soteria ReST server and waits until user disrupts.`,
+ Run: func(_ *cobra.Command, _ []string) {
+ s.main()
+ },
+ },
+ )
+}
diff --git a/internal/commands/accounts/accounts.go b/internal/commands/accounts/accounts.go
deleted file mode 100644
index 7ae75234..00000000
--- a/internal/commands/accounts/accounts.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package accounts
-
-import (
- "fmt"
- "github.com/spf13/cobra"
-)
-
-var Accounts = &cobra.Command{
- Use: "accounts",
- Short: "Soteria account management",
- Long: `accounts is used to manage all operations related to accounts`,
- Run: accountsRun,
-}
-
-func accountsRun(cmd *cobra.Command, args []string) {
- fmt.Println("run `soteria accounts --help` to see available options")
-}
diff --git a/internal/commands/accounts/init.go b/internal/commands/accounts/init.go
deleted file mode 100644
index bf4fd81f..00000000
--- a/internal/commands/accounts/init.go
+++ /dev/null
@@ -1,69 +0,0 @@
-package accounts
-
-import (
- "encoding/json"
- "fmt"
- "io/ioutil"
-
- "github.com/spf13/cobra"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/config"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- "golang.org/x/crypto/bcrypt"
-)
-
-var Init = &cobra.Command{
- Use: "init [json file]",
- Short: "init accounts data from a json file",
- Long: `init is used to set all accounts data from a json file `,
- Args: cobra.ExactArgs(1),
- PreRunE: initPreRun,
- RunE: initRun,
-}
-
-func initPreRun(cmd *cobra.Command, args []string) error {
- cfg := config.InitConfig()
-
- rClient, err := redis.NewRedisClient(cfg.Redis)
- if err != nil {
- return fmt.Errorf("could not init redis: %w", err)
- }
-
- app.GetInstance().SetAccountsService(&accounts.Service{
- Handler: redis.ModelHandler{
- Client: rClient,
- },
- })
-
- return nil
-}
-
-func initRun(cmd *cobra.Command, args []string) error {
- file := args[0]
-
- raw, err := ioutil.ReadFile(file)
- if err != nil {
- return fmt.Errorf("failed to read file: %w", err)
- }
-
- var users []user.User
- if err := json.Unmarshal(raw, &users); err != nil {
- return fmt.Errorf("failed to unmarshal data: %w", err)
- }
-
- for _, u := range users {
- hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
- if err != nil {
- return fmt.Errorf("failed to generate hash for %s, %w", u.Username, err)
- }
- u.Password = string(hash)
-
- if err := app.GetInstance().AccountsService.Handler.Save(cmd.Context(), u); err != nil {
- return fmt.Errorf("failed to import user %s: %w", u.Username, err)
- }
- }
-
- return nil
-}
diff --git a/internal/commands/root.go b/internal/commands/root.go
deleted file mode 100644
index 262f45d8..00000000
--- a/internal/commands/root.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package commands
-
-import (
- "github.com/spf13/cobra"
-)
-
-var Root = &cobra.Command{
- Use: "soteria",
- Short: "Soteria is the authentication service.",
- Long: `Soteria is responsible for Authentication and Authorization of every request witch send to EMQ Server.`,
- Run: rootRun,
-}
-
-func rootRun(cmd *cobra.Command, args []string) {
- cmd.Println("Run `soteria serve` to start serving requests")
-}
diff --git a/internal/commands/serve.go b/internal/commands/serve.go
deleted file mode 100644
index fd1ec8f2..00000000
--- a/internal/commands/serve.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package commands
-
-import (
- "context"
- "crypto/rsa"
- "errors"
- "fmt"
- "net"
- "net/http"
- "os"
- "os/signal"
-
- "github.com/patrickmn/go-cache"
- "github.com/spf13/cobra"
- "gitlab.snapp.ir/dispatching/snappids/v2"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/config"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/cachedredis"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/metrics"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/grpc"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/log"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/memoize"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/tracer"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- _ "go.uber.org/automaxprocs"
- "go.uber.org/zap"
- grpcLib "google.golang.org/grpc"
-)
-
-var Serve = &cobra.Command{
- Use: "serve",
- Short: "serve runs the application",
- Long: `serve will run Soteria REST and gRPC server and waits until user disrupts.`,
- PreRun: servePreRun,
- Run: serveRun,
-}
-
-var cfg config.AppConfig
-
-func servePreRun(cmd *cobra.Command, args []string) {
- cfg = config.InitConfig()
- log.InitLogger()
- log.SetLevel(cfg.Logger.Level)
-
- zap.L().Debug("config init successfully",
- zap.String("cache_config", pkg.PrettifyStruct(cfg.Cache)),
- zap.String("redis_config", pkg.PrettifyStruct(cfg.Redis)),
- zap.String("logger_config", pkg.PrettifyStruct(cfg.Logger)),
- zap.String("jwt_keys_path", cfg.Jwt.KeysPath),
- zap.String("allowed_access_types", fmt.Sprintf("%v", cfg.AllowedAccessTypes)))
-
- privateKey100, err := cfg.ReadPrivateKey(user.ThirdParty)
- if err != nil {
- zap.L().Fatal("could not read third party private key")
- }
-
- publicKey100, err := cfg.ReadPublicKey(user.ThirdParty)
- if err != nil {
- zap.L().Fatal("could not read third party public key")
- }
-
- publicKey0, err := cfg.ReadPublicKey(user.Driver)
- if err != nil {
- zap.L().Fatal("could not read driver public key")
- }
-
- publicKey1, err := cfg.ReadPublicKey(user.Passenger)
- if err != nil {
- zap.L().Fatal("could not read passenger public key")
- }
-
- hid := &snappids.HashIDSManager{
- Salts: map[snappids.Audience]string{
- snappids.DriverAudience: cfg.DriverSalt,
- snappids.PassengerAudience: cfg.PassengerSalt,
- },
- Lengths: map[snappids.Audience]int{
- snappids.DriverAudience: cfg.DriverHashLength,
- snappids.PassengerAudience: cfg.PassengerHashLength,
- },
- }
-
- trc, cl, err := tracer.New(cfg.Tracer)
- if err != nil {
- zap.L().Fatal("could not create tracer", zap.Error(err))
- }
-
- app.GetInstance().SetTracer(trc, cl)
-
- rClient, err := redis.NewRedisClient(cfg.Redis)
- if err != nil {
- zap.L().Fatal("could not create redis client", zap.Error(err))
- }
-
- redisModelHandler := &redis.ModelHandler{Client: rClient}
-
- var modelHandler db.ModelHandler
- if cfg.Cache.Enabled {
- modelHandler = cachedredis.NewCachedRedisModelHandler(redisModelHandler,
- cache.New(cfg.Cache.Expiration, cache.NoExpiration),
- )
- } else {
- modelHandler = redisModelHandler
- }
-
- app.GetInstance().SetAccountsService(&accounts.Service{
- Handler: modelHandler,
- })
-
- store := emq.Store{Client: rClient}
- app.GetInstance().SetEMQStore(store)
-
- allowedAccessTypes, err := cfg.GetAllowedAccessTypes()
- if err != nil {
- zap.L().Fatal("error while getting allowed access types", zap.Error(err))
- }
- memoizedCompareHashAndPassword := memoize.MemoizedCompareHashAndPassword()
-
- app.GetInstance().SetAuthenticator(&authenticator.Authenticator{
- PrivateKeys: map[user.Issuer]*rsa.PrivateKey{
- user.ThirdParty: privateKey100,
- },
- PublicKeys: map[user.Issuer]*rsa.PublicKey{
- user.Driver: publicKey0,
- user.Passenger: publicKey1,
- user.ThirdParty: publicKey100,
- },
- AllowedAccessTypes: allowedAccessTypes,
- ModelHandler: modelHandler,
- HashIDSManager: hid,
- EMQTopicManager: snappids.NewEMQManagerWithCompany(hid, cfg.Company),
- CompareHashAndPassword: memoizedCompareHashAndPassword,
- Company: cfg.Company,
- })
-
- m := metrics.NewMetrics()
- app.GetInstance().SetMetrics(&m.Handler)
-}
-
-func serveRun(cmd *cobra.Command, args []string) {
- rest := api.RestServer(cfg.Mode, cfg.HttpPort)
-
- gListen, err := net.Listen("tcp", fmt.Sprintf(":%d", cfg.GrpcPort))
- if err != nil {
- zap.L().Fatal("failed to listen", zap.Int("port", cfg.GrpcPort), zap.Error(err))
- }
-
- grpcServer := grpc.NewServer()
-
- go func() {
- if err := rest.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
- zap.L().Fatal("failed to run REST HTTP server", zap.Error(err))
- }
- }()
-
- go func() {
- if err := grpcServer.Serve(gListen); err != nil && !errors.Is(err, grpcLib.ErrServerStopped) {
- zap.L().Fatal("failed to run GRPC server", zap.Error(err))
- }
- }()
-
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt)
- <-c
-
- if err := rest.Shutdown(context.Background()); err != nil {
- zap.L().Error("error happened during REST API shutdown", zap.Error(err))
- }
-
- grpcServer.Stop()
-
- if err := app.GetInstance().TracerCloser.Close(); err != nil {
- zap.L().Error("error happened while closing tracer", zap.Error(err))
- }
-}
diff --git a/internal/commands/token.go b/internal/commands/token.go
deleted file mode 100644
index 559a355f..00000000
--- a/internal/commands/token.go
+++ /dev/null
@@ -1,229 +0,0 @@
-package commands
-
-import (
- "bufio"
- "crypto/rsa"
- "errors"
- "fmt"
- "os"
- "time"
-
- "github.com/spf13/cobra"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/config"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
-)
-
-var (
- ErrInvalidToken = errors.New("invalid token type")
- ErrNoToken = errors.New("must be at least one token type")
-)
-
-type ErrInvalidInput struct {
- Message string
-}
-
-func (err ErrInvalidInput) Error() string {
- return fmt.Sprintf("invalid input. %s", err.Message)
-}
-
-var Token = &cobra.Command{
- Use: "token",
- Short: "token issues token based on type of user",
- Args: func(cmd *cobra.Command, args []string) error {
- if len(args) < 1 {
- return ErrNoToken
- }
-
- t := args[0]
- if t == "herald" || t == "superuser" {
- return nil
- }
-
- return ErrInvalidInput{Message: fmt.Sprintf("token with type %s is not valid", t)}
- },
- RunE: func(cmd *cobra.Command, args []string) error {
- config := config.InitConfig()
-
- pk100, err := config.ReadPrivateKey(user.ThirdParty)
- if err != nil {
- return fmt.Errorf("cannot load private keys %w", err)
- }
-
- // nolint: exhaustivestruct
- app.GetInstance().SetAuthenticator(&authenticator.Authenticator{
- PrivateKeys: map[user.Issuer]*rsa.PrivateKey{
- user.ThirdParty: pk100,
- },
- })
-
- switch args[0] {
- case "herald":
- return heraldToken(cmd, args)
- case "superuser":
- return superuserToken(config, cmd, args)
- default:
- return ErrInvalidToken
- }
- },
-}
-
-func heraldToken(cmd *cobra.Command, args []string) error {
- r := bufio.NewScanner(os.Stdin)
-
- cmd.Print("username? >>> ")
- r.Scan()
- username := r.Text()
-
- cmd.Print("duration? (in hours) >>> ")
- r.Scan()
- duration, err := time.ParseDuration(fmt.Sprintf("%sh", r.Text()))
- if err != nil {
- return fmt.Errorf("cannot parse duration %w", err)
- }
-
- var (
- allowedTopics []acl.Topic
- allowedEndpoints []acl.Endpoint
- )
-
- heraldEndpoints := map[string]string{
- "1": "/event",
- "2": "/events",
- "3": "/notification",
- }
- topicTypes := map[string]topics.Type{
- "1": topics.CabEvent,
- "2": topics.BoxEvent,
- "3": topics.SuperappEvent,
- "4": topics.DriverLocation,
- "5": topics.PassengerLocation,
- "6": topics.SharedLocation,
- "7": topics.Chat,
- }
- accessTypes := map[string]acl.AccessType{
- "1": acl.Sub,
- "2": acl.Pub,
- "3": acl.PubSub,
- }
-
- for {
- cmd.Println("do you want to give permissions? [y/n]")
- cmd.Print(">>> ")
- r.Scan()
-
- switch r.Text() {
- case "n":
- token, err := app.GetInstance().Authenticator.HeraldToken(username, allowedEndpoints, allowedTopics, duration)
- if err != nil {
- return fmt.Errorf("token creation failed %w", err)
- }
-
- cmd.Println(token)
-
- return nil
- case "y":
- cmd.Println("which one do you want to grant access to? \n\t1. topic\n\t2. endpoint")
- cmd.Print(">>> ")
- r.Scan()
-
- switch r.Text() {
- case "1":
- cmd.Println("which topic do you want to grant access to?\n" +
- "\t1. Snapp Cab Events" +
- "\t2. Snapp Box Events" +
- "\t3. Snapp Super App Events" +
- "\t4. Snapp Driver Locations")
- cmd.Print(">>> ")
- r.Scan()
- topicType, ok := topicTypes[r.Text()]
- if !ok {
- return ErrInvalidInput{Message: "selected topic does not exist"}
- }
-
- cmd.Println("which access type do you want to grant?\n" +
- "\t1. Subscribe" +
- "\t2. Publish" +
- "\t3. Publish-Subscribe")
- cmd.Print(">>> ")
- r.Scan()
- at, ok := accessTypes[r.Text()]
- if !ok {
- return ErrInvalidInput{Message: "invalid input. selected access type does not exist"}
- }
- topic := acl.Topic{
- Type: topicType,
- AccessType: at,
- }
- allowedTopics = append(allowedTopics, topic)
- case "2":
- cmd.Println("which endpoint do you want to grant access to?\n" +
- "\t1. event" +
- "\t2. events" +
- "\t3. notification")
- cmd.Print(">>> ")
- r.Scan()
- endpoint, ok := heraldEndpoints[r.Text()]
- if !ok {
- return ErrInvalidInput{Message: "invalid input. selected endpoint does not exist"}
- }
- e := acl.Endpoint{Name: endpoint}
- allowedEndpoints = append(allowedEndpoints, e)
- default:
- return ErrInvalidInput{Message: ""}
- }
- default:
- return ErrInvalidInput{Message: "you should enter y or n"}
- }
- }
-}
-
-func superuserToken(config config.AppConfig, cmd *cobra.Command, _ []string) error {
- cli, err := redis.NewRedisClient(config.Redis)
- if err != nil {
- return fmt.Errorf("redis connection failed %w", err)
- }
-
- app.GetInstance().SetEMQStore(emq.Store{
- Client: cli,
- })
-
- r := bufio.NewScanner(os.Stdin)
-
- cmd.Print("username? >>> ")
- r.Scan()
- username := r.Text()
-
- cmd.Print("duration? (in hours) >>> ")
- r.Scan()
- duration, err := time.ParseDuration(fmt.Sprintf("%sh", r.Text()))
- if err != nil {
- return fmt.Errorf("cannot parse duration %w", err)
- }
-
- cmd.Print("password? >>> ")
- r.Scan()
- password := r.Text()
-
- token, err := app.GetInstance().Authenticator.SuperuserToken(username, duration)
- if err != nil {
- return fmt.Errorf("token creation failed %w", err)
- }
-
- if err := app.GetInstance().EMQStore.Save(cmd.Context(), emq.User{
- Username: token,
- Password: password,
- IsSuperuser: true,
- }); err != nil {
- return fmt.Errorf("cannot save token to redis %w", err)
- }
-
- cmd.Println(token)
-
- return nil
-}
diff --git a/internal/config/config.go b/internal/config/config.go
index a0fb72b1..73eba7ff 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -1,165 +1,109 @@
package config
import (
- "crypto/rsa"
- "fmt"
- "io/ioutil"
+ "encoding/json"
+ "log"
+ "strings"
"time"
- "github.com/dgrijalva/jwt-go"
- "github.com/kelseyhightower/envconfig"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
+ "github.com/knadh/koanf/parsers/yaml"
+ "github.com/knadh/koanf/providers/env"
+ "github.com/knadh/koanf/providers/file"
+ "github.com/knadh/koanf/providers/structs"
+ "github.com/knadh/koanf/v2"
+ "github.com/snapp-incubator/soteria/internal/logger"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/internal/tracing"
+ "github.com/tidwall/pretty"
)
-// AppConfig is the main container of Soteria's config.
-type AppConfig struct {
- AllowedAccessTypes []string `default:"sub,pub" split_words:"true"`
- PassengerHashLength int `split_words:"true"`
- DriverHashLength int `split_words:"true"`
- PassengerSalt string `split_words:"true"`
- DriverSalt string `split_words:"true"`
- Redis *RedisConfig
- Jwt *JwtConfig
- Logger *LoggerConfig
- Cache *CacheConfig
- Mode string `default:"debug"`
- HttpPort int `default:"9999" split_words:"true"`
- GrpcPort int `default:"50051" split_words:"true"`
- Tracer *TracerConfig
- Company string `default:"snapp"`
-}
+const (
+ // Prefix indicates environment variables prefix.
+ Prefix = "soteria_"
+)
-// RedisConfig is all configs needed to connect to a Redis server.
-type RedisConfig struct {
- Address string `split_words:"true"`
- Password string `default:"" split_words:"true"`
- ExpirationTime int `default:"30" split_words:"true"`
- PoolSize int `split_words:"true" default:"10"`
- MaxRetries int `split_words:"true" default:"0"`
- MinIdleConnections int `split_words:"true" default:"5"`
- ReadTimeout time.Duration `split_words:"true" default:"3s"`
- PoolTimeout time.Duration `split_words:"true" default:"4s"`
- MinRetryBackoff time.Duration `split_words:"true" default:"8ms"`
- MaxRetryBackoff time.Duration `split_words:"true" default:"512ms"`
- IdleTimeout time.Duration `split_words:"true" default:"300s"`
- IdleCheckFrequency time.Duration `split_words:"true" default:"60s"`
-}
+type (
+ // Config is the main container of Soteria's config.
+ Config struct {
+ Vendors []Vendor `json:"vendors,omitempty" koanf:"vendors"`
+ Logger logger.Config `json:"logger,omitempty" koanf:"logger"`
+ HTTPPort int `json:"http_port,omitempty" koanf:"http_port"`
+ Tracer tracing.Config `json:"tracer,omitempty" koanf:"tracer"`
+ DefaultVendor string `json:"default_vendor,omitempty" koanf:"default_vendor"`
+ Validator Validator `json:"validator,omitempty" koanf:"validator"`
+ BlackListUserLogging BlackListUserLogging `json:"black_list_user_logging,omitempty" koanf:"black_list_user_logging"`
+ }
-// CacheConfig contains configs of in memory cache.
-type CacheConfig struct {
- Enabled bool `split_words:"true" default:"true"`
- Expiration time.Duration `split_words:"true" default:"600s"`
-}
+ Vendor struct {
+ AllowedAccessTypes []string `json:"allowed_access_types,omitempty" koanf:"allowed_access_types"`
+ Company string `json:"company,omitempty" koanf:"company"`
+ Topics []topics.Topic `json:"topics,omitempty" koanf:"topics"`
+ Keys map[string]string `json:"keys,omitempty" koanf:"keys"`
+ IssEntityMap map[string]string `json:"iss_entity_map,omitempty" koanf:"iss_entity_map"`
+ IssPeerMap map[string]string `json:"iss_peer_map,omitempty" koanf:"iss_peer_map"`
+ Jwt JWT `json:"jwt,omitempty" koanf:"jwt"`
+ Type string `json:"type,omitempty" koanf:"type"`
+ HashIDMap map[string]topics.HashData `json:"hash_id_map,omitempty" koanf:"hashid_map"`
+ }
-// JwtConfig contains path of the keys for JWT encryption.
-type JwtConfig struct {
- KeysPath string `split_words:"true" default:"test/"`
-}
+ JWT struct {
+ IssName string `json:"iss_name,omitempty" koanf:"iss_name"`
+ SubName string `json:"sub_name,omitempty" koanf:"sub_name"`
+ SigningMethod string `json:"signing_method,omitempty" koanf:"signing_method"`
+ }
-// LoggerConfig is the config for logging and this kind of stuff.
-type LoggerConfig struct {
- Level string `default:"warn" split_words:"true"`
+ Validator struct {
+ URL string `json:"url,omitempty" koanf:"url"`
+ Timeout time.Duration `json:"timeout,omitempty" koanf:"timeout"`
+ }
- SentryEnabled bool `default:"false" split_words:"true"`
- SentryDSN string `envconfig:"SENTRY_DSN"`
- SentryTimeout time.Duration `split_words:"true" default:"100ms"`
-}
+ BlackListUserLogging struct {
+ Iss int `json:"iss,omitempty" koanf:"iss"`
+ UserIDs []int `json:"user_ids,omitempty" koanf:"user_ids"`
+ UserHashedIDs []string `json:"user_hashed_ids,omitempty" koanf:"user_hashed_ids"`
+ }
+)
-// TracerConfig contains all configs needed to create a tracer.
-type TracerConfig struct {
- Enabled bool `split_words:"false" default:"true"`
- ServiceName string `default:"soteria" split_words:"true"`
- SamplerType string `default:"const" split_words:"true"`
- SamplerParam float64 `default:"1" split_words:"true"`
- Host string `default:"localhost" split_words:"true"`
- Port int `default:"6831" split_words:"true"`
-}
+// New reads configuration with koanf.
+func New() Config {
+ var instance Config
-// InitConfig tries to initialize app config from env variables.
-func InitConfig() AppConfig {
- appConfig := &AppConfig{}
- appConfig.Redis = &RedisConfig{}
- appConfig.Cache = &CacheConfig{}
- appConfig.Jwt = &JwtConfig{}
- appConfig.Logger = &LoggerConfig{}
- appConfig.Tracer = &TracerConfig{}
-
- envconfig.MustProcess("soteria", appConfig)
- envconfig.MustProcess("soteria_redis", appConfig.Redis)
- envconfig.MustProcess("soteria_cache", appConfig.Cache)
- envconfig.MustProcess("soteria_jwt", appConfig.Jwt)
- envconfig.MustProcess("soteria_logger", appConfig.Logger)
- envconfig.MustProcess("soteria_tracer", appConfig.Tracer)
- return *appConfig
-}
+ k := koanf.New(".")
-// ReadPrivateKey will read and return private key that is used for JWT encryption
-func (a *AppConfig) ReadPrivateKey(u user.Issuer) (*rsa.PrivateKey, error) {
- var fileName string
- switch u {
- case user.ThirdParty:
- fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "100.private.pem")
- default:
- return nil, fmt.Errorf("invalid issuer, private key not found")
- }
- pem, err := ioutil.ReadFile(fileName)
- if err != nil {
- return nil, err
+ // load default configuration from file
+ if err := k.Load(structs.Provider(Default(), "koanf"), nil); err != nil {
+ log.Fatalf("error loading default: %s", err)
}
- privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(pem)
- if err != nil {
- return nil, err
+
+ // load configuration from file
+ if err := k.Load(file.Provider("config.yml"), yaml.Parser()); err != nil {
+ log.Printf("error loading config.yml: %s", err)
}
- return privateKey, nil
-}
-// ReadPrivateKey will read and return private key that is used for JWT encryption
-func (a *AppConfig) ReadPublicKey(u user.Issuer) (*rsa.PublicKey, error) {
- var fileName string
- switch u {
- case user.Driver:
- fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "0.pem")
- case user.Passenger:
- fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "1.pem")
- case user.ThirdParty:
- fileName = fmt.Sprintf("%s%s", a.Jwt.KeysPath, "100.pem")
- default:
- return nil, fmt.Errorf("invalid issuer, public key not found")
+ // load environment variables
+ if err := k.Load(env.Provider(Prefix, ".", func(s string) string {
+ return strings.ReplaceAll(strings.ToLower(
+ strings.TrimPrefix(s, Prefix)), "_", ".")
+ }), nil); err != nil {
+ log.Printf("error loading environment variables: %s", err)
}
- pem, err := ioutil.ReadFile(fileName)
- if err != nil {
- return nil, err
+
+ if err := k.Unmarshal("", &instance); err != nil {
+ log.Fatalf("error unmarshalling config: %s", err)
}
- privateKey, err := jwt.ParseRSAPublicKeyFromPEM(pem)
+
+ indent, err := json.MarshalIndent(instance, "", "\t")
if err != nil {
- return nil, err
+ log.Fatalf("error marshaling configuration to json: %s", err)
}
- return privateKey, nil
-}
-// GetAllowedAccessTypes will return all allowed access types in Soteria
-func (a *AppConfig) GetAllowedAccessTypes() ([]acl.AccessType, error) {
- var allowedAccessTypes []acl.AccessType
- for _, a := range a.AllowedAccessTypes {
- at, err := toUserAccessType(a)
- if err != nil {
- return nil, fmt.Errorf("could not convert %s: %w", at, err)
- }
- allowedAccessTypes = append(allowedAccessTypes, at)
- }
- return allowedAccessTypes, nil
-}
+ indent = pretty.Color(indent, nil)
+ tmpl := `
+ ================ Loaded Configuration ================
+ %s
+ ======================================================
+ `
+ log.Printf(tmpl, string(indent))
-// toUserAccessType will convert string access type to it's own type
-func toUserAccessType(i string) (acl.AccessType, error) {
- switch i {
- case "pub":
- return acl.Pub, nil
- case "sub":
- return acl.Sub, nil
- case "pubsub":
- return acl.PubSub, nil
- }
- return "", fmt.Errorf("%v is a invalid acces type", i)
+ return instance
}
diff --git a/internal/config/default.go b/internal/config/default.go
new file mode 100644
index 00000000..6ab7774a
--- /dev/null
+++ b/internal/config/default.go
@@ -0,0 +1,188 @@
+package config
+
+import (
+ "time"
+
+ "github.com/snapp-incubator/soteria/internal/logger"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/internal/tracing"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+)
+
+const (
+ DefaultHTTPPort = 9999
+ DefaultDriverHashLength = 15
+ DefaultPassengerHashLength = 15
+)
+
+// Default return default configuration.
+// nolint: mnd
+func Default() Config {
+ return Config{
+ DefaultVendor: "snapp",
+ Vendors: []Vendor{
+ SnappVendor(),
+ },
+ Logger: logger.Config{
+ Level: "debug",
+ Stacktrace: true,
+ },
+ HTTPPort: DefaultHTTPPort,
+ Tracer: tracing.Config{
+ Enabled: false,
+ Ratio: 0.1,
+ Endpoint: "127.0.0.1:4317",
+ },
+ Validator: Validator{
+ URL: "http://validator-lb",
+ Timeout: 5 * time.Second,
+ },
+ BlackListUserLogging: BlackListUserLogging{
+ Iss: 0,
+ UserIDs: []int{},
+ UserHashedIDs: []string{},
+ },
+ }
+}
+
+// nolint: funlen
+func SnappVendor() Vendor {
+ return Vendor{
+ Type: "manual",
+ AllowedAccessTypes: []string{
+ "pub",
+ "sub",
+ },
+ HashIDMap: map[string]topics.HashData{
+ "0": {
+ Alphabet: "",
+ Length: DefaultDriverHashLength,
+ Salt: "secret",
+ },
+ "1": {
+ Alphabet: "",
+ Length: DefaultPassengerHashLength,
+ Salt: "secret",
+ },
+ },
+ Company: "snapp",
+ Topics: []topics.Topic{
+ {
+ Type: topics.CabEvent,
+ Template: "^{{IssToEntity .iss}}-event-{{ EncodeMD5 (DecodeHashID .sub .iss) }}$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Sub,
+ topics.PassengerIss: acl.Sub,
+ },
+ },
+ {
+ Type: topics.DriverLocation,
+ Template: "^{{.company}}/driver/{{.sub}}/location$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Pub,
+ topics.PassengerIss: acl.None,
+ },
+ },
+ {
+ Type: topics.PassengerLocation,
+ Template: "^{{.company}}/passenger/{{.sub}}/location$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Pub,
+ topics.PassengerIss: acl.Pub,
+ },
+ },
+ {
+ Type: topics.SuperappEvent,
+ Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/superapp$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Sub,
+ topics.PassengerIss: acl.Sub,
+ },
+ },
+ {
+ Type: topics.BoxEvent,
+ Template: "^bucks$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.None,
+ topics.PassengerIss: acl.None,
+ },
+ },
+ {
+ Type: topics.SharedLocation,
+ Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/{{IssToPeer .iss}}-location$", //nolint:lll
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Sub,
+ topics.PassengerIss: acl.Sub,
+ },
+ },
+ {
+ Type: topics.Chat,
+ Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/chat$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Sub,
+ topics.PassengerIss: acl.Sub,
+ },
+ },
+ {
+ Type: topics.GeneralCallEntry,
+ Template: "^shared/{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/send$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Pub,
+ topics.PassengerIss: acl.Pub,
+ },
+ },
+ {
+ Type: topics.NodeCallEntry,
+ Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/[a-zA-Z0-9-_]+/send$", //nolint: lll
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Pub,
+ topics.PassengerIss: acl.Pub,
+ },
+ },
+ {
+ Type: topics.CallOutgoing,
+ Template: "^{{.company}}/{{IssToEntity .iss}}/{{.sub}}/call/receive$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Sub,
+ topics.PassengerIss: acl.Sub,
+ },
+ },
+ },
+ Keys: map[string]string{
+ "0": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyG4XpV9TpDfgWJF9TiIv
+ va4hNhDuqYMJO6iXLzr3y8oCvoB7zUK0EjtbLH+A3gr1kUvyZKDWT4qHTvU2Sshm
+ X+ttWGK34EhCvF3Lb18yxmVDSSK8JIcTaJjMqmyubxzamQnNoWazJ7ea9BIo2YGL
+ C9rgPbi1hihhdb07xPGUkJRqbWkI98xjDhKdMqiwW1hIRXm/apo++FjptvqvF84s
+ ynC5gWGFHiGNICRsLJBczLEAf2Atbafigq6/tovzMabnp2yRtr1ReEgioH1RO4gX
+ J7F4N5f6y/VWd8+sDOSxtS/HcnP/7g8/A54G2IbXxr+EiwOO/1F+pyMPKq7sGDSU
+ DwIDAQAB
+-----END PUBLIC KEY-----`,
+
+ "1": `-----BEGIN PUBLIC KEY-----
+ MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5SeRfOdTyvQZ7N9ahFHl
+ +J05r7e9fgOQ2cpOtnnsIjAjCt1dF7/NkqVifEaxABRBGG9iXIw//G4hi0TqoKqK
+ aoSHMGf6q9pSRLGyB8FatxZf2RBTgrXYqVvpasbnB1ZNv858yTpRjV9NzJXYHLp8
+ 8Hbd/yYTR6Q7ajs11/SMLGO7KBELsI1pBz7UW/fngJ2pRmd+RkG+EcGrOIZ27TkI
+ Xjtog6bgfmtV9FWxSVdKACOY0OmW+g7jIMik2eZTYG3kgCmW2odu3zRoUa7l9VwN
+ YMuhTePaIWwOifzRQt8HDsAOpzqJuLCoYX7HmBfpGAnwu4BuTZgXVwpvPNb+KlgS
+ pQIDAQAB
+-----END PUBLIC KEY-----`,
+ },
+ IssEntityMap: map[string]string{
+ "0": "driver",
+ "1": "passenger",
+ "default": "",
+ },
+ IssPeerMap: map[string]string{
+ "0": "passenger",
+ "1": "driver",
+ "default": "",
+ },
+ Jwt: JWT{
+ IssName: "iss",
+ SubName: "sub",
+ SigningMethod: "RS512",
+ },
+ }
+}
diff --git a/internal/constants.go b/internal/constants.go
deleted file mode 100644
index 5515900e..00000000
--- a/internal/constants.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package internal
-
-const (
- Soteria = "soteria"
- Acl = "acl"
- Auth = "auth"
- Token = "token"
- Success = "success"
- Failure = "failure"
- Ignore = "ignore"
- CabEvent = "cab_event"
- DriverLocation = "driver_location"
- SuperAppEvent = "super_app_event"
-
- HttpApi = "http"
- GrpcApi = "grpc"
-)
diff --git a/internal/db/cachedredis/c.go b/internal/db/cachedredis/c.go
deleted file mode 100644
index 7affa532..00000000
--- a/internal/db/cachedredis/c.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package cachedredis
-
-import (
- "context"
- "encoding/json"
-
- "github.com/patrickmn/go-cache"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis"
-)
-
-type ModelHandler struct {
- redisModelHandler *redis.ModelHandler
- cache *cache.Cache
-}
-
-func NewCachedRedisModelHandler(redisModelHandler *redis.ModelHandler, cache *cache.Cache) *ModelHandler {
- return &ModelHandler{
- redisModelHandler: redisModelHandler,
- cache: cache,
- }
-}
-
-// Save saves a model in redis.
-func (mh *ModelHandler) Save(ctx context.Context, model db.Model) error {
- return mh.redisModelHandler.Save(ctx, model)
-}
-
-// Delete finds and deletes a model from redis and cache.
-func (mh *ModelHandler) Delete(ctx context.Context, modelName, pk string) error {
- key := redis.GenerateKey(modelName, pk)
- mh.cache.Delete(key)
-
- return mh.redisModelHandler.Delete(ctx, modelName, pk)
-}
-
-// Get searches cache at first then returns a model from redis or from cache, if exists.
-func (mh *ModelHandler) Get(ctx context.Context, modelName, pk string, v db.Model) error {
- key := redis.GenerateKey(modelName, pk)
-
- item, found := mh.cache.Get(key)
- if found {
- if err := json.Unmarshal([]byte(item.(string)), v); err != nil {
- return err
- }
-
- return nil
- }
-
- err := mh.redisModelHandler.Get(ctx, modelName, pk, v)
- if err != nil {
- return err
- }
-
- value, _ := json.Marshal(v)
- mh.cache.SetDefault(key, string(value))
-
- return nil
-}
-
-// Update invalidates cache and then finds and updates a model in redis.
-func (mh *ModelHandler) Update(ctx context.Context, model db.Model) error {
- key := redis.GenerateKey(model.GetMetadata().ModelName, model.GetPrimaryKey())
-
- mh.cache.Delete(key)
-
- return mh.redisModelHandler.Update(ctx, model)
-}
diff --git a/internal/db/cachedredis/c_test.go b/internal/db/cachedredis/c_test.go
deleted file mode 100644
index 531ea0f3..00000000
--- a/internal/db/cachedredis/c_test.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package cachedredis_test
-
-import (
- "context"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/alicebob/miniredis/v2"
- "github.com/go-redis/redis/v8"
- "github.com/patrickmn/go-cache"
- "github.com/stretchr/testify/suite"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/cachedredis"
- rd "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis"
-)
-
-type CacheModelHandlerSuite struct {
- suite.Suite
-
- Model db.ModelHandler
- DB *redis.Client
- Cache *cache.Cache
-}
-
-func (suite *CacheModelHandlerSuite) SetupSuite() {
- mr, err := miniredis.Run()
- suite.NoError(err)
-
- // nolint: exhaustivestruct
- client := redis.NewClient(&redis.Options{
- Addr: mr.Addr(),
- })
-
- cache := cache.New(time.Minute*60, time.Minute*60)
-
- suite.DB = client
- suite.Cache = cache
-
- suite.Model = cachedredis.NewCachedRedisModelHandler(&rd.ModelHandler{Client: client}, cache)
-}
-
-func (suite *CacheModelHandlerSuite) TestGet() {
- require := suite.Require()
-
- expected := MockModel{Name: "test", Value: ""}
- v, err := json.Marshal(expected)
- require.NoError(err)
-
- require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err())
-
- suite.Run("testing first successful get", func() {
- var actual MockModel
- require.NoError(suite.Model.Get(context.Background(), "mock", "test", &actual))
- require.Equal(expected, actual)
-
- suite.Cache.Get(rd.GenerateKey("mock", "test"))
- })
- suite.Run("testing second successful get", func() {
- var actual MockModel
- err := suite.Model.Get(context.Background(), "mock", "test", &actual)
-
- require.Equal(expected, actual)
- require.NoError(err)
- })
- suite.Run("testing failed get", func() {
- var actual MockModel
- err := suite.Model.Get(context.Background(), "mock", "t", &actual)
-
- require.EqualError(err, "redis: nil")
- })
-
- suite.NoError(suite.DB.Del(context.Background(), "mock-test").Err())
-}
-
-func (suite *CacheModelHandlerSuite) TestSave() {
- require := suite.Require()
-
- expected := MockModel{Name: "save-test", Value: ""}
-
- require.NoError(suite.Model.Save(context.Background(), expected))
-
- v := suite.DB.Get(context.Background(), "mock-save-test").Val()
-
- var actual MockModel
-
- require.NoError(json.Unmarshal([]byte(v), &actual))
- require.Equal(expected, actual)
-}
-
-func (suite *CacheModelHandlerSuite) TestDelete() {
- require := suite.Require()
-
- expected := MockModel{Name: "test", Value: ""}
- v, err := json.Marshal(expected)
- require.NoError(err)
-
- require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err())
-
- suite.Run("testing successful delete", func() {
- require.NoError(suite.Model.Delete(context.Background(), "mock", "test"))
-
- err := suite.DB.Get(context.Background(), "mock-test").Err()
- require.EqualError(err, "redis: nil")
-
- item, ok := suite.Cache.Get("mock-test")
- require.False(ok)
- require.Nil(item)
- })
- suite.Run("testing failed delete", func() {
- var actual MockModel
-
- err := suite.Model.Get(context.Background(), "mock", "t", &actual)
- require.EqualError(err, "redis: nil")
- })
-}
-
-func (suite *CacheModelHandlerSuite) TestUpdate() {
- require := suite.Require()
-
- m := MockModel{Name: "test", Value: "test-1"}
- v, err := json.Marshal(m)
- require.NoError(err)
-
- require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err())
-
- newModel := MockModel{Name: "test", Value: "test-2"}
- require.NoError(suite.Model.Update(context.Background(), newModel))
-
- var updatedModel MockModel
-
- require.NoError(suite.Model.Get(context.Background(), "mock", "test", &updatedModel))
- require.Equal(newModel, updatedModel)
-}
-
-type MockModel struct {
- Name string `json:"name"`
- Value string `json:"value"`
-}
-
-func (m MockModel) GetMetadata() db.MetaData {
- return db.MetaData{
- ModelName: "mock",
- DateCreated: time.Time{},
- DateModified: time.Time{},
- }
-}
-
-func (m MockModel) GetPrimaryKey() string {
- return m.Name
-}
-
-func TestCacheModelHandlerSuite(t *testing.T) {
- t.Parallel()
-
- suite.Run(t, new(CacheModelHandlerSuite))
-}
diff --git a/internal/db/main/main.go b/internal/db/main/main.go
deleted file mode 100644
index 74f2e44c..00000000
--- a/internal/db/main/main.go
+++ /dev/null
@@ -1,305 +0,0 @@
-package main
-
-import (
- "crypto/rsa"
- "encoding/json"
- "fmt"
- "github.com/dgrijalva/jwt-go"
- "github.com/google/uuid"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- "io/ioutil"
- "os"
- "time"
-)
-
-func main() {
- password := "password"
- secret := "secret"
-
- var users []user.User
- driver := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "driver",
- Password: string(password),
- Type: user.EMQUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.CabEvent,
- AccessType: acl.Sub,
- },
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.DriverLocation,
- AccessType: acl.Pub,
- },
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.SuperappEvent,
- AccessType: acl.Sub,
- },
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.SharedLocation,
- AccessType: acl.Sub,
- },
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.Chat,
- AccessType: acl.PubSub,
- },
- },
- }
-
- passenger := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "passenger",
- Password: string(password),
- Type: user.EMQUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- {
- UUID: uuid.New(),
- Topic: topics.CabEvent,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.SuperappEvent,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.PassengerLocation,
- AccessType: acl.Pub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.SharedLocation,
- AccessType: acl.Sub,
- },
- {
- UUID: uuid.New(),
- Topic: topics.Chat,
- AccessType: acl.PubSub,
- },
- },
- }
- box := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "box",
- Password: string(password),
- Type: user.EMQUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.BoxEvent,
- AccessType: acl.Sub,
- },
- },
- }
-
- colonySubscriber := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "colony-subscriber",
- Password: string(password),
- Type: user.EMQUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Topic: topics.DriverLocation,
- AccessType: acl.Sub,
- },
- },
- }
-
- snappbox := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "snapp-box",
- Password: string(password),
- Type: user.HeraldUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Endpoint: "/notification",
- AccessType: acl.Pub,
- },
- },
- }
-
- snappfood := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "snapp-food",
- Password: string(password),
- Type: user.HeraldUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Endpoint: "/notification",
- AccessType: acl.Pub,
- },
- },
- }
-
- snappmarket := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "snapp-market",
- Password: string(password),
- Type: user.HeraldUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Endpoint: "/notification",
- AccessType: acl.Pub,
- },
- },
- }
-
- snappflight := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "snapp-flight",
- Password: string(password),
- Type: user.HeraldUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Endpoint: "/notification",
- AccessType: acl.Pub,
- },
- },
- }
-
- snappdoctor := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "snapp-doctor",
- Password: string(password),
- Type: user.HeraldUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Endpoint: "/notification",
- AccessType: acl.Pub,
- },
- },
- }
-
- gabriel := user.User{
- MetaData: db.MetaData{
- ModelName: "user",
- DateCreated: time.Now(),
- DateModified: time.Now(),
- },
- Username: "gabriel",
- Password: string(password),
- Type: user.HeraldUser,
- Secret: string(secret),
- TokenExpirationDuration: time.Hour * 30 * 24,
- Rules: []user.Rule{
- user.Rule{
- UUID: uuid.New(),
- Endpoint: "/notification",
- AccessType: acl.Pub,
- },
- user.Rule{
- UUID: uuid.New(),
- Endpoint: "/event",
- AccessType: acl.Pub,
- },
- },
- }
-
- users = append(users, driver)
- users = append(users, passenger)
- users = append(users, box)
- users = append(users, colonySubscriber)
- users = append(users, snappbox)
- users = append(users, snappfood)
- users = append(users, snappmarket)
- users = append(users, snappflight)
- users = append(users, snappdoctor)
- users = append(users, gabriel)
-
- d, _ := json.Marshal(users)
- ioutil.WriteFile("db.json", d, 0644)
-}
-
-func getPublicKey(u user.Issuer) (*rsa.PublicKey, error) {
- var fileName string
- d, _ := os.Getwd()
- fmt.Println(d)
- switch u {
- case user.Passenger:
- fileName = "test/1.pem"
- case user.Driver:
- fileName = "test/0.pem"
- case user.ThirdParty:
- fileName = "test/100.pem"
- default:
- return nil, fmt.Errorf("invalid user, public key not found")
- }
- pem, err := ioutil.ReadFile(fileName)
- if err != nil {
- return nil, err
- }
- publicKey, err := jwt.ParseRSAPublicKeyFromPEM(pem)
- if err != nil {
- return nil, err
- }
- return publicKey, nil
-}
diff --git a/internal/db/models.go b/internal/db/models.go
deleted file mode 100644
index d1633395..00000000
--- a/internal/db/models.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package db
-
-import (
- "context"
- "errors"
- "time"
-)
-
-// ErrDb is returned when there was an error in database operations
-var ErrDb = errors.New("database error")
-
-type ModelHandler interface {
- Save(ctx context.Context, model Model) error
- Delete(ctx context.Context, modelName, pk string) error
- Get(ctx context.Context, modelName, pk string, model Model) error
- Update(ctx context.Context, model Model) error
-}
-
-type Model interface {
- GetMetadata() MetaData
- GetPrimaryKey() string
-}
-
-type MetaData struct {
- ModelName string `json:"model_name"`
- DateCreated time.Time `json:"date_created"`
- DateModified time.Time `json:"date_modified"`
-}
diff --git a/internal/db/redis/client.go b/internal/db/redis/client.go
deleted file mode 100644
index e4bab455..00000000
--- a/internal/db/redis/client.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package redis
-
-import (
- "context"
- "fmt"
-
- "github.com/go-redis/redis/v8"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/config"
-)
-
-func NewRedisClient(cfg *config.RedisConfig) (*redis.Client, error) {
- // nolint: exhaustivestruct
- opts := &redis.Options{
- Addr: cfg.Address,
- Password: cfg.Password,
- PoolTimeout: cfg.PoolTimeout,
- ReadTimeout: cfg.ReadTimeout,
- MaxRetries: cfg.MaxRetries,
- MaxRetryBackoff: cfg.MaxRetryBackoff,
- MinRetryBackoff: cfg.MinRetryBackoff,
- IdleTimeout: cfg.IdleTimeout,
- IdleCheckFrequency: cfg.IdleCheckFrequency,
- PoolSize: cfg.PoolSize,
- MinIdleConns: cfg.MinIdleConnections,
- }
-
- client := redis.NewClient(opts)
- if err := client.Ping(context.Background()).Err(); err != nil {
- return nil, fmt.Errorf("could not ping redis: %w", err)
- }
-
- return client, nil
-}
diff --git a/internal/db/redis/redis.go b/internal/db/redis/redis.go
deleted file mode 100644
index 635f8646..00000000
--- a/internal/db/redis/redis.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package redis
-
-import (
- "context"
- "encoding/json"
- "fmt"
-
- "github.com/go-redis/redis/v8"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
-)
-
-// RedisModelHandler implements ModelHandler interface.
-type ModelHandler struct {
- Client redis.Cmdable
-}
-
-// Save saves a model in redis.
-func (rmh ModelHandler) Save(ctx context.Context, model db.Model) error {
- md := model.GetMetadata()
- pk := model.GetPrimaryKey()
- key := GenerateKey(md.ModelName, pk)
-
- value, err := json.Marshal(model)
- if err != nil {
- return err
- }
-
- if err := rmh.Client.Set(ctx, key, string(value), 0).Err(); err != nil {
- return fmt.Errorf("%w: %s", db.ErrDb, err)
- }
-
- return nil
-}
-
-// Delete finds and deletes a model from redis and cache.
-func (rmh ModelHandler) Delete(ctx context.Context, modelName, pk string) error {
- key := GenerateKey(modelName, pk)
-
- res, err := rmh.Client.Del(ctx, key).Result()
- if err != nil {
- return fmt.Errorf("%w: %s", db.ErrDb, err)
- }
-
- if res < 1 {
- return fmt.Errorf("key does not exist")
- }
-
- return nil
-}
-
-// Get returns a model from redis or from cache, if exists.
-func (rmh ModelHandler) Get(ctx context.Context, modelName, pk string, v db.Model) error {
- key := GenerateKey(modelName, pk)
-
- res, err := rmh.Client.Get(ctx, key).Result()
- if err != nil && err == redis.Nil {
- return fmt.Errorf("%s", err)
- } else if err != nil {
- return fmt.Errorf("%w: %s", db.ErrDb, err)
- }
-
- if err := json.Unmarshal([]byte(res), v); err != nil {
- return fmt.Errorf("cannot unmarshal model %w", err)
- }
-
- return nil
-}
-
-// Update finds and updates a model in redis.
-func (rmh ModelHandler) Update(ctx context.Context, model db.Model) error {
- md := model.GetMetadata()
- pk := model.GetPrimaryKey()
-
- key := GenerateKey(md.ModelName, pk)
-
- value, err := json.Marshal(model)
- if err != nil {
- return err
- }
-
- pipeline := rmh.Client.Pipeline()
- pipeline.Del(ctx, key)
- pipeline.Set(ctx, key, string(value), 0)
- if _, err := pipeline.Exec(ctx); err != nil {
- return fmt.Errorf("%w: %s", db.ErrDb, err)
- }
-
- return nil
-}
-
-// GenerateKey is used to generate redis keys.
-func GenerateKey(modelName, pk string) string {
- return fmt.Sprintf("%s-%s", modelName, pk)
-}
diff --git a/internal/db/redis/redis_test.go b/internal/db/redis/redis_test.go
deleted file mode 100644
index 9b883915..00000000
--- a/internal/db/redis/redis_test.go
+++ /dev/null
@@ -1,144 +0,0 @@
-package redis_test
-
-import (
- "context"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/alicebob/miniredis/v2"
- "github.com/go-redis/redis/v8"
- "github.com/stretchr/testify/suite"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- rd "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis"
-)
-
-type RedisModelHandlerSuite struct {
- suite.Suite
-
- Model db.ModelHandler
- DB *redis.Client
-}
-
-func (suite *RedisModelHandlerSuite) SetupSuite() {
- mr, err := miniredis.Run()
- suite.Require().NoError(err)
-
- // nolint: exhaustivestruct
- client := redis.NewClient(&redis.Options{
- Addr: mr.Addr(),
- })
-
- suite.DB = client
- suite.Model = rd.ModelHandler{Client: client}
-}
-
-func (suite *RedisModelHandlerSuite) TestGet() {
- require := suite.Require()
-
- expected := MockModel{Name: "test", Value: ""}
- v, err := json.Marshal(expected)
- require.NoError(err)
-
- require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err())
-
- suite.Run("testing successful get", func() {
- var actual MockModel
- err := suite.Model.Get(context.Background(), "mock", "test", &actual)
- require.Equal(expected, actual)
- require.NoError(err)
- })
-
- suite.Run("testing invalid get usage", func() {
- var actual MockModel
- err := suite.Model.Get(context.Background(), "mock", "test", actual)
- require.Error(err)
- })
-
- suite.Run("testing failed get", func() {
- var actual MockModel
- err := suite.Model.Get(context.Background(), "mock", "t", &actual)
- require.EqualError(err, "redis: nil")
- })
-
- require.NoError(suite.DB.Del(context.Background(), "mock-test").Err())
-}
-
-func (suite *RedisModelHandlerSuite) TestSave() {
- require := suite.Require()
-
- expected := MockModel{Name: "save-test", Value: ""}
-
- require.NoError(suite.Model.Save(context.Background(), expected))
-
- v := suite.DB.Get(context.Background(), "mock-save-test").Val()
-
- var actual MockModel
-
- require.NoError(json.Unmarshal([]byte(v), &actual))
- require.Equal(expected, actual)
-}
-
-func (suite *RedisModelHandlerSuite) TestDelete() {
- require := suite.Require()
-
- expected := MockModel{Name: "test", Value: ""}
- v, _ := json.Marshal(expected)
-
- require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err())
-
- suite.Run("testing successful delete", func() {
- require.NoError(suite.Model.Delete(context.Background(), "mock", "test"))
-
- err := suite.DB.Get(context.Background(), "mock-test").Err()
- require.EqualError(err, "redis: nil")
- })
-
- suite.Run("testing failed delete", func() {
- var actual MockModel
-
- err := suite.Model.Get(context.Background(), "mock", "t", &actual)
- require.EqualError(err, "redis: nil")
- })
-}
-
-func (suite *RedisModelHandlerSuite) TestUpdate() {
- require := suite.Require()
-
- m := MockModel{Name: "test", Value: "test-1"}
- v, err := json.Marshal(m)
- require.NoError(err)
-
- require.NoError(suite.DB.Set(context.Background(), "mock-test", v, 0).Err())
-
- newModel := MockModel{Name: "test", Value: "test-2"}
- require.NoError(suite.Model.Update(context.Background(), newModel))
-
- var updatedModel MockModel
-
- require.NoError(suite.Model.Get(context.Background(), "mock", "test", &updatedModel))
- require.Equal(newModel, updatedModel)
-}
-
-type MockModel struct {
- Name string `json:"name"`
- Value string `json:"value"`
-}
-
-func (m MockModel) GetMetadata() db.MetaData {
- return db.MetaData{
- ModelName: "mock",
- DateCreated: time.Time{},
- DateModified: time.Time{},
- }
-}
-
-func (m MockModel) GetPrimaryKey() string {
- return m.Name
-}
-
-func TestRedisModelHandlerSuite(t *testing.T) {
- t.Parallel()
-
- suite.Run(t, new(RedisModelHandlerSuite))
-}
diff --git a/internal/emq/store.go b/internal/emq/store.go
deleted file mode 100644
index a08adb25..00000000
--- a/internal/emq/store.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package emq
-
-import (
- "context"
- "errors"
- "fmt"
- "strconv"
- "strings"
-
- "github.com/go-redis/redis/v8"
-)
-
-type Store struct {
- Client *redis.Client
-}
-
-var (
- ErrInvalidHResult = errors.New("invalid hresult structure")
- ErrUserNotExists = errors.New("user doesn't exist")
-)
-
-const (
- PasswordHKey = "password"
- IsSuperuserHKey = "is_superuser"
-)
-
-func Key(username string) string {
- return fmt.Sprintf("mqtt_user:%s", username)
-}
-
-func (s *Store) Save(ctx context.Context, u User) error {
- if err := s.Client.HSet(ctx, Key(u.Username), PasswordHKey, u.Password,
- IsSuperuserHKey, strconv.FormatBool(u.IsSuperuser)).Err(); err != nil {
- return fmt.Errorf("failed to save into redis %w", err)
- }
-
- return nil
-}
-
-func (s *Store) Load(ctx context.Context, username string) (User, error) {
- val, err := s.Client.HGetAll(ctx, Key(username)).Result()
- if err != nil {
- return User{}, fmt.Errorf("failed to load from redis %w", err)
- }
-
- if len(val) == 0 {
- return User{}, ErrUserNotExists
- }
-
- password, ok := val[PasswordHKey]
- if !ok {
- return User{}, ErrInvalidHResult
- }
-
- siss, ok := val[IsSuperuserHKey]
- if !ok {
- return User{}, ErrInvalidHResult
- }
-
- iss, err := strconv.ParseBool(siss)
- if err != nil {
- return User{}, fmt.Errorf("invalid is_superuser %w", err)
- }
-
- return User{
- Password: password,
- IsSuperuser: iss,
- Username: username,
- }, nil
-}
-
-func (s *Store) LoadAll(ctx context.Context) ([]User, error) {
- keys, err := s.Client.Keys(ctx, "mqtt_user:*").Result()
- if err != nil {
- return nil, fmt.Errorf("failed to fetch keys from redis %w", err)
- }
-
- users := make([]User, 0)
-
- for _, key := range keys {
- user, err := s.Load(ctx, strings.TrimPrefix(key, "mqtt_user:"))
- if err != nil {
- return nil, fmt.Errorf("failed to fetch user from redis %w", err)
- }
-
- users = append(users, user)
- }
-
- return users, nil
-}
diff --git a/internal/emq/store_test.go b/internal/emq/store_test.go
deleted file mode 100644
index 63df608e..00000000
--- a/internal/emq/store_test.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package emq_test
-
-import (
- "context"
- "fmt"
- "testing"
-
- "github.com/alicebob/miniredis/v2"
- "github.com/go-redis/redis/v8"
- "github.com/stretchr/testify/suite"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq"
-)
-
-type StoreSuite struct {
- suite.Suite
-
- Client *redis.Client
- Store emq.Store
-}
-
-func (suite *StoreSuite) SetupSuite() {
- mr, err := miniredis.Run()
- suite.Require().NoError(err)
-
- // nolint: exhaustivestruct
- client := redis.NewClient(&redis.Options{
- Addr: mr.Addr(),
- })
-
- suite.Client = client
- suite.Store = emq.Store{Client: client}
-}
-
-func (suite *StoreSuite) TearDownTest() {
- suite.Require().NoError(suite.Client.FlushAll(context.Background()).Err())
-}
-
-func (suite *StoreSuite) TestSave() {
- require := suite.Require()
-
- ctx := context.Background()
-
- require.NoError(suite.Store.Save(ctx, emq.User{
- Username: "secret",
- Password: "password",
- IsSuperuser: true,
- }))
-
- ok, err := suite.Client.Exists(ctx, "mqtt_user:secret").Result()
- require.NoError(err)
- require.Equal(int64(1), ok)
-}
-
-func (suite *StoreSuite) TestLoad() {
- require := suite.Require()
-
- ctx := context.Background()
-
- user := emq.User{
- Username: "secret",
- Password: "password",
- IsSuperuser: true,
- }
-
- require.NoError(suite.Store.Save(ctx, user))
-
- u, err := suite.Store.Load(ctx, user.Username)
- require.NoError(err)
- require.Equal(u, user)
-}
-
-func (suite *StoreSuite) TestLoadAll() {
- require := suite.Require()
-
- total := 10
-
- ctx := context.Background()
-
- users := make([]emq.User, 0)
-
- for index := 0; index < total; index++ {
- user := emq.User{
- Username: fmt.Sprintf("secret-%d", index),
- Password: "password",
- IsSuperuser: true,
- }
-
- users = append(users, user)
-
- require.NoError(suite.Store.Save(ctx, user))
- }
-
- us, err := suite.Store.LoadAll(ctx)
- require.NoError(err)
- require.Equal(us, users)
-}
-
-func TestStoreSuite(t *testing.T) {
- t.Parallel()
-
- suite.Run(t, new(StoreSuite))
-}
diff --git a/internal/emq/user.go b/internal/emq/user.go
deleted file mode 100644
index c03f6ab9..00000000
--- a/internal/emq/user.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package emq
-
-// User contains the user information for storing in emq based on its redis-auth plugin.
-// https://github.com/emqx/emqx-auth-redis
-type User struct {
- Username string
- Password string
- IsSuperuser bool
-}
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
new file mode 100644
index 00000000..e474b220
--- /dev/null
+++ b/internal/logger/logger.go
@@ -0,0 +1,42 @@
+package logger
+
+import (
+ "log"
+ "os"
+
+ "go.uber.org/zap"
+ "go.uber.org/zap/zapcore"
+)
+
+type Config struct {
+ Level string `json:"level,omitempty" koanf:"level"`
+ Stacktrace bool `json:"stacktrace,omitempty" koanf:"stacktrace"`
+}
+
+// New creates a zap logger for console.
+func New(cfg Config) *zap.Logger {
+ var lvl zapcore.Level
+ if err := lvl.Set(cfg.Level); err != nil {
+ log.Printf("cannot parse log level %s: %s", cfg.Level, err)
+
+ lvl = zapcore.WarnLevel
+ }
+
+ encoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
+ defaultCore := zapcore.NewCore(encoder, zapcore.Lock(zapcore.AddSync(os.Stderr)), lvl)
+ cores := []zapcore.Core{
+ defaultCore,
+ }
+
+ core := zapcore.NewTee(cores...)
+ zapOpts := make([]zap.Option, 0, 2) // nolint:mnd
+ zapOpts = append(zapOpts, zap.AddCaller())
+
+ if cfg.Stacktrace {
+ zapOpts = append(zapOpts, zap.AddStacktrace(zap.ErrorLevel))
+ }
+
+ logger := zap.New(core, zapOpts...)
+
+ return logger
+}
diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go
deleted file mode 100644
index 099b1ef4..00000000
--- a/internal/metrics/metrics.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package metrics
-
-import (
- "github.com/prometheus/client_golang/prometheus"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/metrics"
-)
-
-type SoteriaMetrics struct {
- Handler metrics.Handler
-}
-
-// NewMetrics creates and returns all metrics needed in Soteria
-func NewMetrics() *SoteriaMetrics {
- statusCodesCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
- Namespace: "dispatching",
- Subsystem: "soteria",
- Name: "status_codes",
- Help: "status codes observed from soteria and its all external calls",
- }, []string{"api", "service", "function", "code"})
-
- statusesCounter := prometheus.NewCounterVec(prometheus.CounterOpts{
- Namespace: "dispatching",
- Subsystem: "soteria",
- Name: "statuses",
- Help: "statuses observed from soteria and its all external calls",
- }, []string{"api", "service", "function", "status", "info"})
-
- responseTimesSummery := prometheus.NewSummaryVec(prometheus.SummaryOpts{
- Namespace: "dispatching",
- Subsystem: "soteria",
- Name: "response_times",
- Help: "response times observed from and its all external calls",
- }, []string{"api", "service", "function"})
-
- prometheus.MustRegister(statusCodesCounter)
- prometheus.MustRegister(statusesCounter)
- prometheus.MustRegister(responseTimesSummery)
-
- h := metrics.Handler{
- StatusCodeCounterVec: statusCodesCounter,
- StatusCounterVec: statusesCounter,
- ResponseTimeVec: responseTimesSummery,
- }
-
- return &SoteriaMetrics{h}
-}
diff --git a/internal/topics/config.go b/internal/topics/config.go
new file mode 100644
index 00000000..148422f4
--- /dev/null
+++ b/internal/topics/config.go
@@ -0,0 +1,7 @@
+package topics
+
+type HashData struct {
+ Length int `json:"length,omitempty" koanf:"length"`
+ Salt string `json:"salt,omitempty" koanf:"salt"`
+ Alphabet string `json:"alphabet,omitempty" koanf:"alphabet"`
+}
diff --git a/internal/topics/manager.go b/internal/topics/manager.go
new file mode 100644
index 00000000..092dd3ae
--- /dev/null
+++ b/internal/topics/manager.go
@@ -0,0 +1,209 @@
+// Package topics regular expressions which are used for detecting the topic name.
+// topics are prefix with the company name will be trimed before matching
+// so they regular expressions should not contain the company prefix.
+package topics
+
+import (
+ "crypto/md5" //nolint: gosec
+ "encoding/hex"
+ "fmt"
+ "regexp"
+ "strconv"
+ "strings"
+ "text/template"
+
+ "github.com/speps/go-hashids/v2"
+ "go.uber.org/zap"
+)
+
+const (
+ CabEvent string = "cab_event"
+ DriverLocation string = "driver_location"
+ PassengerLocation string = "passenger_location"
+ SuperappEvent string = "superapp_event"
+ BoxEvent string = "box_event"
+ SharedLocation string = "shared_location"
+ Chat string = "chat"
+ GeneralCallEntry string = "general_call_entry"
+ NodeCallEntry string = "node_call_entry"
+ CallOutgoing string = "call_outgoing"
+)
+
+const (
+ Driver string = "driver"
+ DriverIss string = "0"
+
+ Passenger string = "passenger"
+ PassengerIss string = "1"
+
+ NoneIss string = "-1"
+)
+
+const (
+ // EmqCabHashPrefix is the default prefix for hashing part of cab topic, default value is 'emqch'.
+ EmqCabHashPrefix = "emqch"
+
+ Default = "default"
+)
+
+type Manager struct {
+ HashIDSManager map[string]*hashids.HashID
+ Company string
+ TopicTemplates []Template
+ IssEntityMap map[string]string
+ IssPeerMap map[string]string
+ Functions template.FuncMap
+ Logger *zap.Logger
+}
+
+// NewTopicManager returns a topic manager to validate topics.
+func NewTopicManager(
+ topicList []Topic,
+ hashIDManager map[string]*hashids.HashID,
+ company string,
+ issEntityMap, issPeerMap map[string]string,
+ logger *zap.Logger,
+) *Manager {
+ manager := &Manager{ //nolint: exhaustruct
+ HashIDSManager: hashIDManager,
+ Company: company,
+ IssEntityMap: issEntityMap,
+ IssPeerMap: issPeerMap,
+ Logger: logger,
+ }
+
+ manager.Functions = template.FuncMap{
+ "IssToEntity": manager.IssEntityMapper,
+ "DecodeHashID": manager.DecodeHashID,
+ "EncodeHashID": manager.EncodeHashID,
+ "EncodeMD5": manager.EncodeMD5,
+ "IssToPeer": manager.IssPeerMapper,
+ }
+
+ templates := make([]Template, 0)
+
+ for _, topic := range topicList {
+ each := Template{
+ Type: topic.Type,
+ Template: template.Must(template.New(topic.Type).Funcs(manager.Functions).Parse(topic.Template)),
+ Accesses: topic.Accesses,
+ }
+ templates = append(templates, each)
+ }
+
+ manager.TopicTemplates = templates
+
+ return manager
+}
+
+// ParseTopic checks if a topic is valid based on the given parameters.
+func (t *Manager) ParseTopic(topic, iss, sub string, claims map[string]any) *Template {
+ fields := make(map[string]string)
+
+ for k, v := range claims {
+ fields[k] = fmt.Sprintf("%v", v)
+ }
+
+ fields["iss"] = iss
+ fields["company"] = t.Company
+ fields["sub"] = sub
+
+ for _, topicTemplate := range t.TopicTemplates {
+ regex := new(strings.Builder)
+
+ if err := topicTemplate.Template.Execute(regex, fields); err != nil {
+ t.Logger.Error("template execution failed", zap.Error(err), zap.String("template", topicTemplate.Type))
+
+ return nil
+ }
+
+ t.Logger.Debug("topic template generated",
+ zap.String("topic", regex.String()),
+ zap.String("iss", iss),
+ zap.String("sub", sub),
+ zap.String("company", t.Company),
+ )
+
+ if regexp.MustCompile(regex.String()).MatchString(topic) {
+ return &topicTemplate
+ }
+ }
+
+ return nil
+}
+
+func (t *Manager) EncodeMD5(iss string) string {
+ hid := md5.Sum([]byte(fmt.Sprintf("%s-%s", EmqCabHashPrefix, iss))) //nolint:gosec
+
+ return hex.EncodeToString(hid[:])
+}
+
+func (t *Manager) DecodeHashID(sub, iss string) string {
+ id, err := t.HashIDSManager[iss].DecodeWithError(sub)
+ if err != nil {
+ t.Logger.Error("decoding sub failed", zap.Error(err), zap.String("sub", sub))
+
+ return ""
+ }
+
+ return strconv.Itoa(id[0])
+}
+
+func (t *Manager) EncodeHashID(sub, iss string) string {
+ subInt, err := strconv.Atoi(sub)
+ if err != nil {
+ t.Logger.Error("encoding sub failed", zap.Error(err), zap.String("sub", sub))
+
+ return ""
+ }
+
+ id, err := t.HashIDSManager[iss].Encode([]int{subInt})
+ if err != nil {
+ t.Logger.Error("encoding sub failed", zap.Error(err), zap.String("sub", sub))
+
+ return ""
+ }
+
+ return id
+}
+
+func (t *Manager) IssEntityMapper(iss string) string {
+ result, ok := t.IssEntityMap[iss]
+ if ok {
+ return result
+ }
+
+ return t.IssEntityMap[Default]
+}
+
+func (t *Manager) IssPeerMapper(iss string) string {
+ result, ok := t.IssPeerMap[iss]
+ if ok {
+ return result
+ }
+
+ return t.IssPeerMap[Default]
+}
+
+func NewHashIDManager(hidmap map[string]HashData) (map[string]*hashids.HashID, error) {
+ hid := make(map[string]*hashids.HashID)
+
+ for iss, data := range hidmap {
+ var err error
+
+ hd := hashids.NewData()
+ hd.Salt = data.Salt
+ hd.MinLength = data.Length
+
+ if data.Alphabet != "" {
+ hd.Alphabet = data.Alphabet
+ }
+
+ hid[iss], err = hashids.NewWithData(hd)
+ if err != nil {
+ return nil, fmt.Errorf("cannot create hashid enc/dec %w", err)
+ }
+ }
+
+ return hid, nil
+}
diff --git a/internal/topics/manager_test.go b/internal/topics/manager_test.go
new file mode 100644
index 00000000..dd390f47
--- /dev/null
+++ b/internal/topics/manager_test.go
@@ -0,0 +1,163 @@
+package topics_test
+
+import (
+ "testing"
+
+ "github.com/snapp-incubator/soteria/internal/config"
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "go.uber.org/zap"
+)
+
+// nolint: funlen
+func TestTopicManager(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ arg string
+ issuer string
+ want string
+ }{
+ {
+ name: "testing cab event",
+ arg: "passenger-event-152384980615c2bd16143cff29038b67",
+ issuer: topics.PassengerIss,
+ want: topics.CabEvent,
+ },
+ {
+ name: "testing cab event",
+ arg: "driver-event-152384980615c2bd16143cff29038b67",
+ issuer: topics.DriverIss,
+ want: topics.CabEvent,
+ },
+ {
+ name: "testing invalid event",
+ arg: "-event-123456789abcdefgABCDEFG",
+ issuer: topics.NoneIss,
+ want: "",
+ },
+ {
+ name: "testing driver location",
+ arg: "snapp/driver/DXKgaNQa7N5Y7bo/location",
+ issuer: topics.DriverIss,
+ want: topics.DriverLocation,
+ },
+ {
+ name: "testing passenger location",
+ arg: "snapp/passenger/DXKgaNQa7N5Y7bo/location",
+ issuer: topics.PassengerIss,
+ want: topics.PassengerLocation,
+ },
+ {
+ name: "testing invalid location",
+ arg: "snapp/thirdparty/DXKgaNQa7N5Y7bo/location",
+ issuer: topics.NoneIss,
+ want: "",
+ },
+ {
+ name: "testing superapp event",
+ arg: "snapp/passenger/DXKgaNQa7N5Y7bo/superapp",
+ issuer: topics.PassengerIss,
+ want: topics.SuperappEvent,
+ },
+ {
+ name: "testing shared passenger location",
+ arg: "snapp/passenger/DXKgaNQa7N5Y7bo/driver-location",
+ issuer: topics.PassengerIss,
+ want: topics.SharedLocation,
+ },
+ {
+ name: "testing shared driver location",
+ arg: "snapp/driver/DXKgaNQa7N5Y7bo/passenger-location",
+ issuer: topics.DriverIss,
+ want: topics.SharedLocation,
+ },
+ {
+ name: "testing passenger chat",
+ arg: "snapp/passenger/DXKgaNQa7N5Y7bo/chat",
+ issuer: topics.PassengerIss,
+ want: topics.Chat,
+ },
+ {
+ name: "testing driver chat",
+ arg: "snapp/driver/DXKgaNQa7N5Y7bo/chat",
+ issuer: topics.DriverIss,
+ want: topics.Chat,
+ },
+ {
+ name: "testing passenger general call entry",
+ arg: "shared/snapp/passenger/DXKgaNQa7N5Y7bo/call/send",
+ issuer: topics.PassengerIss,
+ want: topics.GeneralCallEntry,
+ },
+ {
+ name: "testing driver general call entry",
+ arg: "shared/snapp/driver/DXKgaNQa7N5Y7bo/call/send",
+ issuer: topics.DriverIss,
+ want: topics.GeneralCallEntry,
+ },
+ {
+ name: "testing passenger node call entry",
+ arg: "snapp/passenger/DXKgaNQa7N5Y7bo/call/heliograph-0/send",
+ issuer: topics.PassengerIss,
+ want: topics.NodeCallEntry,
+ },
+ {
+ name: "testing driver node call entry",
+ arg: "snapp/driver/DXKgaNQa7N5Y7bo/call/heliograph-1/send",
+ issuer: topics.DriverIss,
+ want: topics.NodeCallEntry,
+ },
+ {
+ name: "testing passenger call",
+ arg: "snapp/passenger/DXKgaNQa7N5Y7bo/call/receive",
+ issuer: topics.PassengerIss,
+ want: topics.CallOutgoing,
+ },
+ {
+ name: "testing driver call",
+ arg: "snapp/driver/DXKgaNQa7N5Y7bo/call/receive",
+ issuer: topics.DriverIss,
+ want: topics.CallOutgoing,
+ },
+ {
+ name: "testing box event",
+ arg: "bucks",
+ issuer: topics.DriverIss,
+ want: topics.BoxEvent,
+ },
+ }
+
+ cfg := config.SnappVendor()
+
+ hid, err := topics.NewHashIDManager(cfg.HashIDMap)
+ if err != nil {
+ t.Errorf("invalid default hash-id: %s", err)
+ }
+
+ // nolint: exhaustruct
+ topicManager := topics.NewTopicManager(cfg.Topics, hid, "snapp", cfg.IssEntityMap, cfg.IssPeerMap, zap.NewNop())
+
+ sub := "DXKgaNQa7N5Y7bo"
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ topic := tc.arg
+
+ topicTemplate := topicManager.ParseTopic(topic, tc.issuer, sub, nil)
+ if topicTemplate != nil {
+ if len(tc.want) == 0 {
+ t.Errorf("topic %s is invalid, must throw error.", tc.arg)
+ } else if topicTemplate.Type != tc.want {
+ t.Errorf("GetType() = %v, want %v", topicTemplate.Type, tc.want)
+ }
+ } else {
+ if len(tc.want) != 0 {
+ t.Errorf("failed to find topicTemplate for %s", tc.arg)
+ }
+ }
+ })
+ }
+}
diff --git a/internal/topics/t.go b/internal/topics/t.go
deleted file mode 100644
index 8db908a5..00000000
--- a/internal/topics/t.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package topics
-
-import (
- "regexp"
- "strings"
-)
-
-type Topic string
-
-type Type string
-
-const (
- CabEvent Type = "cab_event"
- DriverLocation Type = "driver_location"
- PassengerLocation Type = "passenger_location"
- SuperappEvent Type = "superapp_event"
- BoxEvent Type = "box_event"
- DaghighSys Type = "daghigh_sys"
- SharedLocation Type = "shared_location"
- Chat Type = "chat"
-)
-
-// Topic regular expressions which are used for detecting the topic name.
-// topics are prefix with the company name will be trimed before matching
-// so they regular expressions should not contain the company prefix.
-var (
- CabEventRegexp = regexp.MustCompile(`(\w+)-event-[a-zA-Z0-9]+`)
- DriverLocationRegexp = regexp.MustCompile(`/driver/[a-zA-Z0-9+]+/location`)
- PassengerLocationRegexp = regexp.MustCompile(`/passenger/[a-zA-Z0-9+]+/location`)
- SuperappEventRegexp = regexp.MustCompile(`/(driver|passenger)/[a-zA-Z0-9]+/(superapp)`)
- SharedLocationRegexp = regexp.MustCompile(`/(driver|passenger)+/[a-zA-Z0-9]+/(driver-location|passenger-location)`)
- DaghighSysRegexp = regexp.MustCompile(`\$SYS/brokers/\+/clients/\+/(connected|disconnected)`)
- ChatRegexp = regexp.MustCompile(`/(driver|passenger)+/[a-zA-Z0-9]+/chat`)
-)
-
-func (t Topic) GetType() Type {
- return t.GetTypeWithCompany("snapp")
-}
-
-func (t Topic) GetTypeWithCompany(company string) Type {
- topic := string(t)
- topic = strings.TrimPrefix(topic, company)
-
- switch {
- case CabEventRegexp.MatchString(topic):
- return CabEvent
- case DriverLocationRegexp.MatchString(topic):
- return DriverLocation
- case PassengerLocationRegexp.MatchString(topic):
- return PassengerLocation
- case SharedLocationRegexp.MatchString(topic):
- return SharedLocation
- case ChatRegexp.MatchString(topic):
- return Chat
- case SuperappEventRegexp.MatchString(topic):
- return SuperappEvent
- case topic == "bucks":
- return BoxEvent
- case DaghighSysRegexp.MatchString(topic):
- return DaghighSys
- }
-
- return ""
-}
diff --git a/internal/topics/t_test.go b/internal/topics/t_test.go
deleted file mode 100644
index c37f25e7..00000000
--- a/internal/topics/t_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package topics
-
-import (
- "fmt"
- "testing"
-)
-
-func TestTopic_GetType(t1 *testing.T) {
- tests := []struct {
- name string
- arg Topic
- want Type
- }{
- {
- name: "testing cab event",
- arg: Topic("passenger-event-123456789abcdefgABCDEFG"),
- want: CabEvent,
- },
- {
- name: "testing cab event",
- arg: "driver-event-123456789abcdefgABCDEFG",
- want: CabEvent,
- },
- {
- name: "testing invalid event",
- arg: "-event-123456789abcdefgABCDEFG",
- want: "",
- },
- {
- name: "testing driver location",
- arg: "snapp/driver/sfhsdkifs475sfhs/location",
- want: DriverLocation,
- },
- {
- name: "testing passenger location",
- arg: "snapp/passenger/sfhsdkifs475sfhs/location",
- want: PassengerLocation,
- },
- {
- name: "testing invalid location",
- arg: "snapp/thirdparty/sfhsdkifs475sfhs/location",
- want: "",
- },
- {
- name: "testing superapp event",
- arg: "snapp/passenger/fhdyfuiksdf5456456adljada/superapp",
- want: SuperappEvent,
- },
- {
- name: "testing superapp event",
- arg: "snapp/driver/+/location",
- want: DriverLocation,
- },
- {
- name: "testing daghigh sys",
- arg: "$SYS/brokers/+/clients/+/disconnected",
- want: DaghighSys,
- },
- {
- name: "testing daghigh sys",
- arg: "$SYS/brokers/+/clients/+/connected",
- want: DaghighSys,
- },
- {
- name: "testing daghigh sys",
- arg: "$share/hello/$SYS/brokers/+/clients/+/connected",
- want: DaghighSys,
- },
- {
- name: "testing shared passenger location",
- arg: "snapp/passenger/py9kdjLYB35RP4q/driver-location",
- want: SharedLocation,
- },
- {
- name: "testing shared driver location",
- arg: "snapp/driver/py9kdjLYB35RP4q/passenger-location",
- want: SharedLocation,
- },
- {
- name: "testing passenger chat",
- arg: "snapp/passenger/py9kdjLYB35RP4q/chat",
- want: Chat,
- },
- {
- name: "testing driver chat",
- arg: "snapp/driver/py9kdjLYB35RP4q/chat",
- want: Chat,
- },
- }
- for i, tt := range tests {
- t1.Run(fmt.Sprintf("#%d %s", i, tt.name), func(t1 *testing.T) {
- t := tt.arg
- if got := t.GetType(); got != tt.want {
- t1.Errorf("GetType() = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/internal/topics/topic.go b/internal/topics/topic.go
new file mode 100644
index 00000000..22ea5e36
--- /dev/null
+++ b/internal/topics/topic.go
@@ -0,0 +1,37 @@
+package topics
+
+import (
+ "strings"
+ "text/template"
+
+ "github.com/snapp-incubator/soteria/pkg/acl"
+)
+
+type Topic struct {
+ Type string `json:"type,omitempty" koanf:"type"`
+ Template string `json:"template,omitempty" koanf:"template"`
+ Accesses map[string]acl.AccessType `json:"accesses,omitempty" koanf:"accesses"`
+}
+
+type Template struct {
+ Type string
+ Template *template.Template
+ Accesses map[string]acl.AccessType
+}
+
+func (t Template) Parse(fields map[string]string) string {
+ writer := new(strings.Builder)
+
+ if err := t.Template.Execute(writer, fields); err != nil {
+ return ""
+ }
+
+ return writer.String()
+}
+
+// HasAccess check if user has access on topic.
+func (t Template) HasAccess(iss string, accessType acl.AccessType) bool {
+ access := t.Accesses[iss]
+
+ return access == acl.PubSub || access == accessType
+}
diff --git a/internal/topics/topic_test.go b/internal/topics/topic_test.go
new file mode 100644
index 00000000..d4c36243
--- /dev/null
+++ b/internal/topics/topic_test.go
@@ -0,0 +1,37 @@
+package topics_test
+
+import (
+ "testing"
+ "text/template"
+
+ "github.com/snapp-incubator/soteria/internal/topics"
+ "github.com/snapp-incubator/soteria/pkg/acl"
+ "github.com/stretchr/testify/require"
+)
+
+func TestTopic(t *testing.T) {
+ t.Parallel()
+
+ require := require.New(t)
+
+ topic := topics.Topic{
+ Type: topics.CabEvent,
+ Template: "^{{.iss}}-event-$",
+ Accesses: map[string]acl.AccessType{
+ topics.DriverIss: acl.Sub,
+ topics.PassengerIss: acl.Sub,
+ },
+ }
+
+ temp := topics.Template{
+ Type: topic.Type,
+ Template: template.Must(template.New("").Parse(topic.Template)),
+ Accesses: topic.Accesses,
+ }
+
+ s := temp.Parse(map[string]string{
+ "iss": "passenger",
+ })
+
+ require.Equal("^passenger-event-$", s)
+}
diff --git a/internal/tracing/config.go b/internal/tracing/config.go
new file mode 100644
index 00000000..0552396d
--- /dev/null
+++ b/internal/tracing/config.go
@@ -0,0 +1,7 @@
+package tracing
+
+type Config struct {
+ Enabled bool `json:"enabled,omitempty" koanf:"enabled"`
+ Endpoint string `json:"endpoint,omitempty" koanf:"endpoint"`
+ Ratio float64 `json:"ratio,omitempty" koanf:"ratio"`
+}
diff --git a/internal/tracing/tracer.go b/internal/tracing/tracer.go
new file mode 100644
index 00000000..a009cc37
--- /dev/null
+++ b/internal/tracing/tracer.go
@@ -0,0 +1,58 @@
+package tracing
+
+import (
+ "context"
+
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
+ "go.opentelemetry.io/otel/propagation"
+ "go.opentelemetry.io/otel/sdk/resource"
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
+ "go.opentelemetry.io/otel/trace"
+ "go.opentelemetry.io/otel/trace/noop"
+ "go.uber.org/zap"
+)
+
+func New(cfg Config, logger *zap.Logger) trace.Tracer {
+ if !cfg.Enabled {
+ return noop.NewTracerProvider().Tracer("snapp.dispatching")
+ }
+
+ exporter, err := otlptracegrpc.New(
+ context.Background(),
+ otlptracegrpc.WithEndpoint(cfg.Endpoint), otlptracegrpc.WithInsecure(),
+ )
+ if err != nil {
+ logger.Fatal("failed to initialize export pipeline for traces (otlp with grpc)", zap.Error(err))
+ }
+
+ res, err := resource.Merge(
+ resource.Default(),
+ resource.NewSchemaless(
+ semconv.ServiceNamespaceKey.String("snapp.dispatching"),
+ semconv.ServiceNameKey.String("soteria"),
+ ),
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ bsp := sdktrace.NewBatchSpanProcessor(exporter)
+ tp := sdktrace.NewTracerProvider(
+ sdktrace.WithSampler(sdktrace.ParentBased(sdktrace.TraceIDRatioBased(cfg.Ratio))),
+ sdktrace.WithSpanProcessor(bsp),
+ sdktrace.WithResource(res),
+ )
+
+ otel.SetTracerProvider(tp)
+
+ // register the TraceContext propagator globally.
+ var tc propagation.TraceContext
+
+ otel.SetTextMapPropagator(tc)
+
+ tracer := otel.Tracer("dispatching/soteria")
+
+ return tracer
+}
diff --git a/internal/web/grpc/contracts/auth.pb.go b/internal/web/grpc/contracts/auth.pb.go
deleted file mode 100644
index cffa4e88..00000000
--- a/internal/web/grpc/contracts/auth.pb.go
+++ /dev/null
@@ -1,402 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// source: auth.proto
-
-package contracts
-
-import (
- context "context"
- fmt "fmt"
- proto "github.com/golang/protobuf/proto"
- grpc "google.golang.org/grpc"
- codes "google.golang.org/grpc/codes"
- status "google.golang.org/grpc/status"
- math "math"
-)
-
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
-
-type AuthContract struct {
- ServiceName string `protobuf:"bytes,1,opt,name=ServiceName,proto3" json:"ServiceName,omitempty"`
- IPAddress string `protobuf:"bytes,2,opt,name=IPAddress,proto3" json:"IPAddress,omitempty"`
- Endpoint string `protobuf:"bytes,3,opt,name=Endpoint,proto3" json:"Endpoint,omitempty"`
- Username string `protobuf:"bytes,4,opt,name=Username,proto3" json:"Username,omitempty"`
- Password string `protobuf:"bytes,5,opt,name=Password,proto3" json:"Password,omitempty"`
- Token string `protobuf:"bytes,6,opt,name=Token,proto3" json:"Token,omitempty"`
- Topic string `protobuf:"bytes,7,opt,name=Topic,proto3" json:"Topic,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *AuthContract) Reset() { *m = AuthContract{} }
-func (m *AuthContract) String() string { return proto.CompactTextString(m) }
-func (*AuthContract) ProtoMessage() {}
-func (*AuthContract) Descriptor() ([]byte, []int) {
- return fileDescriptor_8bbd6f3875b0e874, []int{0}
-}
-
-func (m *AuthContract) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_AuthContract.Unmarshal(m, b)
-}
-func (m *AuthContract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_AuthContract.Marshal(b, m, deterministic)
-}
-func (m *AuthContract) XXX_Merge(src proto.Message) {
- xxx_messageInfo_AuthContract.Merge(m, src)
-}
-func (m *AuthContract) XXX_Size() int {
- return xxx_messageInfo_AuthContract.Size(m)
-}
-func (m *AuthContract) XXX_DiscardUnknown() {
- xxx_messageInfo_AuthContract.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_AuthContract proto.InternalMessageInfo
-
-func (m *AuthContract) GetServiceName() string {
- if m != nil {
- return m.ServiceName
- }
- return ""
-}
-
-func (m *AuthContract) GetIPAddress() string {
- if m != nil {
- return m.IPAddress
- }
- return ""
-}
-
-func (m *AuthContract) GetEndpoint() string {
- if m != nil {
- return m.Endpoint
- }
- return ""
-}
-
-func (m *AuthContract) GetUsername() string {
- if m != nil {
- return m.Username
- }
- return ""
-}
-
-func (m *AuthContract) GetPassword() string {
- if m != nil {
- return m.Password
- }
- return ""
-}
-
-func (m *AuthContract) GetToken() string {
- if m != nil {
- return m.Token
- }
- return ""
-}
-
-func (m *AuthContract) GetTopic() string {
- if m != nil {
- return m.Topic
- }
- return ""
-}
-
-type ServiceResponse struct {
- Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *ServiceResponse) Reset() { *m = ServiceResponse{} }
-func (m *ServiceResponse) String() string { return proto.CompactTextString(m) }
-func (*ServiceResponse) ProtoMessage() {}
-func (*ServiceResponse) Descriptor() ([]byte, []int) {
- return fileDescriptor_8bbd6f3875b0e874, []int{1}
-}
-
-func (m *ServiceResponse) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_ServiceResponse.Unmarshal(m, b)
-}
-func (m *ServiceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_ServiceResponse.Marshal(b, m, deterministic)
-}
-func (m *ServiceResponse) XXX_Merge(src proto.Message) {
- xxx_messageInfo_ServiceResponse.Merge(m, src)
-}
-func (m *ServiceResponse) XXX_Size() int {
- return xxx_messageInfo_ServiceResponse.Size(m)
-}
-func (m *ServiceResponse) XXX_DiscardUnknown() {
- xxx_messageInfo_ServiceResponse.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_ServiceResponse proto.InternalMessageInfo
-
-func (m *ServiceResponse) GetCode() int32 {
- if m != nil {
- return m.Code
- }
- return 0
-}
-
-type GetTokenContract struct {
- GrantType string `protobuf:"bytes,1,opt,name=GrantType,proto3" json:"GrantType,omitempty"`
- ClientID string `protobuf:"bytes,2,opt,name=ClientID,proto3" json:"ClientID,omitempty"`
- ClientSecret string `protobuf:"bytes,3,opt,name=ClientSecret,proto3" json:"ClientSecret,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *GetTokenContract) Reset() { *m = GetTokenContract{} }
-func (m *GetTokenContract) String() string { return proto.CompactTextString(m) }
-func (*GetTokenContract) ProtoMessage() {}
-func (*GetTokenContract) Descriptor() ([]byte, []int) {
- return fileDescriptor_8bbd6f3875b0e874, []int{2}
-}
-
-func (m *GetTokenContract) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_GetTokenContract.Unmarshal(m, b)
-}
-func (m *GetTokenContract) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_GetTokenContract.Marshal(b, m, deterministic)
-}
-func (m *GetTokenContract) XXX_Merge(src proto.Message) {
- xxx_messageInfo_GetTokenContract.Merge(m, src)
-}
-func (m *GetTokenContract) XXX_Size() int {
- return xxx_messageInfo_GetTokenContract.Size(m)
-}
-func (m *GetTokenContract) XXX_DiscardUnknown() {
- xxx_messageInfo_GetTokenContract.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_GetTokenContract proto.InternalMessageInfo
-
-func (m *GetTokenContract) GetGrantType() string {
- if m != nil {
- return m.GrantType
- }
- return ""
-}
-
-func (m *GetTokenContract) GetClientID() string {
- if m != nil {
- return m.ClientID
- }
- return ""
-}
-
-func (m *GetTokenContract) GetClientSecret() string {
- if m != nil {
- return m.ClientSecret
- }
- return ""
-}
-
-type GetTokenResponse struct {
- Code int32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`
- Token string `protobuf:"bytes,2,opt,name=Token,proto3" json:"Token,omitempty"`
- XXX_NoUnkeyedLiteral struct{} `json:"-"`
- XXX_unrecognized []byte `json:"-"`
- XXX_sizecache int32 `json:"-"`
-}
-
-func (m *GetTokenResponse) Reset() { *m = GetTokenResponse{} }
-func (m *GetTokenResponse) String() string { return proto.CompactTextString(m) }
-func (*GetTokenResponse) ProtoMessage() {}
-func (*GetTokenResponse) Descriptor() ([]byte, []int) {
- return fileDescriptor_8bbd6f3875b0e874, []int{3}
-}
-
-func (m *GetTokenResponse) XXX_Unmarshal(b []byte) error {
- return xxx_messageInfo_GetTokenResponse.Unmarshal(m, b)
-}
-func (m *GetTokenResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
- return xxx_messageInfo_GetTokenResponse.Marshal(b, m, deterministic)
-}
-func (m *GetTokenResponse) XXX_Merge(src proto.Message) {
- xxx_messageInfo_GetTokenResponse.Merge(m, src)
-}
-func (m *GetTokenResponse) XXX_Size() int {
- return xxx_messageInfo_GetTokenResponse.Size(m)
-}
-func (m *GetTokenResponse) XXX_DiscardUnknown() {
- xxx_messageInfo_GetTokenResponse.DiscardUnknown(m)
-}
-
-var xxx_messageInfo_GetTokenResponse proto.InternalMessageInfo
-
-func (m *GetTokenResponse) GetCode() int32 {
- if m != nil {
- return m.Code
- }
- return 0
-}
-
-func (m *GetTokenResponse) GetToken() string {
- if m != nil {
- return m.Token
- }
- return ""
-}
-
-func init() {
- proto.RegisterType((*AuthContract)(nil), "contracts.AuthContract")
- proto.RegisterType((*ServiceResponse)(nil), "contracts.ServiceResponse")
- proto.RegisterType((*GetTokenContract)(nil), "contracts.GetTokenContract")
- proto.RegisterType((*GetTokenResponse)(nil), "contracts.GetTokenResponse")
-}
-
-func init() { proto.RegisterFile("auth.proto", fileDescriptor_8bbd6f3875b0e874) }
-
-var fileDescriptor_8bbd6f3875b0e874 = []byte{
- // 312 bytes of a gzipped FileDescriptorProto
- 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x92, 0xd1, 0x4a, 0xfb, 0x30,
- 0x14, 0xc6, 0xff, 0xdd, 0x7f, 0x9b, 0xeb, 0x71, 0xa0, 0x04, 0xc1, 0x50, 0x77, 0x31, 0x02, 0x82,
- 0x57, 0xbb, 0xd0, 0x5b, 0xbd, 0x18, 0x55, 0xc7, 0x6e, 0x64, 0x74, 0xf3, 0x01, 0x6a, 0x7a, 0x60,
- 0x45, 0x4d, 0x42, 0x92, 0x29, 0x3e, 0x84, 0xaf, 0xe6, 0x33, 0x49, 0x93, 0xa6, 0xad, 0x43, 0xbc,
- 0x6a, 0xbe, 0xef, 0x57, 0x4e, 0xbf, 0x2f, 0xa7, 0x00, 0xf9, 0xce, 0x6e, 0x67, 0x4a, 0x4b, 0x2b,
- 0x49, 0xcc, 0xa5, 0xb0, 0x3a, 0xe7, 0xd6, 0xb0, 0xaf, 0x08, 0xc6, 0xf3, 0x9d, 0xdd, 0xa6, 0xb5,
- 0x43, 0xa6, 0x70, 0xb8, 0x46, 0xfd, 0x56, 0x72, 0x7c, 0xc8, 0x5f, 0x91, 0x46, 0xd3, 0xe8, 0x22,
- 0xce, 0xba, 0x16, 0x99, 0x40, 0xbc, 0x5c, 0xcd, 0x8b, 0x42, 0xa3, 0x31, 0xb4, 0xe7, 0x78, 0x6b,
- 0x90, 0x04, 0x46, 0x77, 0xa2, 0x50, 0xb2, 0x14, 0x96, 0xfe, 0x77, 0xb0, 0xd1, 0x15, 0x7b, 0x34,
- 0xa8, 0x45, 0x35, 0xb8, 0xef, 0x59, 0xd0, 0x15, 0x5b, 0xe5, 0xc6, 0xbc, 0x4b, 0x5d, 0xd0, 0x81,
- 0x67, 0x41, 0x93, 0x13, 0x18, 0x6c, 0xe4, 0x33, 0x0a, 0x3a, 0x74, 0xc0, 0x0b, 0xef, 0xaa, 0x92,
- 0xd3, 0x83, 0xe0, 0xaa, 0x92, 0xb3, 0x73, 0x38, 0xaa, 0xc3, 0x66, 0x68, 0x94, 0x14, 0x06, 0x09,
- 0x81, 0x7e, 0x2a, 0x0b, 0xdf, 0x65, 0x90, 0xb9, 0x33, 0x53, 0x70, 0xbc, 0x40, 0xeb, 0x06, 0x35,
- 0xd5, 0x27, 0x10, 0x2f, 0x74, 0x2e, 0xec, 0xe6, 0x43, 0x85, 0xe2, 0xad, 0x51, 0x05, 0x4c, 0x5f,
- 0x4a, 0x14, 0x76, 0x79, 0x5b, 0xb7, 0x6e, 0x34, 0x61, 0x30, 0xf6, 0xe7, 0x35, 0x72, 0x8d, 0xa1,
- 0xf8, 0x0f, 0x8f, 0x5d, 0xb7, 0x5f, 0xfc, 0x2b, 0x59, 0x5b, 0xb6, 0xd7, 0x29, 0x7b, 0xf9, 0x19,
- 0x41, 0xbf, 0xda, 0x13, 0xb9, 0xa9, 0x9f, 0xa7, 0xb3, 0x66, 0x89, 0xb3, 0xee, 0x02, 0x93, 0xa4,
- 0x03, 0xf6, 0x6e, 0x82, 0xfd, 0x23, 0xf7, 0x30, 0x0a, 0x29, 0xc8, 0x59, 0xe7, 0xcd, 0xfd, 0xcb,
- 0x48, 0x7e, 0x83, 0xed, 0x9c, 0xa7, 0xa1, 0xfb, 0x93, 0xae, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff,
- 0x00, 0x55, 0xf1, 0x67, 0x57, 0x02, 0x00, 0x00,
-}
-
-// Reference imports to suppress errors if they are not otherwise used.
-var _ context.Context
-var _ grpc.ClientConn
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the grpc package it is being compiled against.
-const _ = grpc.SupportPackageIsVersion4
-
-// AuthClient is the client API for Auth service.
-//
-// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
-type AuthClient interface {
- Auth(ctx context.Context, in *AuthContract, opts ...grpc.CallOption) (*ServiceResponse, error)
- GetToken(ctx context.Context, in *GetTokenContract, opts ...grpc.CallOption) (*GetTokenResponse, error)
-}
-
-type authClient struct {
- cc *grpc.ClientConn
-}
-
-func NewAuthClient(cc *grpc.ClientConn) AuthClient {
- return &authClient{cc}
-}
-
-func (c *authClient) Auth(ctx context.Context, in *AuthContract, opts ...grpc.CallOption) (*ServiceResponse, error) {
- out := new(ServiceResponse)
- err := c.cc.Invoke(ctx, "/contracts.Auth/Auth", in, out, opts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *authClient) GetToken(ctx context.Context, in *GetTokenContract, opts ...grpc.CallOption) (*GetTokenResponse, error) {
- out := new(GetTokenResponse)
- err := c.cc.Invoke(ctx, "/contracts.Auth/GetToken", in, out, opts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-// AuthServer is the server API for Auth service.
-type AuthServer interface {
- Auth(context.Context, *AuthContract) (*ServiceResponse, error)
- GetToken(context.Context, *GetTokenContract) (*GetTokenResponse, error)
-}
-
-// UnimplementedAuthServer can be embedded to have forward compatible implementations.
-type UnimplementedAuthServer struct {
-}
-
-func (*UnimplementedAuthServer) Auth(ctx context.Context, req *AuthContract) (*ServiceResponse, error) {
- return nil, status.Errorf(codes.Unimplemented, "method Auth not implemented")
-}
-func (*UnimplementedAuthServer) GetToken(ctx context.Context, req *GetTokenContract) (*GetTokenResponse, error) {
- return nil, status.Errorf(codes.Unimplemented, "method GetToken not implemented")
-}
-
-func RegisterAuthServer(s *grpc.Server, srv AuthServer) {
- s.RegisterService(&_Auth_serviceDesc, srv)
-}
-
-func _Auth_Auth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(AuthContract)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(AuthServer).Auth(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: "/contracts.Auth/Auth",
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(AuthServer).Auth(ctx, req.(*AuthContract))
- }
- return interceptor(ctx, in, info, handler)
-}
-
-func _Auth_GetToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(GetTokenContract)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(AuthServer).GetToken(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: "/contracts.Auth/GetToken",
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(AuthServer).GetToken(ctx, req.(*GetTokenContract))
- }
- return interceptor(ctx, in, info, handler)
-}
-
-var _Auth_serviceDesc = grpc.ServiceDesc{
- ServiceName: "contracts.Auth",
- HandlerType: (*AuthServer)(nil),
- Methods: []grpc.MethodDesc{
- {
- MethodName: "Auth",
- Handler: _Auth_Auth_Handler,
- },
- {
- MethodName: "GetToken",
- Handler: _Auth_GetToken_Handler,
- },
- },
- Streams: []grpc.StreamDesc{},
- Metadata: "auth.proto",
-}
diff --git a/internal/web/grpc/contracts/auth.proto b/internal/web/grpc/contracts/auth.proto
deleted file mode 100644
index 43e0ecae..00000000
--- a/internal/web/grpc/contracts/auth.proto
+++ /dev/null
@@ -1,33 +0,0 @@
-syntax = "proto3";
-option go_package = "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/grpc/contracts";
-package contracts;
-
-message AuthContract {
- string ServiceName = 1;
- string IPAddress = 2;
- string Endpoint = 3;
- string Username = 4;
- string Password = 5;
- string Token = 6;
- string Topic = 7;
-}
-
-message ServiceResponse {
- int32 Code = 1;
-}
-
-message GetTokenContract {
- string GrantType = 1;
- string ClientID = 2;
- string ClientSecret = 3;
-}
-
-message GetTokenResponse {
- int32 Code = 1;
- string Token = 2;
-}
-
-service Auth {
- rpc Auth(AuthContract) returns (ServiceResponse) {}
- rpc GetToken(GetTokenContract) returns (GetTokenResponse) {}
-}
diff --git a/internal/web/grpc/server.go b/internal/web/grpc/server.go
deleted file mode 100644
index 0f816f2e..00000000
--- a/internal/web/grpc/server.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package grpc
-
-import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "time"
-
- "gitlab.snapp.ir/dispatching/soteria/v3/internal"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/grpc/contracts"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "go.uber.org/zap"
- "google.golang.org/grpc"
-)
-
-type Server struct {
- contracts.AuthContract
-}
-
-func (s *Server) Auth(ctx context.Context, in *contracts.AuthContract) (*contracts.ServiceResponse, error) {
- start := time.Now()
-
- username := in.GetUsername()
- password := in.GetPassword()
- endpoint := in.GetEndpoint()
- ip := in.GetIPAddress()
- zap.L().Debug("grpc auth call",
- zap.String("username", username),
- zap.String("endpoint", endpoint),
- zap.String("ip", ip),
- )
-
- var ok bool
- var err error
- if len(password) > 0 {
- ok, err = app.GetInstance().Authenticator.EndPointBasicAuth(ctx, username, password, endpoint)
- } else if len(ip) > 0 {
- ok, err = app.GetInstance().Authenticator.EndpointIPAuth(ctx, username, ip, endpoint)
- } else {
- ok = false
- err = fmt.Errorf("both password and ip address are empty")
- }
-
- if !ok {
- if errors.Is(err, db.ErrDb) {
- app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Auth, http.StatusInternalServerError)
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Auth, internal.Failure, "database error happened")
- app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Auth, float64(time.Since(start).Nanoseconds()))
-
- zap.L().Error("grpc auth returned", zap.Int("code", http.StatusInternalServerError), zap.Error(err))
- return &contracts.ServiceResponse{Code: http.StatusInternalServerError}, fmt.Errorf("internal server error")
- }
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Auth, http.StatusUnauthorized)
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Auth, internal.Failure, "request is not authorized")
- app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Auth, float64(time.Since(start).Nanoseconds()))
-
- zap.L().Error("grpc auth returned", zap.Int("code", http.StatusUnauthorized), zap.Error(err))
- return &contracts.ServiceResponse{Code: http.StatusUnauthorized}, fmt.Errorf("request is unauthorized")
- }
-
- zap.L().Info("grpc auth returned", zap.Int("code", http.StatusOK))
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Auth, http.StatusOK)
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Auth, internal.Success, "ok")
- app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Auth, float64(time.Since(start).Nanoseconds()))
-
- return &contracts.ServiceResponse{Code: http.StatusOK}, nil
-}
-
-func (s *Server) GetToken(ctx context.Context, in *contracts.GetTokenContract) (*contracts.GetTokenResponse, error) {
- start := time.Now()
-
- grantType := in.GetGrantType()
- clientID := in.GetClientID()
- clientSecret := in.GetClientSecret()
- zap.L().Debug("grpc token call",
- zap.String("grant_type", grantType),
- zap.String("client_id", clientID),
- zap.String("client_secret", clientSecret),
- )
-
- tokenString, err := app.GetInstance().Authenticator.Token(ctx, acl.AccessType(grantType), clientID, clientSecret)
- if err != nil {
- if errors.Is(err, db.ErrDb) {
- app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Token, http.StatusInternalServerError)
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, "database error happened")
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, clientID)
- app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Token, float64(time.Since(start).Nanoseconds()))
-
- return &contracts.GetTokenResponse{
- Code: http.StatusInternalServerError,
- Token: "",
- }, fmt.Errorf("internal server error")
- }
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Token, http.StatusUnauthorized)
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, "request is not authorized")
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Failure, clientID)
- app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Token, float64(time.Since(start).Nanoseconds()))
-
- return &contracts.GetTokenResponse{
- Code: http.StatusUnauthorized,
- Token: "",
- }, fmt.Errorf("request is unauthorized")
- }
-
- zap.L().
- Info("token request accepted",
- zap.String("grant_type", grantType),
- zap.String("client_id", clientID),
- zap.String("client_secret", clientSecret),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.GrpcApi, internal.Soteria, internal.Token, http.StatusOK)
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Success, "token request accepted")
- app.GetInstance().Metrics.ObserveStatus(internal.GrpcApi, internal.Soteria, internal.Token, internal.Success, clientID)
- app.GetInstance().Metrics.ObserveResponseTime(internal.GrpcApi, internal.Soteria, internal.Token, float64(time.Since(start).Nanoseconds()))
-
- return &contracts.GetTokenResponse{
- Code: 200,
- Token: tokenString,
- }, nil
-}
-
-func NewServer() *grpc.Server {
- s := grpc.NewServer()
- contracts.RegisterAuthServer(s, &Server{})
- return s
-}
diff --git a/internal/web/rest/api/accounts.go b/internal/web/rest/api/accounts.go
deleted file mode 100644
index bc3e2503..00000000
--- a/internal/web/rest/api/accounts.go
+++ /dev/null
@@ -1,218 +0,0 @@
-package api
-
-import (
- "fmt"
- "time"
-
- "github.com/gin-gonic/gin"
- "github.com/google/uuid"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- accountsInfo "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
-)
-
-// Response is the response structure of the REST API.
-type Response struct {
- Code accountsInfo.Code `json:"code"`
- Message string `json:"message"`
- Data interface{} `json:"data"`
-}
-
-// CreateResponse returns a HTTP Status Code and a response.
-func CreateResponse(code accountsInfo.Code, data interface{}, details ...string) (int, *Response) {
- return code.HttpStatusCode(), &Response{
- Code: code,
- Message: fmt.Sprintf("%s: %s", code.Message(), details),
- Data: data,
- }
-}
-
-// createAccountPayload is the body payload structure of create account endpoint
-type createAccountPayload struct {
- Username string `json:"username" form:"username" binding:"required"`
- Password string `json:"password" form:"password" binding:"required"`
- UserType user.Type `json:"user_type" form:"user_type" binding:"required"`
-}
-
-// CreateAccount is the handler of the create account endpoint
-func CreateAccount(ctx *gin.Context) {
- var p createAccountPayload
- if err := ctx.ShouldBind(&p); err != nil {
- ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error()))
- return
- }
-
- if err := app.GetInstance().AccountsService.SignUp(ctx, p.Username, p.Password, p.UserType); err != nil {
- ctx.JSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil))
-}
-
-// ReadAccount is the handler of the read account endpoint
-func ReadAccount(ctx *gin.Context) {
- username := ctx.MustGet("username").(string)
- password := ctx.MustGet("password").(string)
-
- u, err := app.GetInstance().AccountsService.Info(ctx, username, password)
- if err != nil {
- ctx.JSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, u))
-}
-
-// updateAccountPayload is the body payload structure of update account endpoint
-type updateAccountPayload struct {
- NewPassword string `json:"new_password" form:"new_password"`
- IPs []string `json:"ips" form:"ips"`
- Secret string `json:"secret" form:"secret"`
- Type user.Type `json:"type" form:"type"`
- TokenExpiration time.Duration `json:"token_expiration" form:"token_expiration"`
-}
-
-// UpdateAccount is the handler of the update account endpoint
-func UpdateAccount(ctx *gin.Context) {
- username := ctx.MustGet("username").(string)
-
- var p updateAccountPayload
- if err := ctx.ShouldBind(&p); err != nil {
- ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error()))
- return
- }
-
- if err := app.GetInstance().AccountsService.Update(ctx, username, p.NewPassword, p.Type, p.IPs, p.Secret, p.TokenExpiration); err != nil {
- ctx.JSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil))
-}
-
-// DeleteAccount is the handler of the delete account endpoint
-func DeleteAccount(ctx *gin.Context) {
- username := ctx.MustGet("username").(string)
-
- if err := app.GetInstance().AccountsService.Delete(ctx, username); err != nil {
- ctx.JSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil))
-}
-
-// createAccountRulePayload is the body payload structure of create account rule endpoint
-type createAccountRulePayload struct {
- Endpoint string `json:"endpoint" form:"endpoint"`
- Topic topics.Type `json:"topic" form:"topic"`
- AccessType acl.AccessType `json:"access_type" form:"access_type"`
-}
-
-// CreateAccountRule is the handler of the create account rule endpoint
-func CreateAccountRule(ctx *gin.Context) {
- username := ctx.MustGet("username").(string)
-
- var p createAccountRulePayload
- if err := ctx.ShouldBind(&p); err != nil {
- ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error()))
- return
- }
-
- r, err := app.GetInstance().AccountsService.CreateRule(ctx, username, p.Endpoint, p.Topic, p.AccessType)
- if err != nil {
- ctx.JSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, r))
-}
-
-// ReadAccountRule is the handler of the read account rule endpoint
-func ReadAccountRule(ctx *gin.Context) {
- username := ctx.MustGet("username").(string)
-
- plainUUID := ctx.Param("uuid")
- if plainUUID == "" {
- ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil))
- return
- }
-
- ruleUUID, err := uuid.Parse(plainUUID)
- if err != nil {
- ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil, err.Error()))
- return
- }
-
- r, rErr := app.GetInstance().AccountsService.GetRule(ctx, username, ruleUUID)
- if rErr != nil {
- ctx.JSON(CreateResponse(rErr.Code, nil, rErr.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, r))
-}
-
-// updateAccountRulePayload is the body payload structure of update account rule endpoint
-type updateAccountRulePayload struct {
- Endpoint string `json:"endpoint" form:"endpoint"`
- Topic topics.Type `json:"topic" form:"topic"`
- AccessType acl.AccessType `json:"access_type" form:"access_type"`
-}
-
-// UpdateAccountRule is the handler of the update account rule endpoint
-func UpdateAccountRule(ctx *gin.Context) {
- username := ctx.MustGet("username").(string)
-
- var p updateAccountRulePayload
- if err := ctx.ShouldBind(&p); err != nil {
- ctx.JSON(CreateResponse(accountsInfo.BadRequestPayload, nil, err.Error()))
- return
- }
-
- plainUUID := ctx.Param("uuid")
- if plainUUID == "" {
- ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil))
- return
- }
-
- ruleUUID, err := uuid.Parse(plainUUID)
- if err != nil {
- ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil, err.Error()))
- return
- }
-
- if err := app.GetInstance().AccountsService.UpdateRule(ctx, username, ruleUUID, p.Endpoint, p.Topic, p.AccessType); err != nil {
- ctx.JSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil))
-}
-
-// DeleteAccountRule is the handler of the delete account rule endpoint
-func DeleteAccountRule(ctx *gin.Context) {
- username := ctx.MustGet("username").(string)
-
- plainUUID := ctx.Param("uuid")
- if plainUUID == "" {
- ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil))
- return
- }
-
- ruleUUID, err := uuid.Parse(plainUUID)
- if err != nil {
- ctx.JSON(CreateResponse(accountsInfo.InvalidRuleUUID, nil, err.Error()))
- return
- }
-
- if err := app.GetInstance().AccountsService.DeleteRule(ctx, username, ruleUUID); err != nil {
- ctx.JSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.JSON(CreateResponse(accountsInfo.SuccessfulOperation, nil))
-}
diff --git a/internal/web/rest/api/accounts_test.go b/internal/web/rest/api/accounts_test.go
deleted file mode 100644
index badb1dc5..00000000
--- a/internal/web/rest/api/accounts_test.go
+++ /dev/null
@@ -1,487 +0,0 @@
-package api
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "github.com/alicebob/miniredis/v2"
- "github.com/go-redis/redis/v8"
- "github.com/stretchr/testify/assert"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/accounts"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- redisModel "gitlab.snapp.ir/dispatching/soteria/v3/internal/db/redis"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/user"
- "io/ioutil"
- "net/http"
- "net/http/httptest"
- "testing"
- "time"
-)
-
-func init() {
- mr, err := miniredis.Run()
- if err != nil {
- panic(err)
- }
- client := redis.NewClient(&redis.Options{
- Addr: mr.Addr(),
- })
- app.GetInstance().SetAccountsService(&accounts.Service{
- Handler: redisModel.ModelHandler{
- Client: client,
- },
- })
-}
-
-func TestCreateAccount(t *testing.T) {
- router := setupRouter("debug")
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"username":"user","password":"123","user_type":"driver"}`)
- req, _ := http.NewRequest("POST", "/accounts", bytes.NewBuffer(payload))
- req.Header.Set("Content-Type", "application/json")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- expectedResponse := Response{
- Code: errors.SuccessfulOperation,
- Message: fmt.Sprintf("%s: []", errors.SuccessfulOperation.Message()),
- Data: nil,
- }
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, expectedResponse, actualResponse)
- })
-
- t.Run("testing without content type header", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"username":"user","password":"123","user_type":"driver"}`)
- req, _ := http.NewRequest(http.MethodPost, "/accounts", bytes.NewBuffer(payload))
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusBadRequest, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.BadRequestPayload, actualResponse.Code)
- })
-}
-
-func TestReadAccount(t *testing.T) {
- router := setupRouter("debug")
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/accounts/user", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code)
-
- u := actualResponse.Data.(map[string]interface{})
- assert.Equal(t, "user", u["username"])
- assert.Equal(t, "passenger", u["type"])
- })
-}
-
-func TestUpdateAccount(t *testing.T) {
- router := setupRouter("debug")
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"new_password":"password2","ips":["127.0.0.1"],"secret":"12345678","type":"EMQUser","token_expiration":1000000}`)
- req, _ := http.NewRequest(http.MethodPut, "/accounts/user", bytes.NewBuffer(payload))
- req.Header.Set("Content-Type", "application/json")
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code)
-
- u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password2")
- assert.Nil(t, err)
- assert.Equal(t, 1, len(u.IPs))
- assert.Equal(t, "12345678", u.Secret)
- assert.Equal(t, user.EMQUser, u.Type)
- assert.Equal(t, time.Duration(1000000), u.TokenExpirationDuration)
- })
-}
-
-func TestDeleteAccount(t *testing.T) {
- router := setupRouter("debug")
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
- req, _ := http.NewRequest(http.MethodDelete, "/accounts/user", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code)
-
- _, err = app.GetInstance().AccountsService.Info(context.Background(), "user", "password2")
- assert.NotNil(t, err)
- })
-}
-
-func TestCreateAccountRule(t *testing.T) {
- router := setupRouter("debug")
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
-
- t.Run("testing with invalid rule info", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"endpoint":"/notification","topic":"","access_type":""}`)
- req, _ := http.NewRequest(http.MethodPost, "/accounts/user/rules", bytes.NewBuffer(payload))
- req.Header.Set("Content-Type", "application/json")
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusBadRequest, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.InvalidRule, actualResponse.Code)
-
- u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password")
- assert.Nil(t, err)
- assert.Equal(t, 0, len(u.Rules))
- })
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"endpoint":"/notification","topic":"","access_type":"2"}`)
- req, _ := http.NewRequest(http.MethodPost, "/accounts/user/rules", bytes.NewBuffer(payload))
- req.Header.Set("Content-Type", "application/json")
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code)
-
- u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password")
- assert.Nil(t, err)
- assert.Equal(t, 1, len(u.Rules))
- assert.Equal(t, "/notification", u.Rules[0].Endpoint)
- assert.Equal(t, topics.Type(""), u.Rules[0].Topic)
- assert.Equal(t, acl.Pub, u.Rules[0].AccessType)
- })
-}
-
-func TestReadAccountRule(t *testing.T) {
- router := setupRouter("debug")
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
- createdRule, _ := app.GetInstance().AccountsService.CreateRule(context.Background(), "user", "/notification", "", "2")
-
- t.Run("testing with invalid UUID", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/accounts/user/rules/invalid-uuid", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusBadRequest, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.InvalidRuleUUID, actualResponse.Code)
- })
-
- t.Run("testing with undefined rule", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusNotFound, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.RuleNotFound, actualResponse.Code)
- })
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/accounts/user/rules/%s", createdRule.UUID), nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code)
-
- returnedRule := actualResponse.Data.(map[string]interface{})
- assert.Equal(t, createdRule.UUID.String(), returnedRule["uuid"])
- assert.Equal(t, "/notification", returnedRule["endpoint"])
- assert.Equal(t, "", returnedRule["topic"])
- assert.Equal(t, "", returnedRule["topic"])
- })
-}
-
-func TestUpdateAccountRule(t *testing.T) {
- router := setupRouter("debug")
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
- createdRule, _ := app.GetInstance().AccountsService.CreateRule(context.Background(), "user", "/notification", "", "2")
-
- t.Run("testing with no payload", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodPut, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusBadRequest, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.BadRequestPayload, actualResponse.Code)
- })
-
- t.Run("testing with invalid UUID", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"endpoint":"/notification","topic":"","access_type":""}`)
- req, _ := http.NewRequest(http.MethodPut, "/accounts/user/rules/invalid-uuid", bytes.NewBuffer(payload))
- req.SetBasicAuth("user", "password")
- req.Header.Set("Content-Type", "application/json")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusBadRequest, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.InvalidRuleUUID, actualResponse.Code)
- })
-
- t.Run("testing with undefined rule", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"endpoint":"/notification","topic":"","access_type":"2"}`)
- req, _ := http.NewRequest(http.MethodPut, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", bytes.NewBuffer(payload))
- req.SetBasicAuth("user", "password")
- req.Header.Set("Content-Type", "application/json")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusNotFound, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.RuleNotFound, actualResponse.Code)
- })
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- payload := []byte(`{"endpoint":"","topic":"cab_event","access_type":"2"}`)
- req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/accounts/user/rules/%s", createdRule.UUID), bytes.NewBuffer(payload))
- req.SetBasicAuth("user", "password")
- req.Header.Set("Content-Type", "application/json")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code)
-
- u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password")
- assert.Nil(t, err)
- assert.Equal(t, 1, len(u.Rules))
- assert.Equal(t, createdRule.UUID, u.Rules[0].UUID)
- assert.Equal(t, "", u.Rules[0].Endpoint)
- assert.Equal(t, topics.CabEvent, u.Rules[0].Topic)
- assert.Equal(t, acl.Pub, u.Rules[0].AccessType)
- })
-}
-
-func TestDeleteAccountRule(t *testing.T) {
- router := setupRouter("debug")
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
- createdRule, _ := app.GetInstance().AccountsService.CreateRule(context.Background(), "user", "/notification", "", "2")
-
- t.Run("testing with invalid UUID", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodDelete, "/accounts/user/rules/invalid-uuid", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusBadRequest, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.InvalidRuleUUID, actualResponse.Code)
- })
-
- t.Run("testing with undefined rule", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodDelete, "/accounts/user/rules/b33a0b78-c8a6-4719-a222-9a3883cc4b7c", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusNotFound, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.RuleNotFound, actualResponse.Code)
- })
-
- t.Run("testing successful request", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodDelete, fmt.Sprintf("/accounts/user/rules/%s", createdRule.UUID), nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
-
- resBody, err := ioutil.ReadAll(w.Body)
- assert.NoError(t, err)
-
- var actualResponse Response
- err = json.Unmarshal(resBody, &actualResponse)
- assert.NoError(t, err)
-
- assert.Equal(t, errors.SuccessfulOperation, actualResponse.Code)
-
- u, err := app.GetInstance().AccountsService.Info(context.Background(), "user", "password")
- assert.Nil(t, err)
- assert.Equal(t, 0, len(u.Rules))
- })
-}
diff --git a/internal/web/rest/api/acl.go b/internal/web/rest/api/acl.go
deleted file mode 100644
index 763e7bec..00000000
--- a/internal/web/rest/api/acl.go
+++ /dev/null
@@ -1,142 +0,0 @@
-package api
-
-import (
- "errors"
- "net/http"
- "time"
-
- "github.com/gin-gonic/gin"
- "github.com/opentracing/opentracing-go"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/authenticator"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "go.uber.org/zap"
-)
-
-// aclRequest is the body payload structure of the ACL endpoint.
-type aclRequest struct {
- Access acl.AccessType `form:"access"`
- Token string `form:"token"`
- Username string `from:"username"`
- Password string `form:"password"`
- Topic string `form:"topic"`
-}
-
-// ACL is the handler responsible for ACL requests.
-func ACL(ctx *gin.Context) {
- aclSpan := app.GetInstance().Tracer.StartSpan("api.rest.acl")
- defer aclSpan.Finish()
-
- s := time.Now()
- request := &aclRequest{}
- err := ctx.ShouldBind(request)
- if err != nil {
-
- zap.L().
- Warn("acl bad request",
- zap.Error(err),
- zap.String("access", request.Access.String()),
- zap.String("topic", request.Topic),
- zap.String("token", request.Token),
- zap.String("username", request.Password),
- zap.String("password", request.Username),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusBadRequest)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "bad request")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusBadRequest, "bad request")
- return
- }
- tokenString := request.Token
- if len(request.Token) == 0 {
- tokenString = request.Username
- }
- if len(tokenString) == 0 {
- tokenString = request.Password
- }
-
- topic := topics.Topic(request.Topic)
- topicType := topic.GetType()
- if len(topicType) == 0 {
- zap.L().
- Warn("acl bad request",
- zap.Error(err),
- zap.String("access", request.Access.String()),
- zap.String("topic", request.Topic),
- zap.String("token", request.Token),
- zap.String("username", request.Password),
- zap.String("password", request.Username),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusBadRequest)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "bad request")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusBadRequest, "bad request")
- return
- }
-
- aclCheckSpan := app.GetInstance().Tracer.StartSpan("acl check", opentracing.ChildOf(aclSpan.Context()))
-
- ok, err := app.GetInstance().Authenticator.ACL(ctx, request.Access, tokenString, topic)
- if err != nil || !ok {
- aclCheckSpan.SetTag("success", false)
- if err != nil {
- aclCheckSpan.SetTag("error", err.Error())
- }
-
- // nolint: exhaustivestruct
- if errors.Is(err, authenticator.ErrTopicNotAllowed{}) {
- zap.L().
- Warn("acl request is not authorized",
- zap.Error(err))
- } else {
- zap.L().
- Error("acl request is not authorized",
- zap.Error(err))
- }
-
- if errors.Is(err, db.ErrDb) {
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusInternalServerError)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, request.Access.String(), internal.Failure, string(topicType))
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "database error happened")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusInternalServerError, "internal server error")
-
- aclCheckSpan.Finish()
-
- return
- }
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusUnauthorized)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, request.Access.String(), internal.Failure, string(topicType))
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Failure, "request is not authorized")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusUnauthorized, "request is not authorized")
-
- aclCheckSpan.Finish()
-
- return
- }
-
- zap.L().
- Info("acl ok",
- zap.String("access", request.Access.String()),
- zap.String("topic", request.Topic),
- zap.String("token", request.Token),
- zap.String("username", request.Password),
- zap.String("password", request.Username),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Acl, http.StatusOK)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Acl, internal.Success, "ok")
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, request.Access.String(), internal.Success, string(topicType))
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Acl, float64(time.Since(s).Nanoseconds()))
-
- aclCheckSpan.SetTag("success", true)
-
- ctx.String(http.StatusOK, "ok")
-}
diff --git a/internal/web/rest/api/api.go b/internal/web/rest/api/api.go
deleted file mode 100644
index 9641cf73..00000000
--- a/internal/web/rest/api/api.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package api
-
-import (
- "fmt"
- "net/http"
- "time"
-
- ginzap "github.com/gin-contrib/zap"
- "github.com/gin-gonic/gin"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api/emq"
- "go.uber.org/zap"
-)
-
-// setupRouter will attach all routes needed for Soteria to gin's default router
-func setupRouter(mode string) *gin.Engine {
- gin.SetMode(mode)
-
- router := gin.New()
- router.Use(gin.Recovery())
-
- router.Use(ginzap.Ginzap(zap.L(), time.RFC3339, false))
- router.Use(ginzap.RecoveryWithZap(zap.L(), true))
-
- a := router.Group("/accounts")
- {
- a.POST("", CreateAccount)
- authorizedRoutes := a.Use(accountsBasicAuth())
- {
- authorizedRoutes.GET("/:username", ReadAccount)
- authorizedRoutes.PUT("/:username", UpdateAccount)
- authorizedRoutes.DELETE("/:username", DeleteAccount)
-
- authorizedRoutes.POST("/:username/rules", CreateAccountRule)
- authorizedRoutes.GET("/:username/rules/:uuid", ReadAccountRule)
- authorizedRoutes.PUT("/:username/rules/:uuid", UpdateAccountRule)
- authorizedRoutes.DELETE("/:username/rules/:uuid", DeleteAccountRule)
- }
- }
-
- emq.Register(router.Group("/emq"))
-
- router.POST("/auth", Auth)
- router.POST("/acl", ACL)
- router.POST("/token", Token)
-
- router.GET("/metrics", gin.WrapH(promhttp.Handler()))
-
- return router
-}
-
-// RestServer will return an HTTP.Server with given port and gin mode
-func RestServer(mode string, port int) *http.Server {
- return &http.Server{
- Addr: fmt.Sprintf(":%d", port),
- Handler: setupRouter(mode),
- }
-}
diff --git a/internal/web/rest/api/auth.go b/internal/web/rest/api/auth.go
deleted file mode 100644
index bc7fe499..00000000
--- a/internal/web/rest/api/auth.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package api
-
-import (
- "github.com/gin-gonic/gin"
- "github.com/opentracing/opentracing-go"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "go.uber.org/zap"
- "net/http"
- "time"
-)
-
-// authRequest is the body payload structure of the auth endpoint
-type authRequest struct {
- Token string `form:"token"`
- Username string `from:"username"`
- Password string `form:"password"`
-}
-
-// Auth is the handler responsible for authentication
-func Auth(ctx *gin.Context) {
- authSpan := app.GetInstance().Tracer.StartSpan("api.rest.auth")
- defer authSpan.Finish()
-
- s := time.Now()
- request := &authRequest{}
- err := ctx.ShouldBind(request)
- if err != nil {
- zap.L().
- Warn("bad request",
- zap.Error(err),
- )
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusBadRequest)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Failure, "bad request")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusBadRequest, "bad request")
- return
- }
- tokenString := request.Token
- if len(tokenString) == 0 {
- tokenString = request.Username
- }
- if len(tokenString) == 0 {
- tokenString = request.Password
- }
-
- authCheckSpan := app.GetInstance().Tracer.StartSpan("auth check", opentracing.ChildOf(authSpan.Context()))
-
- superuser, err := app.GetInstance().Authenticator.Auth(ctx, tokenString)
- if err != nil {
- authCheckSpan.SetTag("success", false)
- authCheckSpan.SetTag("error", err.Error())
-
- zap.L().
- Error("auth request is not authorized",
- zap.Error(err),
- zap.String("token", request.Token),
- zap.String("username", request.Password),
- zap.String("password", request.Username),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusUnauthorized)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Failure, "request is not authorized")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusUnauthorized, "request is not authorized")
-
- authCheckSpan.Finish()
-
- return
- }
-
- if superuser == true {
- authCheckSpan.SetTag("success", false)
- authCheckSpan.SetTag("ignored", true)
-
- zap.L().
- Error("auth request is ignored",
- zap.Error(err),
- zap.String("token", request.Token),
- zap.String("username", request.Password),
- zap.String("password", request.Username),
- zap.Bool("superuser", superuser),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusOK)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Ignore, "request is ignored")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds()))
-
- ctx.String(http.StatusOK, "ignore")
-
- authCheckSpan.Finish()
-
- return
- }
-
- zap.L().
- Info("auth ok",
- zap.String("token", request.Token),
- zap.String("username", request.Password),
- zap.String("password", request.Username),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Auth, http.StatusOK)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Auth, internal.Success, "ok")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Auth, float64(time.Since(s).Nanoseconds()))
-
- authSpan.SetTag("success", true)
-
- ctx.String(http.StatusOK, "ok")
-}
diff --git a/internal/web/rest/api/emq/emq.go b/internal/web/rest/api/emq/emq.go
deleted file mode 100644
index f3cd430d..00000000
--- a/internal/web/rest/api/emq/emq.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package emq
-
-import (
- "net/http"
- "time"
-
- "github.com/gin-gonic/gin"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/emq"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api/emq/request"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/web/rest/api/emq/response"
-)
-
-func Register(group *gin.RouterGroup) {
- group.POST("", Create)
- group.GET("", List)
-}
-
-// List returns the list of registered users for emqx-redis-auth.
-func List(ctx *gin.Context) {
- users, err := app.GetInstance().EMQStore.LoadAll(ctx)
- if err != nil {
- ctx.JSON(http.StatusInternalServerError, response.Response{
- Message: err.Error(),
- Data: nil,
- })
-
- return
- }
-
- ctx.JSON(http.StatusCreated, response.Response{
- Message: "success",
- Data: users,
- })
-}
-
-// Create is the handler of the create emq redis account endpoint.
-func Create(ctx *gin.Context) {
- var p request.Create
-
- if err := ctx.ShouldBind(&p); err != nil {
- ctx.JSON(http.StatusBadRequest, response.Response{
- Message: err.Error(),
- Data: nil,
- })
-
- return
- }
-
- if err := p.Validate(); err != nil {
- ctx.JSON(http.StatusBadRequest, response.Response{
- Message: err.Error(),
- Data: nil,
- })
-
- return
- }
-
- token, err := app.GetInstance().Authenticator.SuperuserToken(p.Username, time.Duration(p.Duration))
- if err != nil {
- ctx.JSON(http.StatusInternalServerError, response.Response{
- Message: err.Error(),
- Data: nil,
- })
-
- return
- }
-
- if err := app.GetInstance().EMQStore.Save(ctx, emq.User{
- Username: token,
- Password: p.Password,
- IsSuperuser: true,
- }); err != nil {
- ctx.JSON(http.StatusInternalServerError, response.Response{
- Message: err.Error(),
- Data: nil,
- })
-
- return
- }
-
- ctx.JSON(http.StatusCreated, response.Response{
- Message: "success",
- Data: response.Create{
- Token: token,
- Password: p.Password,
- },
- })
-}
diff --git a/internal/web/rest/api/emq/request/create.go b/internal/web/rest/api/emq/request/create.go
deleted file mode 100644
index 7d7b9b23..00000000
--- a/internal/web/rest/api/emq/request/create.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package request
-
-import (
- "fmt"
-
- validation "github.com/go-ozzo/ozzo-validation/v4"
-)
-
-// Create is the body payload structure of create emq endpoint.
-type Create struct {
- Password string `json:"password"`
- Username string `json:"username"`
- Duration int64 `json:"duration"`
-}
-
-func (r Create) Validate() error {
- if err := validation.ValidateStruct(&r,
- validation.Field(&r.Password, validation.Required),
- validation.Field(&r.Username, validation.Required),
- validation.Field(&r.Duration, validation.Required),
- ); err != nil {
- return fmt.Errorf("create request validation failed: %w", err)
- }
-
- return nil
-}
diff --git a/internal/web/rest/api/emq/response/create.go b/internal/web/rest/api/emq/response/create.go
deleted file mode 100644
index 358ec09d..00000000
--- a/internal/web/rest/api/emq/response/create.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package response
-
-type Create struct {
- Token string
- Password string
-}
diff --git a/internal/web/rest/api/emq/response/response.go b/internal/web/rest/api/emq/response/response.go
deleted file mode 100644
index 46d17bc6..00000000
--- a/internal/web/rest/api/emq/response/response.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package response
-
-// Response is the response structure of the REST API.
-type Response struct {
- Message string `json:"message"`
- Data interface{} `json:"data,omitempty"`
-}
diff --git a/internal/web/rest/api/middlewares.go b/internal/web/rest/api/middlewares.go
deleted file mode 100644
index 9170a535..00000000
--- a/internal/web/rest/api/middlewares.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package api
-
-import (
- "encoding/base64"
- "github.com/gin-gonic/gin"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- accountsInfo "gitlab.snapp.ir/dispatching/soteria/v3/pkg/errors"
- "strings"
-)
-
-// accountsBasicAuth is the authentication middleware for the accounts API
-func accountsBasicAuth() gin.HandlerFunc {
- return func(ctx *gin.Context) {
- auth := strings.SplitN(ctx.Request.Header.Get("Authorization"), " ", 2)
-
- if len(auth) != 2 || auth[0] != "Basic" {
- ctx.AbortWithStatusJSON(CreateResponse(accountsInfo.WrongUsernameOrPassword, nil))
- return
- }
-
- payload, _ := base64.StdEncoding.DecodeString(auth[1])
- pair := strings.SplitN(string(payload), ":", 2)
-
- if len(pair) != 2 {
- ctx.AbortWithStatusJSON(CreateResponse(accountsInfo.WrongUsernameOrPassword, nil))
- return
- }
-
- if pair[0] != ctx.Param("username") {
- ctx.AbortWithStatusJSON(CreateResponse(accountsInfo.UsernameMismatch, nil))
- return
- }
-
- _, err := app.GetInstance().AccountsService.Info(ctx, pair[0], pair[1])
- if err != nil {
- ctx.AbortWithStatusJSON(CreateResponse(err.Code, nil, err.Message))
- return
- }
-
- ctx.Set("username", pair[0])
- ctx.Set("password", pair[1])
-
- ctx.Next()
- }
-}
diff --git a/internal/web/rest/api/middlewares_test.go b/internal/web/rest/api/middlewares_test.go
deleted file mode 100644
index 188383ed..00000000
--- a/internal/web/rest/api/middlewares_test.go
+++ /dev/null
@@ -1,76 +0,0 @@
-package api
-
-import (
- "context"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "net/http"
- "net/http/httptest"
- "testing"
-)
-
-func TestAccountsBasicAuth(t *testing.T) {
- router := gin.Default()
- router.Use(accountsBasicAuth())
- router.GET("/:username", func(ctx *gin.Context) {
- ctx.String(http.StatusOK, "done")
- return
- })
-
- _ = app.GetInstance().AccountsService.SignUp(context.Background(), "user", "password", "passenger")
-
- t.Run("testing successful authentication", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/user", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
- })
-
- t.Run("testing without a Authorization header", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/user", nil)
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusUnauthorized, w.Code)
- })
-
- t.Run("testing with an invalid auth", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/user", nil)
- req.Header.Set("Authorization", "Basic invalid")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusUnauthorized, w.Code)
- })
-
- t.Run("testing different username in path", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/user2", nil)
- req.SetBasicAuth("user", "password")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusUnauthorized, w.Code)
- })
-
- t.Run("testing with wrong password", func(t *testing.T) {
- w := httptest.NewRecorder()
-
- req, _ := http.NewRequest(http.MethodGet, "/user", nil)
- req.SetBasicAuth("user", "password2")
-
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusUnauthorized, w.Code)
- })
-}
diff --git a/internal/web/rest/api/token.go b/internal/web/rest/api/token.go
deleted file mode 100644
index 918895fd..00000000
--- a/internal/web/rest/api/token.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package api
-
-import (
- "errors"
- "github.com/gin-gonic/gin"
- "github.com/opentracing/opentracing-go"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/app"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
- "go.uber.org/zap"
- "net/http"
- "time"
-)
-
-// TokenRequest is the body payload structure of the token endpoint
-type TokenRequest struct {
- GrantType acl.AccessType `json:"grant_type" form:"grant_type" query:"grant_type"`
- ClientID string `json:"client_id" form:"client_id" query:"client_id"`
- ClientSecret string `json:"client_secret" form:"client_secret" query:"client_secret"`
-}
-
-// Token is the handler responsible for Token requests
-func Token(ctx *gin.Context) {
- tokenSpan := app.GetInstance().Tracer.StartSpan("api.rest.token")
- defer tokenSpan.Finish()
-
- s := time.Now()
- request := &TokenRequest{}
- err := ctx.Bind(request)
- if err != nil {
- zap.L().
- Warn("bad request",
- zap.Error(err),
- )
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusBadRequest)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, "bad request")
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusBadRequest, "bad request")
- return
- }
-
- tokenIssueSpan := app.GetInstance().Tracer.StartSpan("issue token", opentracing.ChildOf(tokenSpan.Context()))
-
- tokenString, err := app.GetInstance().Authenticator.Token(ctx, request.GrantType, request.ClientID, request.ClientSecret)
- if err != nil {
- zap.L().
- Error("token request is not authorized",
- zap.Error(err),
- zap.String("grant_type", request.GrantType.String()),
- zap.String("client_id", request.ClientID),
- zap.String("client_secret", request.ClientSecret),
- )
-
- tokenIssueSpan.SetTag("success", false)
-
- tokenIssueSpan.SetTag("error", err.Error()).Finish()
-
- if errors.Is(err, db.ErrDb) {
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusInternalServerError)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, "database error happened")
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, request.ClientID)
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusInternalServerError, "internal server error")
-
- tokenIssueSpan.Finish()
-
- return
- }
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusUnauthorized)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, "request is not authorized")
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Failure, request.ClientID)
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds()))
- ctx.String(http.StatusUnauthorized, "request is not authorized")
-
- tokenIssueSpan.Finish()
-
- return
- }
-
- zap.L().
- Info("token request accepted",
- zap.String("grant_type", request.GrantType.String()),
- zap.String("client_id", request.ClientID),
- zap.String("client_secret", request.ClientSecret),
- )
-
- app.GetInstance().Metrics.ObserveStatusCode(internal.HttpApi, internal.Soteria, internal.Token, http.StatusAccepted)
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Success, "token request accepted")
- app.GetInstance().Metrics.ObserveStatus(internal.HttpApi, internal.Soteria, internal.Token, internal.Success, request.ClientID)
- app.GetInstance().Metrics.ObserveResponseTime(internal.HttpApi, internal.Soteria, internal.Token, float64(time.Since(s).Nanoseconds()))
-
- tokenIssueSpan.SetTag("success", true)
-
- ctx.String(http.StatusAccepted, tokenString)
-}
diff --git a/pkg/acl/acl.go b/pkg/acl/acl.go
new file mode 100644
index 00000000..bfdb4ce0
--- /dev/null
+++ b/pkg/acl/acl.go
@@ -0,0 +1,26 @@
+package acl
+
+// AccessType Types for EMQ contains subscribe, publish and publish-subscribe.
+type AccessType string
+
+const (
+ Sub AccessType = "1"
+ Pub AccessType = "2"
+ PubSub AccessType = "3"
+ None AccessType = "-1"
+
+ ClientCredentials = "client_credentials"
+)
+
+func (a AccessType) String() string {
+ switch a { //nolint:exhaustive
+ case Sub:
+ return "subscribe"
+ case Pub:
+ return "publish"
+ case PubSub:
+ return "publish-subscribe"
+ }
+
+ return ""
+}
diff --git a/pkg/acl/token.go b/pkg/acl/token.go
deleted file mode 100644
index 604c395d..00000000
--- a/pkg/acl/token.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package acl
-
-import (
- "github.com/dgrijalva/jwt-go"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
-)
-
-type Claims struct {
- jwt.StandardClaims
- Topics []Topic `json:"topics"`
- Endpoints []Endpoint `json:"endpoints"`
-}
-
-type Topic struct {
- Type topics.Type `json:"type"`
- AccessType AccessType `json:"access_type"`
-}
-
-type Endpoint struct {
- Name string `json:"name"`
-}
-
-type SuperuserClaims struct {
- jwt.StandardClaims
- IsSuperuser bool `json:"is_superuser"`
-}
diff --git a/pkg/acl/utils.go b/pkg/acl/utils.go
deleted file mode 100644
index ab7639b8..00000000
--- a/pkg/acl/utils.go
+++ /dev/null
@@ -1,88 +0,0 @@
-package acl
-
-import "net"
-
-// Access Types for EMQ contains subscribe, publish and publish-subscribe.
-type AccessType string
-
-const (
- Sub AccessType = "1"
- Pub AccessType = "2"
- PubSub AccessType = "3"
-
- ClientCredentials = "client_credentials"
-)
-
-func (a AccessType) String() string {
- switch a {
- case Sub:
- return "subscribe"
- case Pub:
- return "publish"
- case PubSub:
- return "publish-subscribe"
- }
-
- return ""
-}
-
-// ValidateEndpoint takes authorizedEndpoints and unauthorizedEndpoints and
-// tell whether a endpoint is authorized or not.
-func ValidateEndpoint(endpoint string, authorizedEndpoints, unauthorizedEndpoints []string) bool {
- if len(authorizedEndpoints) == 0 && len(unauthorizedEndpoints) == 0 {
- return true
- }
-
- isValid := false
-
- for _, e := range authorizedEndpoints {
- if e == endpoint {
- isValid = true
-
- break
- }
- }
-
- for _, e := range unauthorizedEndpoints {
- if e == endpoint {
- isValid = false
-
- break
- }
- }
-
- return isValid
-}
-
-// ValidateIP takes validIPs and invalidIPs and tell whether a IP is valid or not.
-func ValidateIP(ip string, validIPs, invalidIPs []string) bool {
- isValid := false
-
- for _, validIP := range validIPs {
- _, network, err := net.ParseCIDR(validIP)
- if err != nil && validIP == ip {
- isValid = true
-
- break
- } else if err == nil && network.Contains(net.ParseIP(ip)) {
- isValid = true
-
- break
- }
- }
-
- for _, invalidIP := range invalidIPs {
- _, network, err := net.ParseCIDR(invalidIP)
- if err != nil && invalidIP == ip {
- isValid = false
-
- break
- } else if err == nil && network.Contains(net.ParseIP(ip)) {
- isValid = false
-
- break
- }
- }
-
- return isValid
-}
diff --git a/pkg/acl/utils_test.go b/pkg/acl/utils_test.go
deleted file mode 100644
index 2582a151..00000000
--- a/pkg/acl/utils_test.go
+++ /dev/null
@@ -1,162 +0,0 @@
-package acl_test
-
-import (
- "testing"
-
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
-)
-
-// nolint: funlen
-func TestValidateEndpoint(t *testing.T) {
- t.Parallel()
-
- type args struct {
- endpoint string
- authorizedEndpoints []string
- unauthorizedEndpoints []string
- }
-
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "#1",
- args: args{
- endpoint: "",
- authorizedEndpoints: []string{},
- unauthorizedEndpoints: []string{},
- },
- want: true,
- },
- {
- name: "#2",
- args: args{
- endpoint: "/push",
- authorizedEndpoints: []string{"/push"},
- unauthorizedEndpoints: []string{"/push"},
- },
- want: false,
- },
- {
- name: "#3",
- args: args{
- endpoint: "/push",
- authorizedEndpoints: []string{"/push"},
- unauthorizedEndpoints: []string{"/events"},
- },
- want: true,
- },
- {
- name: "#4",
- args: args{
- endpoint: "/events",
- authorizedEndpoints: []string{"/push"},
- unauthorizedEndpoints: []string{"/events"},
- },
- want: false,
- },
- {
- name: "#5",
- args: args{
- endpoint: "/events",
- authorizedEndpoints: []string{"/push"},
- unauthorizedEndpoints: []string{"/event"},
- },
- want: false,
- },
- }
- for _, tt := range tests {
- tt := tt
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
- if got := acl.ValidateEndpoint(tt.args.endpoint, tt.args.authorizedEndpoints,
- tt.args.unauthorizedEndpoints); got != tt.want {
- t.Errorf("ValidateEndpoint() = %v, want %v", got, tt.want)
- }
- })
- }
-}
-
-// nolint: funlen
-func TestValidateIP(t *testing.T) {
- t.Parallel()
-
- type args struct {
- IP string
- validIPs []string
- invalidIPs []string
- }
-
- tests := []struct {
- name string
- args args
- want bool
- }{
- {
- name: "#1",
- args: args{
- IP: "192.168.24.56",
- validIPs: []string{},
- invalidIPs: []string{},
- },
- want: false,
- },
- {
- name: "#2",
- args: args{
- IP: "192.168.24.56",
- validIPs: []string{"192.168.24.56"},
- invalidIPs: []string{"192.168.24.56"},
- },
- want: false,
- },
- {
- name: "#3",
- args: args{
- IP: "192.168.24.56",
- validIPs: []string{"192.168.24.0/8"},
- invalidIPs: []string{"192.168.24.56"},
- },
- want: false,
- },
- {
- name: "#4",
- args: args{
- IP: "192.168.24.56",
- validIPs: []string{"192.168.24.0/8"},
- invalidIPs: []string{"192.168.24.8"},
- },
- want: true,
- },
- {
- name: "#5",
- args: args{
- IP: "192.168.24.56",
- validIPs: []string{"192.168.24.56"},
- invalidIPs: []string{"192.168.24.8/8"},
- },
- want: false,
- },
- {
- name: "#6",
- args: args{
- IP: "192.168.24.56",
- validIPs: []string{"192.168.24.1", "192.168.0.0/16"},
- invalidIPs: []string{"192.168.24.89"},
- },
- want: true,
- },
- }
-
- for _, tt := range tests {
- tt := tt
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
- if got := acl.ValidateIP(tt.args.IP, tt.args.validIPs, tt.args.invalidIPs); got != tt.want {
- t.Errorf("ValidateIP() = %v, want %v", got, tt.want)
- }
- })
- }
-}
diff --git a/pkg/errors/code.go b/pkg/errors/code.go
deleted file mode 100644
index 3c22d79c..00000000
--- a/pkg/errors/code.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package errors
-
-import "net/http"
-
-// Code is the type of the error returned to user in api
-type Code string
-
-const (
- SuccessfulOperation Code = "successful_operation"
- BadRequestPayload Code = "bad_request_payload"
- SignUpUserFailure Code = "sign_up_user_failure"
- WrongUsernameOrPassword Code = "wrong_username_or_password"
- DatabaseSaveFailure Code = "database_save_failure"
- DatabaseGetFailure Code = "database_get_failure"
- DatabaseUpdateFailure Code = "database_update_failure"
- DatabaseDeleteFailure Code = "database_delete_failure"
- PasswordHashGenerationFailure Code = "password_hash_generation_failure"
- UsernameMismatch Code = "username_mismatch"
- IPMisMatch Code = "ip_mismatch"
- PublicKeyReadFormFailure Code = "public_key_read_form_failure"
- PublicKeyOpenFailure Code = "public_key_open_failure"
- PublicKeyReadFileFailure Code = "public_key_read_file_failure"
- PublicKeyParseFailure Code = "public_key_parse_failure"
- InvalidRuleUUID Code = "invalid_rule_uuid"
- RuleNotFound Code = "rule_not_found"
- InvalidRule Code = "invalid_rule"
-)
-
-// Message will return detailed error information
-func (e Code) Message() string {
- switch e {
- case SuccessfulOperation:
- return "operation done successfully"
- case BadRequestPayload:
- return "request payload is not valid"
- case SignUpUserFailure:
- return "could not save user"
- case WrongUsernameOrPassword:
- return "username or password is not correct"
- case DatabaseSaveFailure:
- return "could not save info to database"
- case DatabaseGetFailure:
- return "could not get info from database"
- case DatabaseUpdateFailure:
- return "could update info in database"
- case DatabaseDeleteFailure:
- return "could delete info from database"
- case PasswordHashGenerationFailure:
- return "could not generate hash from password"
- case UsernameMismatch:
- return "provided username does not match with username in path"
- case IPMisMatch:
- return "ip is not authorized"
- case PublicKeyReadFormFailure:
- return "cloud not read public key file from form"
- case PublicKeyOpenFailure:
- return "could not open public key file"
- case PublicKeyReadFileFailure:
- return "could not read from opened public key file"
- case PublicKeyParseFailure:
- return "could not parse public key"
- case InvalidRuleUUID:
- return "provided rule UUID is not valid"
- case RuleNotFound:
- return "account has no rule with provided UUID"
- case InvalidRule:
- return "provided rule is not valid"
- default:
- return ""
- }
-}
-
-// HttpStatusCode returns the perfect HTTP Status Code for the error
-func (e Code) HttpStatusCode() int {
- switch e {
- case SuccessfulOperation:
- return http.StatusOK
- case BadRequestPayload:
- return http.StatusBadRequest
- case SignUpUserFailure:
- return http.StatusInternalServerError
- case WrongUsernameOrPassword:
- return http.StatusUnauthorized
- case DatabaseSaveFailure:
- return http.StatusInternalServerError
- case DatabaseGetFailure:
- return http.StatusInternalServerError
- case DatabaseUpdateFailure:
- return http.StatusInternalServerError
- case DatabaseDeleteFailure:
- return http.StatusInternalServerError
- case PasswordHashGenerationFailure:
- return http.StatusInternalServerError
- case UsernameMismatch:
- return http.StatusUnauthorized
- case IPMisMatch:
- return http.StatusUnauthorized
- case PublicKeyReadFormFailure:
- return http.StatusBadRequest
- case PublicKeyOpenFailure:
- return http.StatusBadRequest
- case PublicKeyReadFileFailure:
- return http.StatusInternalServerError
- case PublicKeyParseFailure:
- return http.StatusBadRequest
- case InvalidRuleUUID:
- return http.StatusBadRequest
- case RuleNotFound:
- return http.StatusNotFound
- case InvalidRule:
- return http.StatusBadRequest
- default:
- return http.StatusInternalServerError
- }
-}
diff --git a/pkg/errors/error.go b/pkg/errors/error.go
deleted file mode 100644
index 5d7dae66..00000000
--- a/pkg/errors/error.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package errors
-
-import (
- "fmt"
-)
-
-// Error is the error type that contains a Code and a message about the error
-type Error struct {
- Code Code
- Message string
-}
-
-func (e Error) Error() string {
- return fmt.Sprintf("%s: %s", e.Code.Message(), e.Message)
-}
-
-// CreateError returns an instance of Error with given information
-func CreateError(code Code, message string) *Error {
- return &Error{
- Code: code,
- Message: message,
- }
-}
diff --git a/pkg/log/l.go b/pkg/log/l.go
deleted file mode 100644
index 9b0daeb7..00000000
--- a/pkg/log/l.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package log
-
-import (
- "go.uber.org/zap"
- "go.uber.org/zap/zapcore"
- "os"
- "strings"
-)
-
-var atom zap.AtomicLevel
-
-// InitLogger will replace global zap.L() with our own preferred configs
-func InitLogger() func() {
- config := zap.NewProductionEncoderConfig()
- encoder := zapcore.NewJSONEncoder(config)
- atom = zap.NewAtomicLevel()
- logger := zap.New(zapcore.NewCore(encoder, zapcore.Lock(os.Stdout), atom))
- return zap.ReplaceGlobals(logger)
-}
-
-// SetLevel will change zap's log level
-func SetLevel(lvl string) {
- atom.SetLevel(parseLevel(lvl))
-}
-
-// parseLevel will convert a string based log level to zapcore.Level
-func parseLevel(lvl string) zapcore.Level {
- lvl = strings.ToLower(lvl)
- switch lvl {
- case "debug":
- return zapcore.DebugLevel
- case "info":
- return zapcore.InfoLevel
- case "warn":
- return zapcore.WarnLevel
- case "error":
- return zapcore.ErrorLevel
- default:
- return zapcore.ErrorLevel
- }
-}
diff --git a/pkg/memoize/m.go b/pkg/memoize/m.go
deleted file mode 100644
index 9762158c..00000000
--- a/pkg/memoize/m.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package memoize
-
-import (
- "golang.org/x/crypto/bcrypt"
-)
-
-func MemoizedCompareHashAndPassword() func([]byte, []byte) error {
- type args struct {
- hashedPassword string
- password string
- }
-
- cache := make(map[args]error)
-
- return func(hashedPassword []byte, password []byte) error {
- key := args{
- hashedPassword: string(hashedPassword),
- password: string(password),
- }
- if _, ok := cache[key]; !ok {
- cache[key] = bcrypt.CompareHashAndPassword(hashedPassword, password)
- }
-
- return cache[key]
- }
-}
diff --git a/pkg/memoize/m_test.go b/pkg/memoize/m_test.go
deleted file mode 100644
index 1f37858d..00000000
--- a/pkg/memoize/m_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package memoize_test
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/memoize"
- "golang.org/x/crypto/bcrypt"
-)
-
-func TestMemoizedCompareHashAndPassword(t *testing.T) {
- t.Parallel()
-
- fn := memoize.MemoizedCompareHashAndPassword()
- hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("test"), bcrypt.DefaultCost)
- actual := fn(hashedPassword, []byte("test"))
- expected := bcrypt.CompareHashAndPassword(hashedPassword, []byte("test"))
- assert.Equal(t, actual, expected)
-
- s1 := time.Now()
-
- for i := 0; i < 10; i++ {
- _ = bcrypt.CompareHashAndPassword(hashedPassword, []byte("test"))
- }
-
- d1 := time.Since(s1)
-
- s2 := time.Now()
-
- for i := 0; i < 10; i++ {
- _ = fn(hashedPassword, []byte("test"))
- }
-
- d2 := time.Since(s2)
-
- assert.Less(t, d2.Nanoseconds(), d1.Nanoseconds())
-}
diff --git a/pkg/metrics/m.go b/pkg/metrics/m.go
deleted file mode 100644
index 1c3d5886..00000000
--- a/pkg/metrics/m.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package metrics
-
-// Metrics is an interface used to capture metrics in soteria.
-type Metrics interface {
- // ObserveStatusCode is the method for status code metrics
- ObserveStatusCode(api, serviceName, function string, code int)
-
- // ObserveStatus is the method for the status of the done operations
- ObserveStatus(api, serviceName, function, status, info string)
-
- // ObserveResponseTime is the method for times takes by a specific function
- ObserveResponseTime(api, serviceName, function string, time float64)
-}
diff --git a/pkg/metrics/prom.go b/pkg/metrics/prom.go
deleted file mode 100644
index 87ebe700..00000000
--- a/pkg/metrics/prom.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package metrics
-
-import (
- "github.com/prometheus/client_golang/prometheus"
- "strconv"
-)
-
-// Handler is a implementation `Metrics`
-type Handler struct {
- StatusCodeCounterVec *prometheus.CounterVec
- StatusCounterVec *prometheus.CounterVec
- ResponseTimeVec *prometheus.SummaryVec
-}
-
-func (h *Handler) ObserveStatusCode(api, serviceName, function string, code int) {
- h.StatusCodeCounterVec.With(prometheus.Labels{
- "api": api,
- "service": serviceName,
- "function": function,
- "code": strconv.Itoa(code),
- }).Inc()
-}
-
-func (h *Handler) ObserveStatus(api, serviceName, function, status, info string) {
- h.StatusCounterVec.With(prometheus.Labels{
- "api": api,
- "service": serviceName,
- "function": function,
- "status": status,
- "info": info,
- }).Inc()
-}
-
-func (h *Handler) ObserveResponseTime(api, serviceName, function string, time float64) {
- h.ResponseTimeVec.With(prometheus.Labels{
- "api": api,
- "service": serviceName,
- "function": function,
- }).Observe(time)
-}
diff --git a/pkg/prettify.go b/pkg/prettify.go
deleted file mode 100644
index d59b55d3..00000000
--- a/pkg/prettify.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package pkg
-
-import "encoding/json"
-
-func PrettifyStruct(i interface{}) string {
- s, _ := json.MarshalIndent(i, "", "\t")
- return string(s)
-}
diff --git a/pkg/tracer/tracer.go b/pkg/tracer/tracer.go
deleted file mode 100644
index 3f49bcc5..00000000
--- a/pkg/tracer/tracer.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package tracer
-
-import (
- "fmt"
- "io"
-
- "github.com/opentracing/opentracing-go"
- jaegerConf "github.com/uber/jaeger-client-go/config"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/config"
-)
-
-// New receives a `configs.TracerConfig` and returns a `opentracing.Tracer` and a `io.Closer` and a `error` if there was one
-func New(cfg *config.TracerConfig) (opentracing.Tracer, io.Closer, error) {
- trc, cl, err := jaegerConf.Configuration{
- ServiceName: cfg.ServiceName,
- Disabled: !cfg.Enabled,
- Sampler: &jaegerConf.SamplerConfig{
- Type: cfg.SamplerType,
- Param: cfg.SamplerParam,
- },
- Reporter: &jaegerConf.ReporterConfig{
- LogSpans: true,
- LocalAgentHostPort: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
- },
- }.NewTracer()
-
- if err != nil {
- return nil, nil, fmt.Errorf("failed to create new tracer: %w", err)
- }
-
- return trc, cl, nil
-}
diff --git a/pkg/user/user.go b/pkg/user/user.go
deleted file mode 100644
index 96d57944..00000000
--- a/pkg/user/user.go
+++ /dev/null
@@ -1,80 +0,0 @@
-package user
-
-import (
- "time"
-
- "github.com/google/uuid"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/db"
- "gitlab.snapp.ir/dispatching/soteria/v3/internal/topics"
- "gitlab.snapp.ir/dispatching/soteria/v3/pkg/acl"
-)
-
-// Type indicates user type which is herald, emq and staff.
-type Type string
-
-const (
- HeraldUser Type = "HeraldUser"
- EMQUser Type = "EMQUser"
- Staff Type = "Staff"
-)
-
-// Issuer indicate issuers.
-type Issuer string
-
-const (
- Driver Issuer = "0"
- Passenger Issuer = "1"
- ThirdParty Issuer = "100"
-)
-
-// User is Soteria's users db model.
-type User struct {
- MetaData db.MetaData `json:"meta_data"`
- Username string `json:"username"`
- Password string `json:"password"`
- Type Type `json:"type"`
- IPs []string `json:"ips"`
- Secret string `json:"secret"`
- TokenExpirationDuration time.Duration `json:"token_expiration_duration"`
- Rules []Rule `json:"rules"`
-}
-
-// Rule tells about a access to a specific topic or endpoint.
-type Rule struct {
- UUID uuid.UUID `json:"uuid"`
- Endpoint string `json:"endpoint"`
- Topic topics.Type `json:"topic"`
- AccessType acl.AccessType `json:"access_type"`
-}
-
-// GetMetadata is for getting metadata of a user model such as date created.
-func (u User) GetMetadata() db.MetaData {
- return u.MetaData
-}
-
-// GetPrimaryKey is for knowing a model primary key.
-func (u User) GetPrimaryKey() string {
- return u.Username
-}
-
-// CheckTopicAllowance checks whether the user is allowed to pub/sub/pubsub to a topic or not.
-func (u User) CheckTopicAllowance(topic topics.Type, accessType acl.AccessType) bool {
- for _, rule := range u.Rules {
- if rule.Topic == topic && (rule.AccessType == acl.PubSub || rule.AccessType == accessType) {
- return true
- }
- }
-
- return false
-}
-
-// CheckEndpointAllowance checks whether the user is allowed to use a Herald endpoint or not.
-func (u User) CheckEndpointAllowance(endpoint string) bool {
- for _, rule := range u.Rules {
- if rule.Endpoint == endpoint && rule.AccessType == acl.Pub {
- return true
- }
- }
-
- return false
-}
diff --git a/pkg/validator/client.go b/pkg/validator/client.go
new file mode 100644
index 00000000..b8980711
--- /dev/null
+++ b/pkg/validator/client.go
@@ -0,0 +1,176 @@
+package validator
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "strings"
+ "time"
+)
+
+const (
+ ServiceNameHeader = "X-Service-Name"
+
+ validateURI = "/api/v3/internal/validate"
+ authHeader = "Authorization"
+ userDataHeader = "X-User-Data"
+ modeQueryParam = "mode"
+)
+
+var (
+ ErrEmptyServiceName = errors.New("x-service-name can not be empty")
+ ErrInvalidJWT = errors.New("invalid jwt")
+ ErrInvalidUserDataHeader = errors.New("invalid X-User-Data header")
+ ErrRequestFailed = errors.New("validator request failed")
+)
+
+type Client struct {
+ baseURL string
+ client *http.Client
+ timeout time.Duration
+ isOptional bool
+}
+
+type Payload struct {
+ IAT int `json:"iat"`
+ Aud string `json:"aud"`
+ Iss int `json:"iss"`
+ Sub string `json:"sub"`
+ UserID int `json:"user_id"`
+ Email string `json:"email"`
+ Exp int `json:"exp"`
+ Locale string `json:"locale"`
+ Sid string `json:"sid"`
+}
+
+// New creates a new Client with default attributes.
+func New(url string, timeout time.Duration) Client {
+ return Client{
+ baseURL: url,
+ client: new(http.Client),
+ timeout: timeout,
+ isOptional: false,
+ }
+}
+
+// WithOptionalValidate enables you to bypass the signature validation.
+// If the validation of the JWT signature is optional for you, and you just want to extract
+// the payload from the token, you can use the client in `WithOptionalValidate` mode.
+func (c *Client) WithOptionalValidate() {
+ c.isOptional = true
+}
+
+// Validate gets the parent context, headers, and JWT token and calls the validate API of the JWT validator service.
+// The parent context is helpful in canceling the process in the upper hand (a function that used the SDK) and in case
+// you have something like tracing spans in your context and want to extend these things in your custom HTTP handler.
+// Otherwise, you can use `context.Background()`.
+// The headers argument is used when you want to pass some headers like user-agent,
+// X-Service-Name, X-App-Name, X-App-Version and
+// X-App-Version-Code to the validator. It is extremely recommended to pass these headers (if you have them) because
+// it increases the visibility in the logs and metrics of the JWT Validator service.
+// You must place your Authorization header content in the bearerToken argument.
+// Consider that the bearerToken must contain Bearer keyword and JWT.
+// For `X-Service-Name` you should put your project/service name in this header.
+// nolint: funlen, cyclop
+func (c *Client) Validate(parentCtx context.Context, headers http.Header, bearerToken string) (*Payload, error) {
+ if headers.Get(ServiceNameHeader) == "" {
+ return nil, ErrEmptyServiceName
+ }
+
+ segments := strings.Split(bearerToken, " ")
+ if len(segments) < 2 || strings.ToLower(segments[0]) != "bearer" {
+ return nil, ErrInvalidJWT
+ }
+
+ ctx, cancel := context.WithTimeout(parentCtx, c.timeout)
+ defer cancel()
+
+ url := c.baseURL + validateURI
+
+ request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
+ if err != nil {
+ return nil, fmt.Errorf("validator creating request failed %w", err)
+ }
+
+ request.Header = headers
+ request.Header.Set(authHeader, bearerToken)
+
+ query := request.URL.Query()
+ if c.isOptional {
+ query.Add(modeQueryParam, "optional")
+ }
+
+ request.URL.RawQuery = query.Encode()
+
+ response, err := c.client.Do(request)
+ if err != nil {
+ return nil, fmt.Errorf("validator sending request failed %w", err)
+ }
+
+ closeBody(response)
+
+ if response.StatusCode != http.StatusOK {
+ return nil, ErrRequestFailed
+ }
+
+ userDataHeader := response.Header.Get(userDataHeader)
+ if userDataHeader == "" {
+ return nil, ErrInvalidJWT
+ }
+
+ userData := map[string]interface{}{}
+
+ if err := json.Unmarshal([]byte(userDataHeader), &userData); err != nil {
+ return nil, fmt.Errorf("X-User-Data header unmarshal failed: %w", err)
+ }
+
+ payload := new(Payload)
+ if iat, ok := userData["iat"].(float64); ok {
+ payload.IAT = int(iat)
+ }
+
+ if aud, ok := userData["aud"].(string); ok {
+ payload.Aud = aud
+ }
+
+ if iss, ok := userData["iss"].(float64); ok {
+ payload.Iss = int(iss)
+ }
+
+ if sub, ok := userData["sub"].(string); ok {
+ payload.Sub = sub
+ }
+
+ if userID, ok := userData["user_id"].(float64); ok {
+ payload.UserID = int(userID)
+ }
+
+ if email, ok := userData["email"].(string); ok {
+ payload.Email = email
+ }
+
+ if exp, ok := userData["exp"].(float64); ok {
+ payload.Exp = int(exp)
+ }
+
+ if locale, ok := userData["locale"].(string); ok {
+ payload.Locale = locale
+ }
+
+ if sid, ok := userData["sid"].(string); ok {
+ payload.Sid = sid
+ }
+
+ return payload, nil
+}
+
+// closeBody to avoid memory leak when reusing http connection.
+func closeBody(response *http.Response) {
+ if response != nil {
+ _, _ = io.Copy(io.Discard, response.Body)
+ _ = response.Body.Close()
+ }
+}
diff --git a/scripts/cli/.gitignore b/scripts/cli/.gitignore
deleted file mode 100644
index 57feb1a3..00000000
--- a/scripts/cli/.gitignore
+++ /dev/null
@@ -1,152 +0,0 @@
-
-# Created by https://www.toptal.com/developers/gitignore/api/python
-# Edit at https://www.toptal.com/developers/gitignore?templates=python
-
-### Python ###
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-parts/
-sdist/
-var/
-wheels/
-pip-wheel-metadata/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-pytestdebug.log
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-doc/_build/
-
-# PyBuilder
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-.python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# poetry
-#poetry.lock
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-# .env
-.env/
-.venv/
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-pythonenv*
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# operating system-related files
-*.DS_Store #file properties cache/storage on macOS
-Thumbs.db #thumbnail cache on Windows
-
-# profiling data
-.prof
-
-
-# End of https://www.toptal.com/developers/gitignore/api/python
diff --git a/scripts/cli/acl.py b/scripts/cli/acl.py
deleted file mode 100644
index 11748df6..00000000
--- a/scripts/cli/acl.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import requests
-
-from vars import DRIVER_TOKEN, PUBLISH
-
-base_url = "https://soteria-snapp-ode-012.apps.private.teh-1.snappcloud.io"
-acl_url = base_url + "/acl"
-
-
-res = requests.post(
- acl_url,
- data={
- "access": PUBLISH,
- "token": DRIVER_TOKEN,
- "topic": "snapp/driver/1234/location",
- },
-)
-
-print(res.content)
diff --git a/scripts/cli/auth.py b/scripts/cli/auth.py
deleted file mode 100644
index 0131e60b..00000000
--- a/scripts/cli/auth.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import requests
-
-from vars import DRIVER_TOKEN
-
-base_url = "https://soteria-snapp-ode-012.apps.private.teh-1.snappcloud.io"
-auth_url = base_url + "/auth"
-
-
-res = requests.post(
- auth_url,
- data={
- "token": DRIVER_TOKEN,
- },
-)
-
-print(res.content)
diff --git a/scripts/cli/main.py b/scripts/cli/main.py
deleted file mode 100644
index 7d2651fd..00000000
--- a/scripts/cli/main.py
+++ /dev/null
@@ -1,267 +0,0 @@
-import pprint
-
-import click
-
-import soteria
-
-pp = pprint.PrettyPrinter(indent=4)
-
-
-@click.group()
-@click.option("--base", "-b", required=True, type=str)
-@click.pass_context
-def cli(ctx, base):
- ctx.ensure_object(dict)
-
- ctx.obj["BASE"] = base
-
-
-@cli.command()
-@click.option(
- "--username",
- "-u",
- required=True,
- type=str,
- help="account username e.g. driver",
-)
-@click.option(
- "--password",
- "-p",
- required=True,
- type=str,
- help="account password e.g. password",
-)
-@click.pass_context
-def show(ctx, username: str, password: str):
- account_manager = soteria.AccountManager(ctx.obj["BASE"])
- try:
- resp = account_manager.show(username, password)
- click.echo(pp.pformat(resp))
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-
-@cli.command()
-@click.option(
- "--username",
- "-u",
- required=True,
- type=str,
- help="account username e.g. driver",
-)
-@click.option(
- "--password",
- "-p",
- required=True,
- type=str,
- help="account password e.g. password",
-)
-@click.option(
- "--user-type",
- "-t",
- required=True,
- type=click.Choice(["herald", "emq", "staff"]),
-)
-@click.pass_context
-def new(ctx, username: str, password: str, user_type: str):
- account_manager = soteria.AccountManager(ctx.obj["BASE"])
- try:
- resp = account_manager.new(username, password, user_type)
- click.echo(pp.pformat(resp))
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-
-@cli.command()
-@click.option(
- "--username",
- "-u",
- required=True,
- type=str,
- help="account username e.g. driver",
-)
-@click.option(
- "--password",
- "-p",
- required=True,
- type=str,
- help="account password e.g. password",
-)
-@click.option(
- "--access-type",
- "-a",
- required=True,
- type=click.Choice(["pub", "sub", "pubsub"]),
-)
-@click.option(
- "--topic",
- "-t",
- required=True,
- type=str,
- help="topic regular expressions are defined in soteria and has their name"
- "so please refer to soteria",
-)
-@click.pass_context
-def rules_add(ctx, username: str, password: str, access_type: str, topic: str):
- account_manager = soteria.AccountManager(ctx.obj["BASE"])
- try:
- resp = account_manager.add_rule(username, password, topic, access_type)
- click.echo(pp.pformat(resp))
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-
-@cli.command()
-@click.option(
- "--username",
- "-u",
- required=True,
- type=str,
- help="account username e.g. driver",
-)
-@click.option(
- "--password",
- "-p",
- required=True,
- type=str,
- help="account password e.g. password",
-)
-@click.option(
- "--expire",
- "-e",
- required=True,
- type=int,
- help="token expiration time in nanoseconds",
-)
-@click.pass_context
-def set_expire(
- ctx,
- username: str,
- password: str,
- expire: int,
-):
- account_manager = soteria.AccountManager(ctx.obj["BASE"])
- try:
- resp = account_manager.set_expiration(username, password, expire)
- click.echo(pp.pformat(resp))
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-
-@cli.command()
-@click.option(
- "--username",
- "-u",
- required=True,
- type=str,
- help="account username e.g. driver",
-)
-@click.option(
- "--password",
- "-p",
- required=True,
- type=str,
- help="account password e.g. password",
-)
-@click.option(
- "--secret",
- "-s",
- required=True,
- type=str,
- help="account secret which is different from password and"
- "is used to generate token",
-)
-@click.pass_context
-def set_secret(
- ctx,
- username: str,
- password: str,
- secret: str,
-):
- account_manager = soteria.AccountManager(ctx.obj["BASE"])
- try:
- resp = account_manager.set_secret(username, password, secret)
- click.echo(pp.pformat(resp))
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-
-@cli.command()
-@click.option(
- "--username",
- "-u",
- required=True,
- type=str,
- help="account username e.g. driver",
-)
-@click.option(
- "--secret",
- "-s",
- required=True,
- type=str,
- help="account secret which is different from password and"
- "is used to generate token",
-)
-@click.option(
- "--grant-type",
- "-t",
- required=True,
- type=click.Choice(["pub", "sub", "pubsub"]),
-)
-@click.pass_context
-def token(
- ctx,
- username: str,
- secret: str,
- grant_type: str,
-):
- account_manager = soteria.AccountManager(ctx.obj["BASE"])
- try:
- token = account_manager.token(username, secret, grant_type)
- click.echo(token)
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-@cli.command()
-@click.option(
- "--username",
- "-u",
- required=True,
- type=str,
- help="account username e.g. driver",
-)
-@click.option(
- "--password",
- "-p",
- required=True,
- type=str,
- help="account password e.g. password",
-)
-@click.option(
- "--duration",
- "-d",
- required=True,
- type=int,
- help="token expiration time in nanoseconds",
-)
-@click.pass_context
-def superuser(ctx, username: str, password: str, duration: int):
- emq_store = soteria.EMQStore(ctx.obj["BASE"])
- try:
- resp = emq_store.new(username, password, duration)
- click.echo(pp.pformat(resp))
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-@cli.command()
-@click.pass_context
-def superusers(ctx):
- emq_store = soteria.EMQStore(ctx.obj["BASE"])
- try:
- resp = emq_store.get()
- click.echo(pp.pformat(resp))
- except Exception as exep:
- raise click.ClickException(str(exep))
-
-if __name__ == "__main__":
- cli(obj={})
diff --git a/scripts/cli/poetry.lock b/scripts/cli/poetry.lock
deleted file mode 100644
index 36844897..00000000
--- a/scripts/cli/poetry.lock
+++ /dev/null
@@ -1,217 +0,0 @@
-[[package]]
-name = "certifi"
-version = "2021.5.30"
-description = "Python package for providing Mozilla's CA Bundle."
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "chardet"
-version = "4.0.0"
-description = "Universal encoding detector for Python 2 and 3"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "click"
-version = "8.0.1"
-description = "Composable command line interface toolkit"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-colorama = {version = "*", markers = "platform_system == \"Windows\""}
-
-[[package]]
-name = "colorama"
-version = "0.4.4"
-description = "Cross-platform colored terminal text."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[[package]]
-name = "dacite"
-version = "1.6.0"
-description = "Simple creation of data classes from dictionaries."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.extras]
-dev = ["pytest (>=5)", "pytest-cov", "coveralls", "black", "mypy", "pylint"]
-
-[[package]]
-name = "idna"
-version = "2.10"
-description = "Internationalized Domain Names in Applications (IDNA)"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
-
-[[package]]
-name = "mypy"
-version = "0.910"
-description = "Optional static typing for Python"
-category = "main"
-optional = false
-python-versions = ">=3.5"
-
-[package.dependencies]
-mypy-extensions = ">=0.4.3,<0.5.0"
-toml = "*"
-typing-extensions = ">=3.7.4"
-
-[package.extras]
-dmypy = ["psutil (>=4.0)"]
-python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
-
-[[package]]
-name = "mypy-extensions"
-version = "0.4.3"
-description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "requests"
-version = "2.25.1"
-description = "Python HTTP for Humans."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
-
-[package.dependencies]
-certifi = ">=2017.4.17"
-chardet = ">=3.0.2,<5"
-idna = ">=2.5,<3"
-urllib3 = ">=1.21.1,<1.27"
-
-[package.extras]
-security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
-socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
-
-[[package]]
-name = "requests-toolbelt"
-version = "0.9.1"
-description = "A utility belt for advanced users of python-requests"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-requests = ">=2.0.1,<3.0.0"
-
-[[package]]
-name = "toml"
-version = "0.10.2"
-description = "Python Library for Tom's Obvious, Minimal Language"
-category = "main"
-optional = false
-python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
-
-[[package]]
-name = "typing-extensions"
-version = "3.10.0.0"
-description = "Backported and Experimental Type Hints for Python 3.5+"
-category = "main"
-optional = false
-python-versions = "*"
-
-[[package]]
-name = "urllib3"
-version = "1.26.6"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
-
-[package.extras]
-brotli = ["brotlipy (>=0.6.0)"]
-secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
-
-[metadata]
-lock-version = "1.1"
-python-versions = "^3.9"
-content-hash = "aafe737381b5e1135e4a1554f4c893eb297ef5428f84f75e6c5b5cd3006a5c66"
-
-[metadata.files]
-certifi = [
- {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
- {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
-]
-chardet = [
- {file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
- {file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
-]
-click = [
- {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
- {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
-]
-colorama = [
- {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
- {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
-]
-dacite = [
- {file = "dacite-1.6.0-py3-none-any.whl", hash = "sha256:4331535f7aabb505c732fa4c3c094313fc0a1d5ea19907bf4726a7819a68b93f"},
- {file = "dacite-1.6.0.tar.gz", hash = "sha256:d48125ed0a0352d3de9f493bf980038088f45f3f9d7498f090b50a847daaa6df"},
-]
-idna = [
- {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
- {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
-]
-mypy = [
- {file = "mypy-0.910-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457"},
- {file = "mypy-0.910-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb"},
- {file = "mypy-0.910-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9"},
- {file = "mypy-0.910-cp35-cp35m-win_amd64.whl", hash = "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e"},
- {file = "mypy-0.910-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921"},
- {file = "mypy-0.910-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6"},
- {file = "mypy-0.910-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212"},
- {file = "mypy-0.910-cp36-cp36m-win_amd64.whl", hash = "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885"},
- {file = "mypy-0.910-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0"},
- {file = "mypy-0.910-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de"},
- {file = "mypy-0.910-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703"},
- {file = "mypy-0.910-cp37-cp37m-win_amd64.whl", hash = "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a"},
- {file = "mypy-0.910-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504"},
- {file = "mypy-0.910-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9"},
- {file = "mypy-0.910-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072"},
- {file = "mypy-0.910-cp38-cp38-win_amd64.whl", hash = "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811"},
- {file = "mypy-0.910-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e"},
- {file = "mypy-0.910-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b"},
- {file = "mypy-0.910-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2"},
- {file = "mypy-0.910-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97"},
- {file = "mypy-0.910-cp39-cp39-win_amd64.whl", hash = "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8"},
- {file = "mypy-0.910-py3-none-any.whl", hash = "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d"},
- {file = "mypy-0.910.tar.gz", hash = "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150"},
-]
-mypy-extensions = [
- {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
- {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
-]
-requests = [
- {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
- {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
-]
-requests-toolbelt = [
- {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"},
- {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"},
-]
-toml = [
- {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
- {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
-]
-typing-extensions = [
- {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
- {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
- {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
-]
-urllib3 = [
- {file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
- {file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
-]
diff --git a/scripts/cli/pyproject.toml b/scripts/cli/pyproject.toml
deleted file mode 100644
index 0063ee33..00000000
--- a/scripts/cli/pyproject.toml
+++ /dev/null
@@ -1,19 +0,0 @@
-[tool.poetry]
-name = "cli"
-version = "0.1.0"
-description = "soteria command line client"
-authors = ["Parham Alvani "]
-
-[tool.poetry.dependencies]
-python = "^3.9"
-requests = "*"
-click = "*"
-requests-toolbelt = "*"
-dacite = "*"
-
-[tool.poetry.dev-dependencies]
-mypy = "^0.910"
-
-[build-system]
-requires = ["poetry-core>=1.0.0"]
-build-backend = "poetry.core.masonry.api"
diff --git a/scripts/cli/soteria/__init__.py b/scripts/cli/soteria/__init__.py
deleted file mode 100644
index c6fb535a..00000000
--- a/scripts/cli/soteria/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from .account_manager import *
-from .emq_store import *
diff --git a/scripts/cli/soteria/account.py b/scripts/cli/soteria/account.py
deleted file mode 100644
index 41923a22..00000000
--- a/scripts/cli/soteria/account.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import dataclasses
-import typing
-
-from .rule import Rule
-
-
-@dataclasses.dataclass
-class Account:
- password: str
- rules: typing.Optional[typing.List[Rule]]
- secret: str
- type: str
- username: str
- token_expiration_duration: int
diff --git a/scripts/cli/soteria/account_manager.py b/scripts/cli/soteria/account_manager.py
deleted file mode 100644
index c9aa575a..00000000
--- a/scripts/cli/soteria/account_manager.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import dacite
-from requests_toolbelt import sessions
-
-from .account import Account
-from .rule import Rule
-
-
-class AccountManager:
- user_types = {
- "herald": "HeraldUser",
- "emq": "EMQUser",
- "staff": "Staff",
- }
-
- grant_types = {
- "sub": "1",
- "pub": "2",
- "pubsub": "3",
- }
-
- def __init__(self, base_url: str):
- self.session = sessions.BaseUrlSession(base_url=base_url)
-
- def show(self, username: str, password: str) -> Account:
- res = self.session.get(
- f"accounts/{username}", auth=(username, password)
- ).json()
-
- if res["data"] is not None:
- return dacite.from_dict(data_class=Account, data=res["data"])
- raise Exception(res["message"])
-
- def new(self, username: str, password: str, user_type: str):
- res = self.session.post(
- "accounts/",
- json={
- "username": username,
- "password": password,
- "user_type": self.user_types[user_type],
- },
- )
- return res.json()
-
- def add_rule(
- self, username: str, password: str, topic: str, access_type: str
- ) -> Rule:
- res = self.session.post(
- f"accounts/{username}/rules",
- json={
- "topic": topic,
- "access_type": self.grant_types[access_type],
- },
- auth=(username, password),
- ).json()
-
- if res["data"] is not None:
- return dacite.from_dict(data_class=Rule, data=res["data"])
- raise Exception(res["message"])
-
- def set_secret(self, username: str, password: str, secret: str):
- res = self.session.put(
- f"accounts/{username}",
- json={
- "secret": secret,
- },
- auth=(username, password),
- )
- return res.json()
-
- def set_expiration(self, username: str, password: str, expiration: int):
- res = self.session.put(
- f"accounts/{username}",
- json={
- "token_expiration": expiration,
- },
- auth=(username, password),
- )
- return res.json()
-
- def token(self, username: str, secret: str, grant_type: str) -> bytes:
- """
- Generates a token from soteria.
- """
- res = self.session.post(
- "token",
- json={
- "client_id": username,
- "client_secret": secret,
- "grant_type": self.grant_types[grant_type],
- },
- )
- return res.content
diff --git a/scripts/cli/soteria/emq_store.py b/scripts/cli/soteria/emq_store.py
deleted file mode 100644
index f998b90c..00000000
--- a/scripts/cli/soteria/emq_store.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from requests_toolbelt import sessions
-
-
-class EMQStore:
- def __init__(self, base_url: str):
- self.session = sessions.BaseUrlSession(base_url=base_url)
-
- def new(self, username: str, password: str, duration: int):
- res = self.session.post(
- "emq/",
- json={
- "username": username,
- "password": password,
- "duration": duration,
- },
- )
- return res.json()
-
- def get(self):
- res = self.session.get(
- "emq/",
- )
- return res.json()
diff --git a/scripts/cli/soteria/rule.py b/scripts/cli/soteria/rule.py
deleted file mode 100644
index 7cf507d3..00000000
--- a/scripts/cli/soteria/rule.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import dataclasses
-
-
-@dataclasses.dataclass
-class Rule:
- access_type: str
- endpoint: str
- topic: str
- uuid: str
diff --git a/scripts/cli/vars.py b/scripts/cli/vars.py
deleted file mode 100644
index 035b30cb..00000000
--- a/scripts/cli/vars.py
+++ /dev/null
@@ -1,11 +0,0 @@
-
-COLONY_TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjMzMDI3ODM0MjcsImlzcyI6IjEwMCIsInN1YiI6ImNvbG9ueS1zdWJzY3JpYmVyIn0.BChKcMUDFJ_GP9jdd2vl5GtDmtO0ha0EnLsmt4dgIwkxom8LQkpQD1sfAeEw53iClV8RYJOo_qLIdy1M0X7G26ePQuxaqi9FHpIvpM83w6BazINavM3SUZZmvhvoZN412eTwzjSPWr3v6OhJ-l-Lv7HUet7BOZ6D4IUKC3jjchHrJFXE9NKeBz886HE20y6iY5YIeuOqha-bfON3fzD5HcUs3WsvqcPNCL2AydkeafjS0k8SOY3m6vV5QQWGoIL7rRSDKbUf7KJXXs6BV3LFr3LblDxqh9P0zVUaKN8AQf4KnoqJJG8Awxab3OqTioV3OdWDiSuPQkl2s03BW1E-Xw'
-DRIVER_TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDU4MzQwNjksImlzcyI6IjEwMCIsInN1YiI6ImNvbG9ueS1zdWJzY3JpYmVyIn0.hM3o2Tt2Q6-kshNT43YQh9LMzDZxbYlf11g9XcLkmrsuYokatM1iT1-IsKpcT2wiWQVkf5APtFxyzEYUMB2VGSbzl-C3kFbMicvlpT8fIpOlfo2FzOg_Ey8pLq-qnbWmxW7Mii6bS4m208OH15FM8-25f0Kmv-64TFHpZqpqpZI'
-PASSENGER_TOKEN = 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDU4MzQwNjksImlzcyI6IjEwMCIsInN1YiI6ImNvbG9ueS1zdWJzY3JpYmVyIn0.hM3o2Tt2Q6-kshNT43YQh9LMzDZxbYlf11g9XcLkmrsuYokatM1iT1-IsKpcT2wiWQVkf5APtFxyzEYUMB2VGSbzl-C3kFbMicvlpT8fIpOlfo2FzOg_Ey8pLq-qnbWmxW7Mii6bS4m208OH15FM8-25f0Kmv-64TFHpZqpqpZI'
-
-
-SUBSCRIE = '1'
-PUBLISH = '2'
-PUBLISH_SUBSCRIE = '3'
-
-
diff --git a/test/100.pem b/test/100.pem
deleted file mode 100644
index ad20ae77..00000000
--- a/test/100.pem
+++ /dev/null
@@ -1,6 +0,0 @@
------BEGIN PUBLIC KEY-----
-MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8
-OWWzUDxlG2kL+0WxHh8/+76ngIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0Ytu
-GgmGuuVTaD27G2inwgFDabUsAKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0o
-Wsupn2HVhMSjFbE6dwIDAQAB
------END PUBLIC KEY-----
\ No newline at end of file
diff --git a/test/100.private.pem b/test/100.private.pem
deleted file mode 100644
index 467519f4..00000000
--- a/test/100.private.pem
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICWwIBAAKBgQCTxwVGmWzJNOaZnBRL+9/V1Mc8OWWzUDxlG2kL+0WxHh8/+76n
-gIvp7YsHHxUadR0iY6NkzLgFv+sBkKh523Nx0YtuGgmGuuVTaD27G2inwgFDabUs
-AKISBbn6d6jwlkfk3D+O6h5pdLqoI64Zq/NPmD0oWsupn2HVhMSjFbE6dwIDAQAB
-AoGAJDRGHp3IASNsu4V5k4QJuqF+jkqhl+S4Zyzn939/+3ydu1c5xl+/53fC7+O1
-j93RXXN7vF5LV11FfgSqwe/5wDEcAtyLXWPCHtLB1CIYFfqHxgwOQm4gQSyOgBnP
-hMccyv0VCDK23+Mk37lLkuON6xXMC8+IUq5UW/aadxkeqoECQQDU9Z5IFCmp3Ibs
-hJXx/x+ZQI+gLj0hh71pQEO2zqhu19i+M62KKnDb7k2OtK9IJR2ucU+YE3SXGShh
-1sPmgfVfAkEAsaTtZ1oZmszOs5TPYhs2e7LKLPNADZ36GVsG2h1FHVX+LyNkwk/E
-pn+FV3Dd1vkzxG/a3znEKz+2BJiz4vF56QJATUxKA4euB8XQA5Gsi4Y7Bfl1KIMg
-FUeb7NQyv+wLHxChz4gaeYgmJu48oIvdA6bVOzhN17lYHHA5RCocOVL6qQJAdHdT
-6nmo5dO3BQfgO0rqGolqgbPtX8AeE3eZc3DTOluBrbf/vGF95UcfzedCmkmBxh0r
-m0SNN2mq1TKkZXq52QJAIjxhG7spkOSZJQnVBA9TfLYZFM/aUzDpLxflvFAuJuqJ
-l+PFsbek2Zl6fgy294dXRvQhp1PJryngDaXTBiWK6g==
------END RSA PRIVATE KEY-----
\ No newline at end of file
diff --git a/test/0.pem b/test/snapp-0.pem
similarity index 100%
rename from test/0.pem
rename to test/snapp-0.pem
diff --git a/test/0.private.pem b/test/snapp-0.private.pem
similarity index 100%
rename from test/0.private.pem
rename to test/snapp-0.private.pem
diff --git a/test/1.pem b/test/snapp-1.pem
similarity index 100%
rename from test/1.pem
rename to test/snapp-1.pem
diff --git a/test/1.private.pem b/test/snapp-1.private.pem
similarity index 100%
rename from test/1.private.pem
rename to test/snapp-1.private.pem
diff --git a/test/snapp-admin.pem b/test/snapp-admin.pem
new file mode 100644
index 00000000..c73d6089
--- /dev/null
+++ b/test/snapp-admin.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1lNRwyNsDieWs6LvHOJ+
+GyehhRC4Pn5yL5edKP3565F3LtRDMrkzwDRsQbqnUtTea9HCdTdBv+lI8vE17qRi
+RQn10IMaIH6e4Aa3OWNClFhuqNOag7VmffsjTOgxHgHpfGAKVF/4BwqOHrdHFbAD
+VOiWB1hv9Uc0C5laffGAub7fj+EAI02zlrsNDxYW8vyF2H47N7VWcvgd3RhZpxlG
+8bq9phl7Ja55YmQiT2Ic3/K5tsazg5z9lz6OTrx+JvWbefHFlJpjCLz5yefEaRmX
+9L/zyDMi4jgFTZEWNXC2vIrxwZMFwFhBXEp0PcCbuHJgJIucbRrbwukQC16uHJwP
+zQIDAQAB
+-----END PUBLIC KEY-----
\ No newline at end of file
diff --git a/test/snapp-admin.private.pem b/test/snapp-admin.private.pem
new file mode 100644
index 00000000..bffadbc9
--- /dev/null
+++ b/test/snapp-admin.private.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDWU1HDI2wOJ5az
+ou8c4n4bJ6GFELg+fnIvl50o/fnrkXcu1EMyuTPANGxBuqdS1N5r0cJ1N0G/6Ujy
+8TXupGJFCfXQgxogfp7gBrc5Y0KUWG6o05qDtWZ9+yNM6DEeAel8YApUX/gHCo4e
+t0cVsANU6JYHWG/1RzQLmVp98YC5vt+P4QAjTbOWuw0PFhby/IXYfjs3tVZy+B3d
+GFmnGUbxur2mGXslrnliZCJPYhzf8rm2xrODnP2XPo5OvH4m9Zt58cWUmmMIvPnJ
+58RpGZf0v/PIMyLiOAVNkRY1cLa8ivHBkwXAWEFcSnQ9wJu4cmAki5xtGtvC6RAL
+Xq4cnA/NAgMBAAECggEBAKC93JR9/IyhJGWVzb/cHrg/AOTLpWM5cmo/S59y2/3R
+G5IDoGJqhGWi645gbx2wiSBUMYO37ZgBXrTWM5zHrOwajEHWEcJNNNrQLprb1xNr
+P5NfIIOniTbzI4aKnkvjIzokzZp6X4roX66pxqZ9XAJSbjMUIOPUgzQvz66lceXb
+4aKXSEPdBIRQRmwEUhOV3W8aXNDULR+LwPRt1PYxRm1+W0OPtOwnjfSxBdTbFf8+
+Lqsg92/kI/qfPnFq0LsbIdCF9yS/0GhmmJZNErwGl320SF2oXJXKiK2PXKj0mbPV
+LLXOMvEkbwlD+6Nbgz3vIrTdcJXc0TfawZOwvin3AAECgYEA+s5Wl+qS+AEP2gPS
+w0J/KqXA22jYN/bBXwZInBBFM7lugmX1A1OGh3Sbu7S1aGQI81OQ7aDSzhkeK2sI
+irKnnaLQKzXsalQZvPSG9Q6De2Tj9sN5N0eI7VdB72b+sz/NpI2IsRE6+pBAvEzJ
+YXFwH4mrj4SbHbcaUA6bhdkAP4UCgYEA2sOT2b8zsGk6JLrfq5CdbywdpT/xITdA
+PivmsRF2bQ9F2j2V1y0B+cgfGJLWJgN5iSwg7SGlalUcFs188JcTgfJB5XsPXH8E
+kNmD7fkfAoohn5d480fQRKuEboNa//F+zHq7yQVTcTnFsWctsXppv8A1ME7DNJrp
+c2mAEHVU7akCgYEAqmEL8G3ZY4MNKrTYM+9zhhxOFH94CySlHpGdN+/Rox7AVPNA
+a/8M7+4mcXCEoCL89Zf6Z4OOUZY8qZAvoFFXjr3xHrmmHmF9jqCrIcS3S1cxigwm
+x4fgHCPf1euo8UpRwAyqJGepIlhmscSUNY8jdTlIA9o4qgoeZO5XdqkBAyUCgYEA
+m+Ykx1hrDZzvwp0qKKm2iDN4LPuUa4dkUOoYTLeVHcN0lEKvNdjtP4ROJMT/t7di
+NU8tZ9BCgbSFf/qQvyPq0wBB1bgNCm26Yz+ftUeDwduOep0HpNfYpBdXGSqi/yKq
+qi4NBQS2okn5iKNu/LuwAOaJARQgKKz9ETJuAUycaYECgYA43841te0rJ81lNKE5
+wnCL/tx3OURDhSUJVf6tjwB+vo2FOIUfUJTBowzdWn5cTXCwGTp04JZBrZlY5RDH
+24Tzo+/PTIbH9jiSYtKJCwJ+umPxek3OuBQo+4zAejN55jwcPOJcadp6u4hwq6yy
+wpqfsglQldbuiZ0TilCRNhv00g==
+-----END PRIVATE KEY-----
\ No newline at end of file