diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 2b1c07a7d..a5caf4681 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -9,13 +9,6 @@ updates: - "neicnordic/sensitive-data-development-collaboration" # Each subfolder needs to be checked separately - - package-ecosystem: docker - directory: "/sda-auth" - schedule: - interval: weekly - open-pull-requests-limit: 10 - reviewers: - - "neicnordic/sensitive-data-development-collaboration" - package-ecosystem: docker directory: "/postgresql" schedule: @@ -54,18 +47,6 @@ updates: # Each subfolder needs to be checked separately - package-ecosystem: gomod - directory: "/sda-auth" - groups: - all-modules: - patterns: - - "*" - open-pull-requests-limit: 10 - reviewers: - - "neicnordic/sensitive-data-development-collaboration" - schedule: - interval: weekly - - - package-ecosystem: "gomod" directory: "/sda" groups: all-modules: diff --git a/.github/integration/sda-posix-integration.yml b/.github/integration/sda-posix-integration.yml index 33e1c3821..6e9915114 100644 --- a/.github/integration/sda-posix-integration.yml +++ b/.github/integration/sda-posix-integration.yml @@ -46,7 +46,7 @@ services: command: [ "python", - "/cega/users.py", + "/users.py", "0.0.0.0", "8443", "/shared/users.json" @@ -58,7 +58,7 @@ services: ports: - "8443:8443" volumes: - - ./sda/users.py:/cega/users.py + - ./sda/users.py:/users.py - certs:/certs - shared:/shared diff --git a/.github/integration/sda-s3-integration.yml b/.github/integration/sda-s3-integration.yml index 3c5671ae7..e753cc9fa 100644 --- a/.github/integration/sda-s3-integration.yml +++ b/.github/integration/sda-s3-integration.yml @@ -317,6 +317,65 @@ services: - ./sda/config.yaml:/config.yaml - shared:/shared + cega-nss: + container_name: cega-nss + depends_on: + credentials: + condition: service_completed_successfully + command: + [ + "python", + "/cega/users.py", + "0.0.0.0", + "8443", + "/shared/users.json" + ] + environment: + - CEGA_USERS_PASSWORD=test + - CEGA_USERS_USER=test + image: "egarchive/lega-base:release.v0.2.0" + ports: + - "8443:8443" + volumes: + - ./sda/users.py:/cega/users.py + - shared:/shared + + auth-cega: + command: [ sda-auth ] + container_name: auth-cega + depends_on: + cega-nss: + condition: service_started + environment: + - AUTH_RESIGNJWT=true + - AUTH_CEGA_ID=test + - AUTH_CEGA_SECRET=test + image: ghcr.io/neicnordic/sensitive-data-archive:PR${PR_NUMBER} + ports: + - "8888:8080" + restart: always + volumes: + - ./sda/config.yaml:/config.yaml + - shared:/shared + + auth-oidc: + command: [ sda-auth ] + container_name: auth-oidc + depends_on: + oidc: + condition: service_healthy + environment: + - AUTH_RESIGNJWT=false + - OIDC_ID=XC56EL11xx + - OIDC_SECRET=wHPVQaYXmdDHg + image: ghcr.io/neicnordic/sensitive-data-archive:PR${PR_NUMBER} + ports: + - "8889:8080" + restart: always + volumes: + - ./sda/config.yaml:/config.yaml + - shared:/shared + integration_test: container_name: tester command: @@ -324,6 +383,12 @@ services: - "/tests/run_scripts.sh" - "/tests/sda" depends_on: + auth-cega: + condition: service_started + auth-oidc: + condition: service_started + cega-nss: + condition: service_started credentials: condition: service_completed_successfully finalize: diff --git a/.github/integration/sda/config.yaml b/.github/integration/sda/config.yaml index 9b44e5d75..24cd95a86 100644 --- a/.github/integration/sda/config.yaml +++ b/.github/integration/sda/config.yaml @@ -10,6 +10,22 @@ archive: secretKey: "secretKey" bucket: "archive" region: "us-east-1" + +auth: + cega: + authUrl: "http://cega-nss:8443/username/" + id: + secret: + infoText: "About service text" + infoURL: "http://example.org/about" + jwt: + issuer: "https://auth:8888" + privateKey: /shared/keys/jwt.key + signatureAlg: ES256 + publicFile: "/shared/c4gh.pub.pem" + resignJwt: + s3Inbox: "http://inbox:8000" + backup: type: s3 url: "http://s3" @@ -52,6 +68,13 @@ c4gh: passphrase: "c4ghpass" syncPubKeyPath: /shared/sync.pub.pem +oidc: + id: + jwkPath: /jwk + provider: http://oidc:8080 + redirectUrl: http://auth_oidc:8080/oidc/login + secret: + server: cert: "" key: "" @@ -78,4 +101,4 @@ sync: password: "pass" user: "user" -schema.type: "isolated" \ No newline at end of file +schema.type: "isolated" diff --git a/.github/integration/sda/users.py b/.github/integration/sda/users.py index 60e80fe22..8cf3dfc4f 100644 --- a/.github/integration/sda/users.py +++ b/.github/integration/sda/users.py @@ -7,7 +7,7 @@ import json from base64 import b64decode import ssl - +from pathlib import Path from aiohttp import web logging.basicConfig(format="[%(levelname)-8s] (L:%(lineno)s) %(message)s") @@ -49,6 +49,7 @@ async def user(request): raise web.HTTPBadRequest(text=f"No info for that user\n") return web.json_response(user_info) +HTTP_PROTOCOL = "http" def main(): @@ -69,14 +70,20 @@ def main(): server.router.add_get("/username/{identifier}", user, name="user") # SSL settings - cacertfile = "/certs/ca.crt" - certfile = "/certs/server.crt" - keyfile = "/certs/server.key" - - ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=cacertfile) - ssl_ctx.check_hostname = False - ssl_ctx.verify_mode = ssl.CERT_NONE - ssl_ctx.load_cert_chain(certfile, keyfile=keyfile) + here = Path(__file__) + cacertfile = here.parent / "certs" / "ca.crt" + certfile = here.parent / "certs" / "server.crt" + keyfile = here.parent / "certs" / "server.key" + + global HTTP_PROTOCOL + if keyfile.is_file(): + ssl_ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=cacertfile) + ssl_ctx.check_hostname = False + ssl_ctx.verify_mode = ssl.CERT_NONE + ssl_ctx.load_cert_chain(certfile, keyfile=keyfile) + HTTP_PROTOCOL = "https" + else: + ssl_ctx = None web.run_app(server, host=host, port=port, shutdown_timeout=0, ssl_context=ssl_ctx) diff --git a/.github/integration/tests/sda/99_auth_test.sh b/.github/integration/tests/sda/99_auth_test.sh new file mode 100644 index 000000000..eb63dbba4 --- /dev/null +++ b/.github/integration/tests/sda/99_auth_test.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +python -m pip install --upgrade pip +pip install tox + +tox -e unit_tests -c /tests/sda/auth/tox.ini + +echo "auth test completes successfully" diff --git a/.github/integration/tests/sda/auth/integration/test_auth.py b/.github/integration/tests/sda/auth/integration/test_auth.py new file mode 100644 index 000000000..c1eea863f --- /dev/null +++ b/.github/integration/tests/sda/auth/integration/test_auth.py @@ -0,0 +1,28 @@ +import unittest +import requests + +class TestEGAAuth(unittest.TestCase): + """Testing EgaAuth.""" + + def setUp(self): + """Initialise authenticator.""" + self.backend_url = "http://auth-cega:8080/ega" + + + def tearDown(self): + """Finalise test.""" + print("Finishing test") + + + def test_valid_ega_login(self): + """Test that the login is successful.""" + creds_payload = { "username":'dummy@example.com', "password":'dummy', "submit": 'log+in' } + login_response = requests.post(self.backend_url, allow_redirects=False, data=creds_payload, cookies=None) + self.assertEqual(login_response.status_code, 200) + + + def test_invalid_ega_login(self): + """Test that the login is not successful.""" + creds_payload = { "username":'dummy@foo.bar', "password":'wrongpassword', "submit": 'log+in' } + login_response = requests.post(self.backend_url, allow_redirects=False, data=creds_payload, cookies=None) + self.assertEqual(login_response.status_code, 303) diff --git a/sda-auth/tests/requirements.txt b/.github/integration/tests/sda/auth/requirements.txt similarity index 100% rename from sda-auth/tests/requirements.txt rename to .github/integration/tests/sda/auth/requirements.txt diff --git a/sda-auth/tests/tox.ini b/.github/integration/tests/sda/auth/tox.ini similarity index 100% rename from sda-auth/tests/tox.ini rename to .github/integration/tests/sda/auth/tox.ini diff --git a/.github/workflows/build_pr_container.yaml b/.github/workflows/build_pr_container.yaml index 2ffedbf7e..86308274b 100644 --- a/.github/workflows/build_pr_container.yaml +++ b/.github/workflows/build_pr_container.yaml @@ -33,19 +33,6 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build container for sda-auth - uses: docker/build-push-action@v5 - with: - context: ./sda-auth - push: true - tags: | - ghcr.io/${{ github.repository }}:sha-${{ github.sha }}-auth - ghcr.io/${{ github.repository }}:PR${{ github.event.number }}-auth - labels: | - org.opencontainers.image.source=${{ github.event.repository.clone_url }} - org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ') - org.opencontainers.image.revision=${{ github.sha }} - - name: Build container for sda-download uses: docker/build-push-action@v5 with: @@ -300,7 +287,10 @@ jobs: - name: Check deployment run: | sleep 30 - for n in download finalize inbox ingest mapper verify; do + for n in auth download finalize inbox ingest mapper verify; do + if [ ${{matrix.storage}} == "posix" ] && [ "$n" == "auth" ]; then + continue + fi if [ ! $(kubectl get pods -l role="$n" -o=jsonpath='{.items[*].status.containerStatuses[0].ready}' | grep true) ]; then echo "$n is not ready after 30s, exiting" exit 1 diff --git a/.github/workflows/code-linter.yaml b/.github/workflows/code-linter.yaml index 49b15b909..19df3094c 100644 --- a/.github/workflows/code-linter.yaml +++ b/.github/workflows/code-linter.yaml @@ -8,29 +8,6 @@ on: - '**/go.*' jobs: - lint_auth: - name: Lint auth code - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - go-version: ['1.21'] - steps: - - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - id: go - - - name: Check out code into the Go module directory - uses: actions/checkout@v4 - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v4.0.0 - with: - args: -E bodyclose,gocritic,gofmt,gosec,govet,nestif,nlreturn,revive,rowserrcheck --timeout 5m - working-directory: sda-auth - lint_download: name: Lint download code runs-on: ubuntu-latest diff --git a/.github/workflows/functionality.yml b/.github/workflows/functionality.yml index 886285410..c14d360ea 100644 --- a/.github/workflows/functionality.yml +++ b/.github/workflows/functionality.yml @@ -7,7 +7,6 @@ on: jobs: check_changes: outputs: - sda-auth: ${{ steps.changes.outputs.sda-auth }} sda-download: ${{ steps.changes.outputs.sda-download }} sftp-inbox: ${{ steps.changes.outputs.sftp-inbox }} runs-on: ubuntu-latest @@ -24,38 +23,6 @@ jobs: sftp-inbox: - 'sftp-inbox/**' - sda-auth: - needs: check_changes - if: needs.check_changes.outputs.sda-auth == 'true' - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - go-version: ['1.21'] - steps: - - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - - uses: actions/checkout@v4 - - name: Set up Python 3.11 - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Add oidc to /etc/hosts - run: | - sudo echo "127.0.0.1 oidc" | sudo tee -a /etc/hosts - - name: Start virtual infrastructure - run: | - cd sda-auth - GOLANG_VERSION=${{ matrix.go-version }} docker-compose -f ./dev-server/docker-compose.yml up -d --force-recreate --build - - name: Run unit tests - run: tox -e unit_tests -c sda-auth/tests/tox.ini - sda-download: needs: check_changes if: needs.check_changes.outputs.sda-download == 'true' diff --git a/.github/workflows/publish_container.yml b/.github/workflows/publish_container.yml index 44e4e2ee3..41d8aeffa 100644 --- a/.github/workflows/publish_container.yml +++ b/.github/workflows/publish_container.yml @@ -54,16 +54,11 @@ jobs: - name: Pull PR containers run : | - for t in -auth -download -postgres -rabbitmq -sftp-inbox; do + for t in -download -postgres -rabbitmq -sftp-inbox; do docker pull ghcr.io/${{ github.repository }}:PR${{ github.event.number }}$t done docker pull ghcr.io/${{ github.repository }}:PR${{ github.event.number }} - - name: Retag PR image for auth - run: | - docker tag ghcr.io/${{ github.repository }}:PR${{ github.event.number }}-auth ghcr.io/${{ github.repository }}:${{ needs.tag_release.outputs.tag }}-auth - docker push ghcr.io/${{ github.repository }}:${{ needs.tag_release.outputs.tag }}-auth - - name: Retag PR image for download run: | docker tag ghcr.io/${{ github.repository }}:PR${{ github.event.number }}-download ghcr.io/${{ github.repository }}:${{ needs.tag_release.outputs.tag }}-download diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4f7599e22 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +### Go Patch ### +/vendor/ +/Godeps/ + +### Exclude test files ### +.github/integration/tests/sda/auth/integration/__* \ No newline at end of file diff --git a/Makefile b/Makefile index 6d760d05e..f4a2012ca 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ help: @echo 'In-depth description of how to use this Makefile can be found in the README.md' bootstrap: go-version-check docker-version-check - @for dir in sda sda-auth sda-download; do \ + @for dir in sda sda-download; do \ cd $$dir; \ go get ./...; \ cd ..; \ @@ -23,15 +23,13 @@ bootstrap: go-version-check docker-version-check GO111MODULE=off go get golang.org/x/tools/cmd/goimports # build containers -build-all: build-postgresql build-rabbitmq build-sda build-sda-auth build-sda-download build-sda-sftp-inbox +build-all: build-postgresql build-rabbitmq build-sda build-sda-download build-sda-sftp-inbox build-postgresql: @cd postgresql && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F)-postgres . build-rabbitmq: @cd rabbitmq && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F)-rabbitmq . build-sda: @cd sda && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F) . -build-sda-auth: - @cd sda-auth && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F)-auth . build-sda-download: @cd sda-download && docker build -t ghcr.io/neicnordic/sensitive-data-archive:PR$$(date +%F)-download . build-sda-sftp-inbox: @@ -83,23 +81,18 @@ integrationtest-sda: build-all @PR_NUMBER=$$(date +%F) docker compose -f .github/integration/sda-posix-integration.yml down -v --remove-orphans # lint go code -lint-all: lint-sda lint-sda-auth lint-sda-download +lint-all: lint-sda lint-sda-download lint-sda: @echo 'Running golangci-lint in the `sda` folder' @cd sda && golangci-lint run $(LINT_INCLUDE) $(LINT_EXCLUDE) -lint-sda-auth: - @echo 'Running golangci-lint in the `sda-auth` folder' - @cd sda-auth && golangci-lint run $(LINT_INCLUDE) $(LINT_EXCLUDE) lint-sda-download: @echo 'Running golangci-lint in the `sda-download` folder' @cd sda-download && golangci-lint run $(LINT_INCLUDE) $(LINT_EXCLUDE) # run static code tests -test-all: test-sda test-sda-auth test-sda-download test-sda-sftp-inbox +test-all: test-sda test-sda-download test-sda-sftp-inbox test-sda: @cd sda && go test ./... -count=1 -test-sda-auth: - @cd sda-auth && go test ./... -count=1 test-sda-download: @cd sda-download && go test ./... -count=1 test-sda-sftp-inbox: diff --git a/charts/sda-db/Chart.yaml b/charts/sda-db/Chart.yaml index 1ff539fa0..e8b3bade7 100644 --- a/charts/sda-db/Chart.yaml +++ b/charts/sda-db/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: sda-db version: 0.8.16 -appVersion: v0.2.112 +appVersion: v0.3.0 kubeVersion: '>= 1.26.0' description: Database component for Sensitive Data Archive (SDA) installation home: https://neic-sda.readthedocs.io diff --git a/charts/sda-mq/Chart.yaml b/charts/sda-mq/Chart.yaml index b6d66cb87..07898bccc 100644 --- a/charts/sda-mq/Chart.yaml +++ b/charts/sda-mq/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: sda-mq version: 0.7.17 -appVersion: v0.2.112 +appVersion: v0.3.0 kubeVersion: '>= 1.26.0' description: RabbitMQ component for Sensitive Data Archive (SDA) installation home: https://neic-sda.readthedocs.io diff --git a/charts/sda-svc/Chart.yaml b/charts/sda-svc/Chart.yaml index 06b701f35..fa7fa4a78 100644 --- a/charts/sda-svc/Chart.yaml +++ b/charts/sda-svc/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: sda-svc -version: 0.23.12 -appVersion: v0.2.112 +version: 0.24.0 +appVersion: v0.3.0 kubeVersion: '>= 1.26.0' description: Components for Sensitive Data Archive (SDA) installation home: https://neic-sda.readthedocs.io diff --git a/charts/sda-svc/templates/auth-deploy.yaml b/charts/sda-svc/templates/auth-deploy.yaml index 559d5e849..679e98561 100644 --- a/charts/sda-svc/templates/auth-deploy.yaml +++ b/charts/sda-svc/templates/auth-deploy.yaml @@ -58,7 +58,7 @@ spec: fsGroup: 65534 containers: - name: auth - image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}-auth" + image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy | quote }} securityContext: allowPrivilegeEscalation: false @@ -70,35 +70,28 @@ spec: {{- if .Values.global.extraSecurityContext }} {{- toYaml .Values.global.extraSecurityContext | nindent 10 -}} {{- end }} + command: ["sda-auth"] env: - {{- if .Values.global.log.format }} - - name: LOG_FORMAT - value: {{ .Values.global.log.format | quote }} - {{- end }} - {{- if .Values.global.log.level }} - - name: LOG_LEVEL - value: {{ .Values.global.log.level | quote }} - {{- end }} {{- if not .Values.global.vaultSecrets }} - {{- if and (ne "" .Values.global.oidc.id) (ne "" .Values.global.oidc.secret) }} - - name: ELIXIR_ID + {{- if and (ne "" .Values.global.oidc.id) (ne "" .Values.global.oidc.secret) }} + - name: OIDC_ID valueFrom: secretKeyRef: name: {{ template "sda.fullname" . }}-auth key: oidcID - - name: ELIXIR_SECRET + - name: OIDC_SECRET valueFrom: secretKeyRef: name: {{ template "sda.fullname" . }}-auth key: oidcSecret - {{- end }} + {{- end }} {{- if or ( eq "federated" .Values.global.schemaType) ( eq "" .Values.global.schemaType) }} - - name: CEGA_ID + - name: AUTH_CEGA_ID valueFrom: secretKeyRef: name: {{ template "sda.fullname" . }}-auth key: cegaID - - name: CEGA_SECRET + - name: AUTH_CEGA_SECRET valueFrom: secretKeyRef: name: {{ template "sda.fullname" . }}-auth @@ -108,54 +101,60 @@ spec: - name: SERVER_CONFFILE value: {{ include "confFile" . }} {{- end }} - - name: ELIXIR_REDIRECTURL - value: {{ template "authRedirect" .}} - - name: ELIXIR_PROVIDER - value: "{{ .Values.global.oidc.provider }}" - - name: ELIXIR_SCOPE - value: "ga4gh_passport_v1" - - name: ELIXIR_JWKPATH - value: {{ .Values.global.oidc.jwkPath | quote }} + {{- if or ( eq "federated" .Values.global.schemaType) ( eq "" .Values.global.schemaType) }} + - name: AUTH_CEGA_AUTHURL + value: {{ .Values.global.cega.host | quote }} + {{- end }} {{- if .Values.global.auth.corsOrigins }} - - name: CORS_ORIGINS + - name: AUTH_CORS_ORIGINS value: {{ .Values.global.auth.corsOrigins | quote }} - - name: CORS_METHODS + - name: AUTH_CORS_METHODS value: {{ .Values.global.auth.corsMethods | quote }} - - name: CORS_CREDENTIALS + - name: AUTH_CORS_CREDENTIALS value: {{ .Values.global.auth.corsCreds | quote }} {{- end }} - {{- if or ( eq "federated" .Values.global.schemaType) ( eq "" .Values.global.schemaType) }} - - name: CEGA_AUTHURL - value: {{ .Values.global.cega.host | quote }} - {{- end }} + - name: AUTH_INFOTEXT + value: {{ .Values.global.auth.infoText }} + - name: AUTH_INFOURL + value: {{ .Values.global.auth.inforURL }} {{- if .Values.global.auth.resignJwt }} - - name: JWTISSUER + - name: AUTH_JWT_ISSUER {{- if .Values.global.tls.enabled }} value: "https://{{ .Values.global.ingress.hostName.auth }}" {{- else }} value: "http://{{ .Values.global.ingress.hostName.auth }}" {{- end }} - - name: JWTPRIVATEKEY + - name: AUTH_JWT_PRIVATEKEY value: "{{ template "jwtPath" . }}/{{ .Values.global.auth.jwtKey }}" - - name: JWTSIGNATUREALG + - name: AUTH_JWT_SIGNATUREALG value: {{ .Values.global.auth.jwtAlg }} {{- end }} - - name: RESIGNJWT - value: {{ .Values.global.auth.resignJwt | quote }} - - name: PUBLICFILE + - name: AUTH_PUBLICFILE value: "{{ template "c4ghPath" . }}/{{ .Values.global.c4gh.publicFile }}" + - name: AUTH_RESIGNJWT + value: {{ .Values.global.auth.resignJwt | quote }} + - name: AUTH_S3INBOX + value: {{ .Values.global.ingress.hostName.s3Inbox }} + {{- if .Values.global.log.format }} + - name: LOG_FORMAT + value: {{ .Values.global.log.format | quote }} + {{- end }} + {{- if .Values.global.log.level }} + - name: LOG_LEVEL + value: {{ .Values.global.log.level | quote }} + {{- end }} + - name: OIDC_REDIRECTURL + value: {{ template "authRedirect" .}} + - name: OIDC_PROVIDER + value: "{{ .Values.global.oidc.provider }}" + - name: OIDC_JWKPATH + value: {{ .Values.global.oidc.jwkPath | quote }} {{- if .Values.global.tls.enabled}} - name: SERVER_CERT value: {{ template "tlsPath" . }}/tls.crt - name: SERVER_KEY value: {{ template "tlsPath" . }}/tls.key {{- end }} - - name: S3INBOX - value: {{ .Values.global.ingress.hostName.s3Inbox }} - - name: INFOTEXT - value: {{ .Values.global.auth.infoText }} - - name: INFOURL - value: {{ .Values.global.auth.inforURL }} ports: - name: auth containerPort: 8080 diff --git a/sda-auth/.dockerignore b/sda-auth/.dockerignore deleted file mode 100644 index ecb753fce..000000000 --- a/sda-auth/.dockerignore +++ /dev/null @@ -1,49 +0,0 @@ -# MacOS specific -*.DS_Store - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] - -# vscode -*.vscode - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# log dumps -*.dump - -# deployment -dev-server/ -deployment/ -Dockerfile -config.yaml - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg diff --git a/sda-auth/.github/dependabot.yml b/sda-auth/.github/dependabot.yml deleted file mode 100644 index ac1a2d70e..000000000 --- a/sda-auth/.github/dependabot.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "gomod" - directory: "/" - # Check the npm registry for updates every day (weekdays) - schedule: - interval: "weekly" - reviewers: - - "neicnordic/sensitive-data-development-collaboration" - - - package-ecosystem: "docker" - # Look for a `Dockerfile` in the `root` directory - directory: "/" - # Check for updates once a week - schedule: - interval: "weekly" - reviewers: - - "neicnordic/sensitive-data-development-collaboration" - - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 10 - reviewers: - - "neicnordic/sensitive-data-development-collaboration" diff --git a/sda-auth/.github/workflows/build_pr_container.yaml b/sda-auth/.github/workflows/build_pr_container.yaml deleted file mode 100644 index eaa7739d2..000000000 --- a/sda-auth/.github/workflows/build_pr_container.yaml +++ /dev/null @@ -1,40 +0,0 @@ -name: build PR container - -on: - pull_request: - paths: - - "Dockerfile" - - "*.go" - - "frontend/**" - -jobs: - build: - name: Build PR image - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Log in to the Github Container registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build container - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - push: true - tags: | - ghcr.io/${{ github.repository }}:PR${{ github.event.number }} - ghcr.io/${{ github.repository }}:sha-${{ github.sha }} - labels: | - org.opencontainers.image.source=${{ github.event.repository.clone_url }} - org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ') - org.opencontainers.image.revision=${{ github.sha }} diff --git a/sda-auth/.github/workflows/functionality.yml b/sda-auth/.github/workflows/functionality.yml deleted file mode 100644 index eba0d1470..000000000 --- a/sda-auth/.github/workflows/functionality.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Functionality - -on: [push] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - go-version: [1.19] - steps: - - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@v4 - with: - go-version: ${{ matrix.go-version }} - - uses: actions/checkout@v3 - - name: Set up Python 3.11 - uses: actions/setup-python@v4 - with: - python-version: "3.11" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox tox-gh-actions - - name: Add oidc to /etc/hosts - run: | - sudo echo "127.0.0.1 oidc" | sudo tee -a /etc/hosts - - name: Start virtual infrastructure - run: GOLANG_VERSION=${{ matrix.go-version }} docker-compose -f ./dev-server/docker-compose.yml up -d --force-recreate --build - - name: Run unit tests - run: tox -e unit_tests -c tests/tox.ini diff --git a/sda-auth/.github/workflows/lint.yml b/sda-auth/.github/workflows/lint.yml deleted file mode 100644 index fcb4d7326..000000000 --- a/sda-auth/.github/workflows/lint.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Multilinters - -on: - push: - -jobs: - build: - name: Check code - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - go-version: [1.19] - steps: - - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@v4 - with: - go-version: ${{ matrix.go-version }} - - name: Check out code into the Go module directory - uses: actions/checkout@v3 - - - name: Run golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.50 - args: -E gosec,goconst,nestif,bodyclose,rowserrcheck diff --git a/sda-auth/.github/workflows/tag_and_build.yaml b/sda-auth/.github/workflows/tag_and_build.yaml deleted file mode 100644 index 2dc5767e2..000000000 --- a/sda-auth/.github/workflows/tag_and_build.yaml +++ /dev/null @@ -1,69 +0,0 @@ -name: Bump version -on: - push: - branches: - - main - paths: - - "Dockerfile" - - "*.go" - - "go.*" - - "frontend/**" - -jobs: - tag: - name: bump tags - outputs: - part: ${{ steps.bump_tag.outputs.part }} - tag: ${{ steps.bump_tag.outputs.tag }} - new_tag: ${{ steps.bump_tag.outputs.new_tag }} - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: "1" - - name: Bump version and push tag - id: bump_tag - uses: anothrNick/github-tag-action@1.67.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - WITH_V: true - DEFAULT_BUMP: patch - - push_to_registry: - needs: tag - if: needs.tag.outputs.part != '' - name: Push Docker image to Github Container registry - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - name: Check out the repo - uses: actions/checkout@v3 - - - name: Log in to the Github Container registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: ghcr.io/${{ github.repository }} - - - name: Build and push - uses: docker/build-push-action@v4 - with: - context: . - file: ./Dockerfile - push: true - tags: | - ghcr.io/${{ github.repository }}:${{ needs.tag.outputs.tag }} - ghcr.io/${{ github.repository }}:latest - labels: | - org.opencontainers.image.source=${{ github.event.repository.clone_url }} - org.opencontainers.image.created=$(date -u +'%Y-%m-%dT%H:%M:%SZ') - org.opencontainers.image.revision=${{ github.sha }} diff --git a/sda-auth/.gitignore b/sda-auth/.gitignore deleted file mode 100644 index c17924110..000000000 --- a/sda-auth/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# MacOS specific -*.DS_Store - -# vscode -*.vscode - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Service binaries -auth - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# log dumps -*.dump - diff --git a/sda-auth/Dockerfile b/sda-auth/Dockerfile deleted file mode 100644 index 8d3d09904..000000000 --- a/sda-auth/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -ARG GOLANG_VERSION=1.21 -FROM "golang:${GOLANG_VERSION}-alpine" -RUN apk add --no-cache git -COPY . . -ENV GO111MODULE=on -ENV GOPATH=$PWD -ENV CGO_ENABLED=0 -ENV GOOS=linux -RUN go build -ldflags "-extldflags -static" -o ./build/svc . -RUN echo "nobody:x:65534:65534:nobody:/:/sbin/nologin" > passwd - -FROM scratch -COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ -COPY --from=0 /go/build/svc svc -COPY --from=0 /go/frontend frontend/ -COPY --from=0 /go/passwd /etc/passwd -USER 65534 -EXPOSE 8080 -ENTRYPOINT [ "/svc" ] - diff --git a/sda-auth/LICENSE b/sda-auth/LICENSE deleted file mode 100644 index be3f7b28e..000000000 --- a/sda-auth/LICENSE +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 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 Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are 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. - - 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. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - 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 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 work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - 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 AGPL, see -. diff --git a/sda-auth/README.md b/sda-auth/README.md deleted file mode 100644 index 5aeb9048d..000000000 --- a/sda-auth/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# SDA authentication service - -This service allows users to log in both via Elixir AAI or EGA. - -## Configuration example for local testing - -The following settings can be configured for deploying the service, either by using environment variables or a YAML file. - -Parameter | Description | Defined value ---------- | ----------- | ------- -`LOG_LEVEL` | Log level | `info` -`ELIXIR_ID` | Elixir authentication id | `XC56EL11xx` -`ELIXIR_SECRET` | Elixir authentication secret | `wHPVQaYXmdDHg` -`ELIXIR_PROVIDER` | Elixir issuer URL | `http://oidc:9090` -`ELIXIR_JWKPATH` | JWK endpoint where the public key of the Elixir issuer can be retrieved from for token validation | `/jwks` -`CEGA_AUTHURL` | CEGA server endpoint | `http://cega:8443/lega/v1/legas/users/` -`CEGA_ID` | CEGA server authentication id | `dummy` -`CEGA_SECRET` | CEGA server authentication secret | `dummy` -`CORS_ORIGINS` | Allowed Cross-Origin Resource Sharing (CORS) origins | `""` -`CORS_METHODS` | Allowed Cross-Origin Resource Sharing (CORS) methods | `""` -`CORS_CREDENTIALS` | If cookies, authorization headers, and TLS client certificates are allowed over CORS | `false` -`SERVER_CERT` | Certificate file path | `""` -`SERVER_KEY` | Private key file path | `""` -`S3INBOX` | S3 inbox host | `s3.example.com` -`JWTISSUER` | Issuer of JWT tokens | `http://auth:8080` -`JWTPRIVATEKEY` | Path to private key for signing the JWT token | `keys/sign-jwt.key` -`JWTSIGNATUREALG` | Algorithm used to sign the JWT token. ES256 (ECDSA) or RS256 (RSA) are supported | `RS256` -`RESIGNJWT` | Set to `false` to serve the raw OIDC JWT, i.e. without re-signing it | `""` - -## Running the development setup - -Start the full stack by running docker-compose in the `dev-server` folder: - -```bash -docker-compose up --build -``` - -The current setup also requires that `127.0.0.1 oidc` is added to `/etc/hosts`, so that routing works properly. - -## Running with Cross-Origin Resource Sharing (CORS) - -This service can be run as a backend only, and in the case where the frontend -is running somewhere else, CORS is needed. - -Recommended CORS settings for a given host are: - -```txt -export CORS_ORIGINS="https://" -export CORS_METHODS="GET,OPTIONS,POST" -export CORS_CREDENTIALS="true" -``` - -There is a minimal CORS login testing site at `http://localhost:8000` of the -dev-server. - -## Building a Docker container - -Using the provided Dockerfile, you may build a Docker image: - -```bash -docker build -t neicnordic/sda-auth:mytag -``` - -## Choosing provider login - -The `sda-auth` allows for two different type of login providers: `EGA` and `Elixir` (LS-AAI). It is possible, however, to run the `sda-auth` using only one of the providers. In order to remove the `EGA` option, remove the `CEGA_ID` and `CEGA_SECRET` options for the configuration, while for removing the `Elixir` option, remove the `ELIXIR_ID` and `ELIXIR_SECRET` variables. diff --git a/sda-auth/config.go b/sda-auth/config.go deleted file mode 100644 index c582e094b..000000000 --- a/sda-auth/config.go +++ /dev/null @@ -1,199 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "os" - "path" - "strings" - - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" -) - -// ElixirConfig stores the config about the elixir oidc endpoint -type ElixirConfig struct { - ID string - Provider string - RedirectURL string - RevocationURL string - Secret string - jwkURL string -} - -// CegaConfig stores information about the cega endpoint -type CegaConfig struct { - AuthURL string - ID string - Secret string -} - -// CORSConfig stores information about cross-origin resource sharing -type CORSConfig struct { - AllowOrigin string - AllowMethods string - AllowHeaders string - AllowCredentials bool -} - -// ServerConfig stores general server information -type ServerConfig struct { - Cert string - Key string - CORS CORSConfig -} - -// Config is a parent object for all the different configuration parts -type Config struct { - Elixir ElixirConfig - Cega CegaConfig - JwtIssuer string - JwtPrivateKey string - JwtSignatureAlg string - Server ServerConfig - S3Inbox string - ResignJwt bool - InfoURL string - InfoText string - PublicFile string -} - -// NewConfig initializes and parses the config file and/or environment using -// the viper library. -func NewConfig() (*Config, error) { - viper.SetConfigName("config") - viper.AddConfigPath(".") - viper.AutomaticEnv() - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.SetConfigType("yaml") - if viper.IsSet("server.confPath") { - cp := viper.GetString("server.confPath") - ss := strings.Split(strings.TrimLeft(cp, "/"), "/") - viper.AddConfigPath(path.Join(ss...)) - } - if viper.IsSet("server.confFile") { - viper.SetConfigFile(viper.GetString("server.confFile")) - } - - if err := viper.ReadInConfig(); err != nil { - if _, ok := err.(viper.ConfigFileNotFoundError); ok { - log.Infoln("No config file found, using ENVs only") - } else { - return nil, err - } - } - - c := &Config{} - err := c.readConfig() - - return c, err -} - -func (c *Config) readConfig() error { - c.JwtPrivateKey = viper.GetString("JwtPrivateKey") - c.JwtSignatureAlg = viper.GetString("JwtSignatureAlg") - c.JwtIssuer = viper.GetString("jwtIssuer") - c.InfoURL = viper.GetString("infoUrl") - c.InfoText = viper.GetString("infoText") - c.PublicFile = viper.GetString("publicFile") - - viper.SetDefault("ResignJwt", true) - c.ResignJwt = viper.GetBool("resignJwt") - - // Setup elixir - elixir := ElixirConfig{} - - elixir.ID = viper.GetString("elixir.id") - elixir.Provider = viper.GetString("elixir.provider") - elixir.RedirectURL = viper.GetString("elixir.redirectUrl") - elixir.Secret = viper.GetString("elixir.secret") - if viper.IsSet("elixir.jwkPath") { - elixir.jwkURL = elixir.Provider + viper.GetString("elixir.jwkPath") - } - - c.Elixir = elixir - - // Setup cega - cega := CegaConfig{} - - cega.AuthURL = viper.GetString("cega.authUrl") - cega.ID = viper.GetString("cega.id") - cega.Secret = viper.GetString("cega.secret") - - c.Cega = cega - - // Check the either cega or Elixir config exists - if (elixir.ID == "" || elixir.Secret == "") && (cega.ID == "" || cega.Secret == "") { - return fmt.Errorf("neither cega or elixir login configured") - } - - // Read CORS settings - cors := CORSConfig{AllowCredentials: false} - if viper.IsSet("cors.origins") { - cors.AllowOrigin = viper.GetString("cors.origins") - } - if viper.IsSet("cors.methods") { - cors.AllowMethods = viper.GetString("cors.methods") - } - if viper.IsSet("cors.headers") { - cors.AllowHeaders = viper.GetString("cors.headers") - } - if viper.IsSet("cors.credentials") { - cors.AllowCredentials = viper.GetBool("cors.credentials") - } - - // Setup server - s := ServerConfig{CORS: cors} - - if viper.IsSet("server.cert") { - s.Cert = viper.GetString("server.cert") - } - if viper.IsSet("server.key") { - s.Key = viper.GetString("server.key") - } - - c.Server = s - - c.S3Inbox = viper.GetString("s3Inbox") - - if viper.IsSet("log.format") { - if viper.GetString("log.format") == "json" { - log.SetFormatter(&log.JSONFormatter{}) - log.Info("The logs format is set to JSON") - } - } - - if viper.IsSet("log.level") { - stringLevel := viper.GetString("log.level") - intLevel, err := log.ParseLevel(stringLevel) - if err != nil { - log.Printf("Log level '%s' not supported, setting to 'trace'", stringLevel) - intLevel = log.TraceLevel - } - log.SetLevel(intLevel) - log.Printf("Setting log level to '%s'", stringLevel) - } - - for _, s := range []string{"s3Inbox", "publicFile"} { - if viper.GetString(s) == "" { - return fmt.Errorf("%s not set", s) - } - } - - // no need to check the variables for JWT generation if we won't use it - if (cega.ID == "" && cega.Secret == "") && !c.ResignJwt { - return nil - } - - for _, s := range []string{"jwtIssuer", "JwtPrivateKey", "JwtSignatureAlg"} { - if viper.GetString(s) == "" { - return fmt.Errorf("%s not set", s) - } - } - - if _, err := os.Stat(c.JwtPrivateKey); errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("missing private key file, reason: '%s'", err.Error()) - } - - return nil -} diff --git a/sda-auth/config.yaml b/sda-auth/config.yaml deleted file mode 100644 index fc7c1cec3..000000000 --- a/sda-auth/config.yaml +++ /dev/null @@ -1,27 +0,0 @@ ---- -log: - level: "info" - format: "json" -elixir: - id: "XC56EL11xx" - provider: "http://oidc:9090" - redirectUri: "http://oidc:31111/elixir/login" - revocationUrl: "http://oidc:9090" - secret: "wHPVQaYXmdDHg" - jwkPath: "/jwks" -cega: - authUrl: "http://cega:8443/username/" - id: "dummy" - secret: "dummy" -server: - # do not use TLS until certs are fixed - # cert: "keys/cert.pem" - # key: "keys/key.pem" -s3Inbox: "s3.example.com" -jwtIssuer: "http://auth:8080" -jwtPrivateKey: "keys/sign-jwt.key" -jwtSignatureAlg: "ES256" -resignJwt: true -infoText: "About Federated EGA" -infoUrl: "https://ega-archive.org/about/projects-and-funders/federated-ega/" -publicFile: "/keys/c4gh.pub.pem" diff --git a/sda-auth/config_test.go b/sda-auth/config_test.go deleted file mode 100644 index ac5f36547..000000000 --- a/sda-auth/config_test.go +++ /dev/null @@ -1,259 +0,0 @@ -package main - -import ( - "fmt" - "os" - "path/filepath" - "strconv" - "testing" - - log "github.com/sirupsen/logrus" - "github.com/spf13/viper" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" - "gopkg.in/yaml.v3" -) - -// These are not complete tests of all functions in elixir. New tests should -// be added as the code is updated. - -type ConfigTests struct { - suite.Suite - TempDir string - ConfigFile *os.File - ElixirConfig ElixirConfig - CegaConfig CegaConfig - ServerConfig ServerConfig - S3Inbox string - JwtIssuer string - JwtPrivateKey string - JwtPrivateKeyFile *os.File - JwtSignatureAlg string - ResignJwt bool - InfoURL string - InfoText string - PublicFile string -} - -func TestConfigTestSuite(t *testing.T) { - suite.Run(t, new(ConfigTests)) -} - -func (suite *ConfigTests) SetupTest() { - - var err error - - // Create a temporary directory for our config file - suite.TempDir, err = os.MkdirTemp("", "sda-auth-test-") - if err != nil { - log.Fatal("Couldn't create temporary test directory", err) - } - suite.ConfigFile, err = os.Create(filepath.Join(suite.TempDir, "config.yaml")) - if err != nil { - log.Fatal("Cannot create temporary config file", err) - } - - // Create temporary dummy keys - suite.JwtPrivateKeyFile, err = os.Create(filepath.Join(suite.TempDir, "jwt-dummy-sec.c4gh")) - if err != nil { - log.Fatal("Cannot create temporary private key file", err) - } - _, err = os.Create(filepath.Join(suite.TempDir, "jwt-dummy-sec.c4gh_env")) - if err != nil { - log.Fatal("Cannot create temporary private key file", err) - } - - // config values to write to the config file - suite.ElixirConfig = ElixirConfig{ - ID: "elixirTestID", - Provider: "elixirTestIssuer", - RedirectURL: "http://elixir/login", - RevocationURL: "http://elixir/revoke", - Secret: "elixirTestSecret", - } - - suite.CegaConfig = CegaConfig{ - AuthURL: "http://cega/auth", - ID: "cegaID", - Secret: "cegaSecret", - } - - suite.ServerConfig = ServerConfig{ - Cert: "serverCert.pem", - Key: "serverKey.pem", - } - - suite.S3Inbox = "s3://testInbox" - suite.JwtIssuer = "JwtIssuer" - suite.JwtPrivateKey = suite.JwtPrivateKeyFile.Name() - suite.JwtSignatureAlg = "RS256" - suite.ResignJwt = true - suite.InfoURL = "https://test.info" - suite.InfoText = "About LEGA" - suite.PublicFile = "public.pem" - - // Write config to temp config file - configYaml, err := yaml.Marshal(Config{ - Elixir: suite.ElixirConfig, - Cega: suite.CegaConfig, - Server: suite.ServerConfig, - S3Inbox: suite.S3Inbox, - JwtIssuer: suite.JwtIssuer, - JwtPrivateKey: suite.JwtPrivateKey, - JwtSignatureAlg: suite.JwtSignatureAlg, - ResignJwt: suite.ResignJwt, - InfoURL: suite.InfoURL, - InfoText: suite.InfoText, - PublicFile: suite.PublicFile, - }) - if err != nil { - log.Errorf("Error marshalling config yaml: %v", err) - } - _, err = suite.ConfigFile.Write(configYaml) - if err != nil { - log.Errorf("Error writing config file: %v", err) - } - -} - -func (suite *ConfigTests) TearDownTest() { - os.RemoveAll(suite.TempDir) -} - -// Both readConfig and parseConfig is called when using NewConfig, so they are -// both tested in this single test. -func (suite *ConfigTests) TestConfig() { - // change dir so that we read the right config - err := os.Chdir(suite.TempDir) - if err != nil { - log.Errorf("Couldn't access temp directory: %v", err) - } - - config, err := NewConfig() - assert.NoError(suite.T(), err) - - // Check elixir values - assert.Equal(suite.T(), suite.ElixirConfig.ID, config.Elixir.ID, "Elixir ID misread from config file") - assert.Equal(suite.T(), suite.ElixirConfig.Provider, config.Elixir.Provider, "Elixir Issuer misread from config file") - assert.Equal(suite.T(), suite.ElixirConfig.RedirectURL, config.Elixir.RedirectURL, "Elixir RedirectURL misread from config file") - assert.Equal(suite.T(), suite.ElixirConfig.Secret, config.Elixir.Secret, "Elixir Secret misread from config file") - - // Check CEGA values - assert.Equal(suite.T(), suite.CegaConfig.ID, config.Cega.ID, "CEGA ID misread from config file") - assert.Equal(suite.T(), suite.CegaConfig.AuthURL, config.Cega.AuthURL, "CEGA AuthURL misread from config file") - assert.Equal(suite.T(), suite.CegaConfig.Secret, config.Cega.Secret, "CEGA Secret misread from config file") - - // Check ServerConfig values - assert.Equal(suite.T(), suite.ServerConfig.Cert, config.Server.Cert, "ServerConfig Cert misread from config file") - assert.Equal(suite.T(), suite.ServerConfig.Key, config.Server.Key, "ServerConfig Key misread from config file") - - // Check S3Inbox value - assert.Equal(suite.T(), suite.S3Inbox, config.S3Inbox, "S3Inbox misread from config file") - - // Check JWT values - assert.Equal(suite.T(), suite.JwtIssuer, config.JwtIssuer, "JwtIssuer misread from config file") - assert.Equal(suite.T(), suite.JwtPrivateKey, config.JwtPrivateKey, "JwtPrivateKey misread from config file") - assert.Equal(suite.T(), suite.JwtSignatureAlg, config.JwtSignatureAlg, "JwtSignatureAlg misread from config file") - assert.Equal(suite.T(), suite.ResignJwt, config.ResignJwt, "ResignJwt misread from config file") - - // Check project info values - assert.Equal(suite.T(), suite.InfoText, config.InfoText, "Project info test misread from config file") - assert.Equal(suite.T(), suite.InfoURL, config.InfoURL, "Project info URL misread from config file") - - // sanitycheck without config file or ENVs - // this should fail - os.Remove(suite.ConfigFile.Name()) - viper.Reset() - _, e := NewConfig() - assert.Error(suite.T(), e) - - // Set all values as environment variables - os.Setenv("ELIXIR_ID", fmt.Sprintf("env_%v", suite.ElixirConfig.ID)) - os.Setenv("ELIXIR_PROVIDER", fmt.Sprintf("env_%v", suite.ElixirConfig.Provider)) - os.Setenv("ELIXIR_REDIRECTURL", fmt.Sprintf("env_%v", suite.ElixirConfig.RedirectURL)) - os.Setenv("ELIXIR_SECRET", fmt.Sprintf("env_%v", suite.ElixirConfig.Secret)) - - os.Setenv("CEGA_ID", fmt.Sprintf("env_%v", suite.CegaConfig.ID)) - os.Setenv("CEGA_AUTHURL", fmt.Sprintf("env_%v", suite.CegaConfig.AuthURL)) - os.Setenv("CEGA_SECRET", fmt.Sprintf("env_%v", suite.CegaConfig.Secret)) - - os.Setenv("SERVER_CERT", fmt.Sprintf("env_%v", suite.ServerConfig.Cert)) - os.Setenv("SERVER_KEY", fmt.Sprintf("env_%v", suite.ServerConfig.Key)) - - os.Setenv("S3INBOX", fmt.Sprintf("env_%v", suite.S3Inbox)) - - os.Setenv("JWTISSUER", fmt.Sprintf("env_%v", suite.JwtIssuer)) - os.Setenv("JWTPRIVATEKEY", fmt.Sprintf("%v_env", suite.JwtPrivateKey)) - os.Setenv("JWTSIGNATUREALG", fmt.Sprintf("env_%v", suite.JwtSignatureAlg)) - os.Setenv("RESIGNJWT", fmt.Sprintf("%t", suite.ResignJwt)) - - os.Setenv("INFOTEXT", fmt.Sprintf("env_%v", suite.InfoText)) - os.Setenv("INFOURL", fmt.Sprintf("env_%v", suite.InfoURL)) - os.Setenv("PUBLICFILE", fmt.Sprintf("env_%v", suite.PublicFile)) - - // re-read the config - config, err = NewConfig() - assert.NoError(suite.T(), err) - - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.ElixirConfig.ID), config.Elixir.ID, "Elixir ID misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.ElixirConfig.Provider), config.Elixir.Provider, "Elixir Issuer misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.ElixirConfig.RedirectURL), config.Elixir.RedirectURL, "Elixir RedirectURL misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.ElixirConfig.Secret), config.Elixir.Secret, "Elixir Secret misread from environment variable") - - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.CegaConfig.ID), config.Cega.ID, "CEGA ID misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.CegaConfig.AuthURL), config.Cega.AuthURL, "CEGA AuthURL misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.CegaConfig.Secret), config.Cega.Secret, "CEGA Secret misread from environment variable") - - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.ServerConfig.Cert), config.Server.Cert, "ServerConfig Cert misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.ServerConfig.Key), config.Server.Key, "ServerConfig Key misread from environment variable") - - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.S3Inbox), config.S3Inbox, "S3Inbox misread from environment variable") - - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.JwtIssuer), config.JwtIssuer, "JwtIssuer misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("%v_env", suite.JwtPrivateKey), config.JwtPrivateKey, "JwtPrivateKey misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.JwtSignatureAlg), config.JwtSignatureAlg, "JwtSignatureAlg misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("%t", suite.ResignJwt), strconv.FormatBool(config.ResignJwt), "ResignJwt misread from environment variable") - - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.InfoText), config.InfoText, "Project info text misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.InfoURL), config.InfoURL, "Project info text misread from environment variable") - assert.Equal(suite.T(), fmt.Sprintf("env_%v", suite.PublicFile), config.PublicFile, "Public file misread from environment variable") - - // Check missing private key - os.Setenv("JWTPRIVATEKEY", "nonexistent-key-file") - - // re-read the config - _, err = NewConfig() - assert.ErrorContains(suite.T(), err, "missing private key file") - - // Repeat check with CEGA login and JWT resigning disabled - os.Setenv("CEGA_ID", "") - os.Setenv("CEGA_SECRET", "") - os.Setenv("RESIGNJWT", fmt.Sprintf("%t", false)) - - // re-read the config - _, err = NewConfig() - assert.NoError(suite.T(), err) - - // Repeat with Elixir secret but no ID - os.Setenv("ELIXIR_ID", "") - - // re-read the config - _, err = NewConfig() - assert.Equal(suite.T(), err.Error(), "neither cega or elixir login configured") - - // Repeat test without CEGA or Elixir login - os.Setenv("ELIXIR_ID", "") - os.Setenv("ELIXIR_SECRET", "") - - // re-read the config - _, err = NewConfig() - assert.Equal(suite.T(), err.Error(), "neither cega or elixir login configured") - - os.Setenv("CEGA_ID", fmt.Sprintf("env_%v", suite.CegaConfig.ID)) - os.Setenv("CEGA_SECRET", fmt.Sprintf("env_%v", suite.CegaConfig.Secret)) - os.Setenv("JWTPRIVATEKEY", fmt.Sprintf("%v_env", suite.JwtPrivateKey)) - - // re-read the config - _, err = NewConfig() - assert.NoError(suite.T(), err) -} diff --git a/sda-auth/dev-server/cega-users/cega-mock.py b/sda-auth/dev-server/cega-users/cega-mock.py deleted file mode 100644 index 0bf578fdd..000000000 --- a/sda-auth/dev-server/cega-users/cega-mock.py +++ /dev/null @@ -1,82 +0,0 @@ -import sys -import os -import logging -import asyncio -import json -from base64 import b64decode -from aiohttp import web - -#logging.basicConfig(format='[%(asctime)s][%(levelname)-8s] (L:%(lineno)s) %(message)s', datefmt='%Y-%m-%d %H:%M:%S') -logging.basicConfig(format='[%(levelname)-8s] (L:%(lineno)s) %(message)s') -LOG = logging.getLogger(__name__) -LOG.setLevel(logging.INFO) - -filepath = None -instances = {} -store = None -usernames = {} -uids = {} - -def fetch_user_info(identifier, query): - LOG.info(f'Requesting User {identifier}') - pos = usernames.get(identifier, None) - return store[pos] if pos is not None else None - -async def user(request): - # Authenticate - auth_header = request.headers.get('AUTHORIZATION') - if not auth_header: - raise web.HTTPUnauthorized(text=f'Protected access\n') - _, token = auth_header.split(None, 1) # Skipping the Basic keyword - LOG.debug(f'Token is {token}') - instance, passwd = b64decode(token).decode().split(':', 1) - LOG.debug(f'I am instance {instance} and the password is {passwd}') - info = instances.get(instance) - if info is None or info != passwd: - raise web.HTTPUnauthorized(text=f'Protected access\n') - - # Reload users list - load_users() - - # Find user - user_info = fetch_user_info(request.match_info['identifier'], request.rel_url.query) - if user_info is None: - raise web.HTTPBadRequest(text=f'No info for that user\n') - LOG.info(f'user info {user_info}') - return web.json_response(user_info) - -def main(): - print("Main is being run") - if len(sys.argv) < 3: - print('Usage: {sys.argv[0] }', file=sys.stderr) - sys.exit(2) - - host = sys.argv[1] - port = sys.argv[2] - - global filepath - filepath = sys.argv[3] - - server = web.Application() - load_users() - - # Registering the routes - server.router.add_get('/username/{identifier}', user, name='user') - - # aaaand... cue music - web.run_app(server, host=host, port=port, shutdown_timeout=0) - - -def load_users(): - # Initialization - global filepath, instances, store, usernames, uids - instances[os.environ[f'CEGA_USERS_USER']] = os.environ[f'CEGA_USERS_PASSWORD'] - with open(filepath, 'rt') as f: - store = json.load(f) - for i, d in enumerate(store): - usernames[d['username']] = i - uids[d['uid']] = i - - -if __name__ == '__main__': - main() diff --git a/sda-auth/dev-server/cega-users/users.json b/sda-auth/dev-server/cega-users/users.json deleted file mode 100644 index 9ed6bbd06..000000000 --- a/sda-auth/dev-server/cega-users/users.json +++ /dev/null @@ -1,7 +0,0 @@ -[{"username": "dummy@example.com", - "uid": 1, - "passwordHash": "$2b$12$1gyKIjBc9/cT0MYkXX24xe1LjEUjNwgL4rEk8fDoO.vDQZzWkqrn.", - "gecos": "dummy user", - "sshPublicKey": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDiEcu2czBfbQh6+A3DplO2DRHG4LmdUKLRPX1vFOpC30TZusio0cFcgi8TEQv9TlMwu2ujF/wn/0D2VwXiDGk/Rbeq9jgTLpVbDWg/3ZGxGSqjPV3fzl0NzmaSgF0+IZQaSr6OjGVpTmpk5G4d4qqFg4Shjjm+AwlgmruThHNS9KmdJ7Vru+rQ8LwcjuSWtBdf6JM3bjlw1swQt6776p+wTK51YSKdtFEE5yVZjVwxlcPre7sRiem0XwSCFsu9sAfUTbHNTfwQ8lVXbvgRGu9SoW0wwb0Qele1WXZ8YFF10KLxGKpb1u0NsXSIZrJZhk0nxKb5tGBSnKXquoAvLZfVEKc+AXw1sDSaKvZaDw0/GORoAVSt3LDKYydAlzpMw6am4fgcEzm0vwCieWvSxd9uLY9IxV4sx0n0ZcuG55Le2TaQMnSm5XQ8zHBFYOb9ux8h6TY6JO+HmSjoHXkGhILKg8Y7zpq0PWy7HUvWzQVVfShAMN/N2gZ3a2T7amg17/S0wtgvjxxpNtnLc0HnHjr/mwtBjrN8C4n+IYI13rqZzPPU1wZu5qiacmHmeR15XAktEFKrpuvViJcylksjwyl6aY9psm+dwocON/yA3pdJGA8mPvrnDpPkGpzqvTqqIMxQkgel46jF2B7+lLzq6wQOsb7Ct66CKKppM6kpVVHRWQ==", - "enabled": null -}] diff --git a/sda-auth/dev-server/docker-compose.yml b/sda-auth/dev-server/docker-compose.yml deleted file mode 100644 index de51dbd48..000000000 --- a/sda-auth/dev-server/docker-compose.yml +++ /dev/null @@ -1,102 +0,0 @@ -services: - oidc: - container_name: oidc - build: - context: ./oidc - dockerfile: Dockerfile - image: mock-oidc-user-server - environment: - - PORT=9090 - - HOST=oidc - - CLIENT_ID=XC56EL11xx - - CLIENT_SECRET=wHPVQaYXmdDHg - - CLIENT_REDIRECT_URI=http://localhost:8080/elixir/login - - AUTH_ROUTE=/auth - - INTROSPECTION_ROUTE=/token/introspection - - JWKS_ROUTE=/jwks - - REVOCATION_ROUTE=/token/revocation - - TOKEN_ROUTE=/token - - USERINFO_ROUTE=/userinfo - ports: - - 9090:9090 - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9090/.well-known/openid-configuration"] - interval: 5s - timeout: 10s - retries: 4 - cega: - container_name: cega - image: egarchive/lega-base:release.v0.2.0 - volumes: - - ./cega-users:/cega - command: - [ - "python", - "/cega/cega-mock.py", - "0.0.0.0", - "8443", - "/cega/users.json" - ] - environment: - - LEGA_INSTANCES=dummy - - CEGA_USERS_PASSWORD=dummy - - CEGA_USERS_USER=dummy - ports: - - 8443:8443 - keygen: - image: golang:alpine3.16 - container_name: keygen - command: - - "/bin/sh" - - "-c" - - if [ ! -f "/out/c4gh.sec.pem" ]; then wget -qO- "https://github.com/neicnordic/crypt4gh/releases/latest/download/crypt4gh_linux_x86_64.tar.gz" | tar zxf -; - ./crypt4gh generate -n c4gh -p privatekeypass && mv *.pem /out/; fi - volumes: - - /tmp:/out - auth: - container_name: auth - build: - context: ../ - args: - GOLANG_VERSION: ${GOLANG_VERSION-1.20} - dockerfile: Dockerfile - depends_on: - oidc: - condition: service_healthy - cega: - condition: service_started - keygen: - condition: service_completed_successfully - environment: - - ELIXIR_ID=XC56EL11xx - - ELIXIR_PROVIDER=http://oidc:9090 - - ELIXIR_SECRET=wHPVQaYXmdDHg - - ELIXIR_JWKPATH=/jwks - - CEGA_AUTHURL=http://cega:8443/username/ - - CEGA_ID=dummy - - CEGA_SECRET=dummy - - CORS_ORIGINS=http://localhost:8080 - - CORS_METHODS=GET,POST, OPTIONS - - CORS_CREDENTIALS=TRUE - - LOG_LEVEL=info - - S3INBOX=s3.example.com - - JWTISSUER=http://auth:8080 - - JWTPRIVATEKEY=keys/sign-jwt.key - - JWTSIGNATUREALG=ES256 - - INFOTEXT=About Federated EGA - - INFOURL=https://ega-archive.org/about/projects-and-funders/federated-ega/ - - PUBLICFILE=/c4gh.pub.pem - volumes: - - ../keys:/keys - - ../:/sda-auth - - /tmp/c4gh.pub.pem:/c4gh.pub.pem - image: sda-auth - ports: - - 8080:8080 - remote: - container_name: remote - image: nginx:1.21 - volumes: - - ./remote:/usr/share/nginx/html - ports: - - 8000:80 diff --git a/sda-auth/dev-server/oidc/.dockerignore b/sda-auth/dev-server/oidc/.dockerignore deleted file mode 100644 index 4819d0e8a..000000000 --- a/sda-auth/dev-server/oidc/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -package-lock.json -npm-debug.log diff --git a/sda-auth/dev-server/oidc/Dockerfile b/sda-auth/dev-server/oidc/Dockerfile deleted file mode 100644 index be231e034..000000000 --- a/sda-auth/dev-server/oidc/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:16.2.0-slim - -WORKDIR /app - -RUN apt update && apt upgrade -qy && apt install -qy curl - -COPY package.json ./ - -RUN rm -rf node_modules - -RUN npm install -g npm@latest && \ - npm i camelcase oidc-provider - -COPY . . - -CMD [ "node", "server.js" ] diff --git a/sda-auth/dev-server/oidc/package.json b/sda-auth/dev-server/oidc/package.json deleted file mode 100644 index 4b4a1a554..000000000 --- a/sda-auth/dev-server/oidc/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "mock-oidc", - "version": "0.1.0", - "main": "server.js", - "engines": { - "node": "12" - }, - "scripts": { - "start": "node server.js" - }, - "dependencies": { - "camelcase": "5.3.1", - "oidc-provider": "5.5.2" - } -} diff --git a/sda-auth/dev-server/oidc/server.js b/sda-auth/dev-server/oidc/server.js deleted file mode 100644 index 04593b848..000000000 --- a/sda-auth/dev-server/oidc/server.js +++ /dev/null @@ -1,86 +0,0 @@ -const assert = require('assert'); -const camelCase = require('camelcase'); -const Provider = require('oidc-provider'); - -const port = process.env.PORT || 3000; -// External port can legally be an empty string -const ext_port = process.env.EXTERNAL_PORT ?? process.env.PORT; -const host = process.env.HOST || "oidc" ; - -const config = ['CLIENT_ID', 'CLIENT_SECRET', 'CLIENT_REDIRECT_URI'].reduce((acc, v) => { - assert(process.env[v], `${v} config missing`); - acc[camelCase(v)] = process.env[v]; - return acc; -}, {}); - -const oidcConfig = { - - features: { - devInteractions: true, - discovery: true, - registration: false, - revocation: true, - sessionManagement: false - }, - formats: { - default: 'jwt', - AccessToken: 'jwt', - RefreshToken: 'jwt' - }, - routes: { - authorization: process.env.AUTH_ROUTE || '/auth', - introspection: process.env.INTROSPECTION_ROUTE || '/token/introspection', - certificates: process.env.JWKS_ROUTE || '/jwks', - revocation: process.env.REVOCATION_ROUTE ||'/token/revocation', - token: process.env.TOKEN_ROUTE || '/token', - userinfo: process.env.USERINFO_ROUTE ||'/userinfo' - }, - scopes: [ - 'openid', - 'ga4gh_passport_v1', - 'profile', - 'email', - 'offline_access' - ], - claims: { - acr: null, - sid: null, - ga4gh_passport_v1: ['ga4gh_passport_v1'], - auth_time: null, - ss: null, - openid: [ 'sub' ], - profile: ['name', 'email'] - }, - - findById: async function findById(ctx, sub, token) { - return { - accountId: sub, - async claims(use, scope, claims, rejected) { - return { name: 'Dummy Tester', email:'dummy.tester@gs.uu.se', sub, ga4gh_passport_v1: ['eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIwIiwibmFtZSI6InRlc3QiLCJnYTRnaF92aXNhX3YxIjp7ImFzc2VydGVkIjoxLCJieSI6InN5c3RlbSIsInNvdXJjZSI6Imh0dHA6Ly93d3cudXUuc2UvZW4vIiwidHlwZSI6IkFmZmlsaWF0aW9uQW5kUm9sZSIsInZhbHVlIjoic3RhZmZAdXUuc2UifSwiYWRtaW4iOnRydWUsImp0aSI6InRlc3QiLCJpYXQiOjE1ODQ4OTc4NDIsImV4cCI6MTU4NDkwMTQ0Mn0.RkAULuJEaExt0zVu3_uE2BSdkHLAHRD8owqhrsrTfLI'] }; - }, - }; - }, - -}; - -const oidc = new Provider(`http://${host}${ext_port ? ':' : ''}${ext_port}`, oidcConfig); - -const clients= [{ - client_id: config.clientId, - client_secret: config.clientSecret, - redirect_uris: config.clientRedirectUri.split(",") - }]; - -let server; -(async () => { -await oidc.initialize({ clients }); - server = oidc.listen(port, () => { - console.log( - `mock-oidc-user-server listening on port ${port}, check http://${host}:${port}/.well-known/openid-configuration` - ); - }); -})().catch(err => { - if (server && server.listening) server.close(); - console.error(err); - process.exitCode = 1; -}); diff --git a/sda-auth/dev-server/remote/index.html b/sda-auth/dev-server/remote/index.html deleted file mode 100644 index 62c0fc367..000000000 --- a/sda-auth/dev-server/remote/index.html +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - CORS login test page - - - - Log in -
- Reset - -
-

-
-    
-    
-
diff --git a/sda-auth/go.mod b/sda-auth/go.mod
deleted file mode 100644
index 96f378829..000000000
--- a/sda-auth/go.mod
+++ /dev/null
@@ -1,90 +0,0 @@
-module auth
-
-go 1.21
-
-require (
-	github.com/coreos/go-oidc v2.2.1+incompatible
-	github.com/google/uuid v1.6.0
-	github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee
-	github.com/kataras/iris/v12 v12.2.10
-	github.com/lestrrat-go/jwx/v2 v2.0.20
-	github.com/neicnordic/crypt4gh v1.9.1
-	github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282
-	github.com/sirupsen/logrus v1.9.3
-	github.com/spf13/viper v1.18.2
-	github.com/stretchr/testify v1.8.4
-	golang.org/x/crypto v0.20.0
-	golang.org/x/oauth2 v0.17.0
-	gopkg.in/yaml.v3 v3.0.1
-)
-
-require (
-	filippo.io/edwards25519 v1.1.0 // indirect
-	github.com/BurntSushi/toml v1.3.2 // indirect
-	github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
-	github.com/CloudyKit/jet/v6 v6.2.0 // indirect
-	github.com/Joker/jade v1.1.3 // indirect
-	github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
-	github.com/andybalholm/brotli v1.1.0 // indirect
-	github.com/aymerick/douceur v0.2.0 // indirect
-	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
-	github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a // indirect
-	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
-	github.com/fatih/structs v1.1.0 // indirect
-	github.com/flosch/pongo2/v4 v4.0.2 // indirect
-	github.com/fsnotify/fsnotify v1.7.0 // indirect
-	github.com/goccy/go-json v0.10.2 // indirect
-	github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
-	github.com/golang/protobuf v1.5.3 // indirect
-	github.com/golang/snappy v0.0.4 // indirect
-	github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 // indirect
-	github.com/gorilla/css v1.0.1 // indirect
-	github.com/hashicorp/hcl v1.0.0 // indirect
-	github.com/iris-contrib/schema v0.0.6 // indirect
-	github.com/josharian/intern v1.0.0 // indirect
-	github.com/kataras/blocks v0.0.8 // indirect
-	github.com/kataras/golog v0.1.11 // indirect
-	github.com/kataras/pio v0.0.13 // indirect
-	github.com/kataras/sitemap v0.0.6 // indirect
-	github.com/kataras/tunnel v0.0.4 // indirect
-	github.com/klauspost/compress v1.17.6 // indirect
-	github.com/lestrrat-go/blackmagic v1.0.2 // indirect
-	github.com/lestrrat-go/httpcc v1.0.1 // indirect
-	github.com/lestrrat-go/httprc v1.0.4 // indirect
-	github.com/lestrrat-go/iter v1.0.2 // indirect
-	github.com/lestrrat-go/option v1.0.1 // indirect
-	github.com/magiconair/properties v1.8.7 // indirect
-	github.com/mailgun/raymond/v2 v2.0.48 // indirect
-	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/microcosm-cc/bluemonday v1.0.26 // indirect
-	github.com/mitchellh/mapstructure v1.5.0 // indirect
-	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
-	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
-	github.com/pquerna/cachecontrol v0.2.0 // indirect
-	github.com/russross/blackfriday/v2 v2.1.0 // indirect
-	github.com/sagikazarmark/locafero v0.4.0 // indirect
-	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
-	github.com/schollz/closestmatch v2.1.0+incompatible // indirect
-	github.com/segmentio/asm v1.2.0 // indirect
-	github.com/sourcegraph/conc v0.3.0 // indirect
-	github.com/spf13/afero v1.11.0 // indirect
-	github.com/spf13/cast v1.6.0 // indirect
-	github.com/spf13/pflag v1.0.5 // indirect
-	github.com/subosito/gotenv v1.6.0 // indirect
-	github.com/tdewolff/minify/v2 v2.20.16 // indirect
-	github.com/tdewolff/parse/v2 v2.7.11 // indirect
-	github.com/valyala/bytebufferpool v1.0.0 // indirect
-	github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
-	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
-	github.com/yosssi/ace v0.0.5 // indirect
-	go.uber.org/multierr v1.11.0 // indirect
-	golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
-	golang.org/x/net v0.21.0 // indirect
-	golang.org/x/sys v0.17.0 // indirect
-	golang.org/x/text v0.14.0 // indirect
-	golang.org/x/time v0.5.0 // indirect
-	google.golang.org/appengine v1.6.8 // indirect
-	google.golang.org/protobuf v1.32.0 // indirect
-	gopkg.in/ini.v1 v1.67.0 // indirect
-	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
-)
diff --git a/sda-auth/go.sum b/sda-auth/go.sum
deleted file mode 100644
index f5b09c409..000000000
--- a/sda-auth/go.sum
+++ /dev/null
@@ -1,288 +0,0 @@
-filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
-filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
-github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
-github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
-github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
-github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
-github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
-github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
-github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
-github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
-github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
-github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
-github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
-github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
-github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
-github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
-github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
-github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
-github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
-github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
-github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
-github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
-github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
-github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
-github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
-github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
-github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
-github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
-github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
-github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
-github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
-github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
-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/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
-github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
-github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
-github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
-github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
-github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
-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/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
-github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
-github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k=
-github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
-github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
-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/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
-github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
-github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
-github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
-github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
-github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go=
-github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE=
-github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee h1:srr0HUvI9IAximtqxhF0+WSy9cv9Dd5YlH8RMEkHtsw=
-github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee/go.mod h1:hAsE+pfCAz1M2cCfsEolTmaBwHCyzchRFyCk5I1+kDo=
-github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
-github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
-github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
-github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM=
-github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg=
-github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20=
-github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A=
-github.com/kataras/iris/v12 v12.2.10 h1:rEJVM7qMoyhv8wpgkA1yGxibFcONE0jkJ70LFLibTAA=
-github.com/kataras/iris/v12 v12.2.10/go.mod h1:z4+E+kLMqZ7U4WtDsYfFnG7BjMTXLkdzMAXLVMLnMNs=
-github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
-github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
-github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY=
-github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
-github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
-github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
-github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
-github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
-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/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
-github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
-github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
-github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
-github.com/lestrrat-go/httprc v1.0.4 h1:bAZymwoZQb+Oq8MEbyipag7iSq6YIga8Wj6GOiJGdI8=
-github.com/lestrrat-go/httprc v1.0.4/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
-github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
-github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
-github.com/lestrrat-go/jwx/v2 v2.0.20 h1:sAgXuWS/t8ykxS9Bi2Qtn5Qhpakw1wrcjxChudjolCc=
-github.com/lestrrat-go/jwx/v2 v2.0.20/go.mod h1:UlCSmKqw+agm5BsOBfEAbTvKsEApaGNqHAEUTv5PJC4=
-github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
-github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
-github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
-github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
-github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
-github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
-github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-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.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
-github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
-github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
-github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
-github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
-github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/neicnordic/crypt4gh v1.9.1 h1:NKeOmsZ1/0xDpLvBDjN8Ltoh3ODbLB+NeKulDaNo4Oc=
-github.com/neicnordic/crypt4gh v1.9.1/go.mod h1:C8jHyUkt3bnQg0EwdRRswzvLXfu4dyLQOPARDIQTU24=
-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/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282 h1:TQMyrpijtkFyXpNI3rY5hsZQZw+paiH+BfAlsb81HBY=
-github.com/oauth2-proxy/mockoidc v0.0.0-20220308204021-b9169deeb282/go.mod h1:rW25Kyd08Wdn3UVn0YBsDTSvReu0jqpmJKzxITPSjks=
-github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
-github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
-github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
-github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
-github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
-github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
-github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
-github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
-github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
-github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
-github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
-github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
-github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
-github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
-github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
-github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
-github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
-github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
-github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
-github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
-github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
-github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
-github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
-github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
-github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
-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/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
-github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
-github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
-github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
-github.com/tdewolff/minify/v2 v2.20.16 h1:/C8dtRkxLTIyUlKlBz46gDiktCrE8a6+c1gTrnPFz+U=
-github.com/tdewolff/minify/v2 v2.20.16/go.mod h1:/FvxV9KaTrFu35J9I2FhRvWSBxcHj8sDSdwBFh5voxM=
-github.com/tdewolff/parse/v2 v2.7.11 h1:v+W45LnzmjndVlfqPCT5gGjAAZKd1GJGOPJveTIkBY8=
-github.com/tdewolff/parse/v2 v2.7.11/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
-github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
-github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
-github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
-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/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
-github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
-github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
-github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
-github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
-github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
-github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
-github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
-github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
-github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
-github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
-github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
-github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
-github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
-github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
-github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
-github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
-github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
-go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
-golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
-golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFcdWLGIKnZA4IXNFSrvo=
-golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
-golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
-golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
-golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
-golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
-golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
-golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
-golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
-golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
-golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
-golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
-google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
-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.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
-google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
-gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
-moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
diff --git a/sda-auth/hooks/build b/sda-auth/hooks/build
deleted file mode 100644
index 0c1346458..000000000
--- a/sda-auth/hooks/build
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-docker build \
-       --cache-from neicnordic/sda-auth:latest \
-       --build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
-       --build-arg SOURCE_COMMIT=$(git rev-parse --short HEAD) \
-       --tag $IMAGE_NAME .
diff --git a/sda-auth/keys/cert.pem b/sda-auth/keys/cert.pem
deleted file mode 100644
index af8d81d57..000000000
--- a/sda-auth/keys/cert.pem
+++ /dev/null
@@ -1,34 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIF0zCCA7ugAwIBAgIULq1CtDtu0FwvgPWyLwwWIh8XZD0wDQYJKoZIhvcNAQEL
-BQAweTELMAkGA1UEBhMCU0UxEDAOBgNVBAgMB1VwcHNhbGExEDAOBgNVBAcMB1Vw
-cHNhbGExCzAJBgNVBAoMAlVVMQswCQYDVQQLDAJVVTEPMA0GA1UEAwwGc2RhdXRo
-MRswGQYJKoZIhvcNAQkBFgx0ZXN0QHRlc3Quc2UwHhcNMjAwOTE5MTA1NjQ0WhcN
-MjEwOTE5MTA1NjQ0WjB5MQswCQYDVQQGEwJTRTEQMA4GA1UECAwHVXBwc2FsYTEQ
-MA4GA1UEBwwHVXBwc2FsYTELMAkGA1UECgwCVVUxCzAJBgNVBAsMAlVVMQ8wDQYD
-VQQDDAZzZGF1dGgxGzAZBgkqhkiG9w0BCQEWDHRlc3RAdGVzdC5zZTCCAiIwDQYJ
-KoZIhvcNAQEBBQADggIPADCCAgoCggIBALgtVLCyVUx65kgD4uECC7DxCighu++v
-F4xBPB2lhtfbGj2PZ3b1PXfOFMLiounf+doa6sIIgVHbtq59VO0Mfn5nosAJuQjf
-Hu8/F4NbAe5PZBw3xM8tNgIjQ6QEV9rI0Z5JXkL7/HCZBhgPiyHPZzm7HelvRgPe
-9KQHacWa6feOkdDbCVDtWwEYVP9JaieGFfy/5gbuc5M5aHNZO/p96DGzBJDISPR+
-5ZkOrLcLQ/t6r1Xu9/EuWHApW/ujbEn8mcy+LPvVKsNRBfUdTtrr33HKKKIp1MdR
-uXNLoJnMTVHyYkSK8mZhCshvuGWzloi7DWM3mYQgzjBoPhJHds+URlMOrhHEyzEA
-LU15rckYuQhMpiJyhPKAHzYcB420QWlYv0dJnFD/IUcRn7UQSbzlw6YtcNRxBLHr
-ANIWKeBZvcg0FmdsiRxznoVpCgrGrVT+0Jf5cotRV4dJA89TWi1CQuOMraUvwzU3
-qLBMpr6LcDOktFC6bIY8vjZxbVTkFRwwC1gvadiPZoGTYh0WuSMhJwZjeQXw0nJ3
-Unx8N5K6m77ciRFlZC6YHkjMesGu4VEy/nFERO4m6i/9qpXBBrIF8LAcCg6IWPwt
-Dq6n3dAaNDmymWzlwIQjXDjiBgPDuVOq/SQ55QUlG+UzgBbi2dFDCKGYaGl9Xu6x
-dekuOnZ9WxctAgMBAAGjUzBRMB0GA1UdDgQWBBR6nSQmWrlKMDj4OHIoG8jsjkrk
-ezAfBgNVHSMEGDAWgBR6nSQmWrlKMDj4OHIoG8jsjkrkezAPBgNVHRMBAf8EBTAD
-AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBfBwYKaFI902y+Y6fzvM8Id6i1inb6kMI0
-g/ssWZXXv/AAObcKfbCexdy611mrs+7z+KGWmU21qrTkcZIWocV1Qkq6XYW8n5uk
-AEiSMfkVRQnkiVjOiTSKq1/v0+KtTyU8Zq3Zged/n+aU/Kbt73lsnyOy0NKAW5pD
-TXZdm09wpk16kjTAII5HrC/X5R0PWlT7561A1KsTMygYhk2z4c+UnUTA2X/ggUVq
-GgfUL1iksFlv3i0Tq12ozTrl2qh6TJcVD4TWXLvkoOYwtoc1Iga78MkGodbdFAo3
-UkgOQt1vwxqNxJW/9TE15TLo5IEt6C7cG1IGhsCZfvKC624w9IFfu2gau2r2oXC5
-ngmJ9DKgBoClasRfv4hOclqyEl9Zug0rGQzwYxI0bP9EV0E0j2pu1Avo0kPvfOiE
-ytBcldufmZsDXSgeqZvNbQpmajKH807tdpz/rwrHow4qZ4C/bGWdYvQL9PO7sOKo
-iLRX5TYWN2MYlsvWYqLU3qTQ2UZFJt8QGdiXZzLY5kVItTPXk3BGvxYl6eZ+UJbS
-TP6qDFhyrvNZtBaSuWSofvg6VflqZrnV4X9sP7IycDSt1PrAUiyyYcOU1iCCjWDV
-BTa+r766YU4HGGjsrDafagllL2OoDou8ovPX7r4oLjCYZacTT8dVo5TW3CNu1ae9
-330dQ5Ow8A==
------END CERTIFICATE-----
diff --git a/sda-auth/keys/key.pem b/sda-auth/keys/key.pem
deleted file mode 100644
index 7bb3848b7..000000000
--- a/sda-auth/keys/key.pem
+++ /dev/null
@@ -1,52 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQC4LVSwslVMeuZI
-A+LhAguw8QooIbvvrxeMQTwdpYbX2xo9j2d29T13zhTC4qLp3/naGurCCIFR27au
-fVTtDH5+Z6LACbkI3x7vPxeDWwHuT2QcN8TPLTYCI0OkBFfayNGeSV5C+/xwmQYY
-D4shz2c5ux3pb0YD3vSkB2nFmun3jpHQ2wlQ7VsBGFT/SWonhhX8v+YG7nOTOWhz
-WTv6fegxswSQyEj0fuWZDqy3C0P7eq9V7vfxLlhwKVv7o2xJ/JnMviz71SrDUQX1
-HU7a699xyiiiKdTHUblzS6CZzE1R8mJEivJmYQrIb7hls5aIuw1jN5mEIM4waD4S
-R3bPlEZTDq4RxMsxAC1Nea3JGLkITKYicoTygB82HAeNtEFpWL9HSZxQ/yFHEZ+1
-EEm85cOmLXDUcQSx6wDSFingWb3INBZnbIkcc56FaQoKxq1U/tCX+XKLUVeHSQPP
-U1otQkLjjK2lL8M1N6iwTKa+i3AzpLRQumyGPL42cW1U5BUcMAtYL2nYj2aBk2Id
-FrkjIScGY3kF8NJyd1J8fDeSupu+3IkRZWQumB5IzHrBruFRMv5xRETuJuov/aqV
-wQayBfCwHAoOiFj8LQ6up93QGjQ5spls5cCEI1w44gYDw7lTqv0kOeUFJRvlM4AW
-4tnRQwihmGhpfV7usXXpLjp2fVsXLQIDAQABAoICAQCPY4Nu+bhdDcXhRV1Kjrwp
-as/chL77kRbFNDioYCmGPmfEi7QisjXD42bYf3gUmzK7cn9YxyRfZa5pVNxcnYCR
-yv+zgR5U4NkFrNoSgzUSoy6upWCNZ4aHzYqo1FTN2dEQ6dAAWIyl4Q0UiG1qyj10
-fdCA4AfjlZ5jYf4gQUZsXMv7jbxIDDLwvE/YXQDPep39pC1jMhw6/9PpEg0XLRUs
-NFKWocfiyjTYn8spyxSHYnHFdoIEfG+QCzM4y3fQdmALPfhprAFaBOedeM7etArH
-XXubHPQypda7A6MqehusdAe7k2269Uxe1lP6ogHNu8tldnEHpD7DAzVoJ8lBPRAt
-4PyRLvexwJcEbt1vSGL4bcWlE8LTLJlwLX7FopuNcIhXUj/+lx+lh5OeG03FGqbJ
-4YGUswL8DUqWJcDc7Qm3PolgzwEZUglaTgkkezmBVlTc9QVysvxoxbYwu/7VxcPc
-6yEoVPUWSIOkJOjJy5t6nR4ew4sn19TwgNHC3+HjkFWjJpQlYgxDEr2ybthZGT9d
-VHl+nF7Gby5q5ODjTaojBncbM/D9Y3ccW8aWtbt5b1gQuEtWovxTvt6iGvwalwIC
-xsZmHm+qDE1ah12nqjxIjsIjAOdA4P7+mTUiBmYsxwvDwpImRRsviuLc5OiwSmCV
-xEvjUDz1O0aeqLldzC9TpQKCAQEA6EteRtz91EM13Mx8McFI6a1PxQi0Zqucilru
-6AgugOekIfQSvubBAZXu5bVSKplyp1zqaji+cPB/GCF+M3LRHIfp/vRgoDMt9Fdh
-N7I/ntk+E6fsaH6zqx94p8IymLh1jYDJw2csKo9EGvUZHoKCNj8IFdj583UnGhAQ
-smemgJ2UDu0O3OUAkGRRpil+ICdcgd/aipn/v1TxvaipQ83ag2/Bx2lwIFyAJ8EM
-f5BQTlRqrF3ZFDKr4+qn0jF4pEqFrpWH3eitPBtLLxqlgOcoZW3aCrMMy7VykRnS
-knI1vEhGUEg3je1S0Mgnb0RkGbjg44xBpozk/fpU5ihQgLTvwwKCAQEAyvjo2URP
-GL6KpBxZSnsDJvRAvwQz8nvZea006OgiX4R4dZhVfuOYS2heIIaOlTv4U982AFDc
-OFQNFsXyAJv+cO/+zGJaV9qQfbeC8r2g0+k508wY5ONDkCtxL59LrKDUJRsg5yRS
-UQJreU/Vo+RigrrDuZ1PT39583ZvVwm01RunoyhfCZ+ynj/P4eEC2GfyO29Moobs
-5rzc7U+bclTj5gKmOe14sweFTmWx5E+rBGqbL20bIeeQPyCGArXoc3uUX/kCcxQS
-c8fwb3lubIGmgITtwn3M9QVoqpV8eugFtSUSGe4Ucbha2nC69McEUEbgWLFuC9UW
-CZ8Kyhw9nF7eTwKCAQEA2Z3h0Fkvoi7kwtj+cE421FN8q4WgG58ho6vUrKMdjb10
-UiqpdRYQMkAX8jKPq0Dd3FR7ds95+awUJLMb227w392kJSkjbHEF2CYT+W2PlGzI
-bvmdy7cG8oUlL2QKqnAEEHBalCiPg797ehWrVp3FBrS+Clmer6lM3Gm4eqoHne4B
-yonipqYShTK2Gwg8FDtLuo/P2JC0urYFUjwzdkXfM8r6qqnoVSjTFmsQHmFAGmGT
-Ha89smJWDcrDNXMLJj56RjfndMiJogmfwZ0g90WW3o6u+d8LWbu+fJjIYFGGbcD3
-vjYpdVijgNxBtUdIz6AXbpTy2+iajiR6bLPP1wOqqQKCAQAqMPl+CFQEyb7aBEaP
-U+jA1S8MRdxigFohj96at35Fqqy7jRHgkYDb0/MVAcNrxA6HqLN591T/pIEgBb+b
-XtHSOiEV7S/oaTd12oqu2Lowsp9hpmBaW990fYNGqe86EChpGi2JvqQZ0M30ZqL7
-MYNEt3vt/oQ4+z+nWywjxJHBZsE/CipPPtILUSpuQq6ru1fAAQ/GDRP7lrC5571h
-U8IX0t8o1XMsi9irBjqnQeRDw8FEZ4KnVpJGfQU+VP/f2YVrOcDHmRmI1bvmADDy
-/zRULM2bizjqidhK9U3t4oMlN3J+z12SkgbYt2w1a5U69rC13xQIA6BUGapNfvdT
-pOjDAoIBAHbtTkcJw26AeKwhWsrL6U7pxLfWff0KE8YMJRukpvrGBS/HNYHSM4bh
-2Psq8FDPyfqsuRhInbPvgeAM8IQLW1LRYx4uIi97TvBzoIxwUZNiPISBHtZSL6R8
-XwRynwzCjYtorhay2w8gGvbPIw93crnfc6xh03xU++vx2Wq1cid0Akg9PxEcXuoo
-3tp1QEyuenzb1+4ADaoYFT+wkDdrCoj4I51Ljf3BEwbbfRtG9byw2AsS/4jA3cxg
-FXAX9JUbAn/crMZMJ0lt4+PWLCbWF4J3NR6NWFHBzLvKBAFLOIB60tzIkAhYC5OM
-h4bS/AwPLInvxr3d2Q8zXCbFqCJ4zBI=
------END PRIVATE KEY-----
diff --git a/sda-auth/keys/sign-jwt.key b/sda-auth/keys/sign-jwt.key
deleted file mode 100644
index 88a342896..000000000
--- a/sda-auth/keys/sign-jwt.key
+++ /dev/null
@@ -1,5 +0,0 @@
------BEGIN EC PRIVATE KEY-----
-MHcCAQEEIOwU+SoxTKhL/btDfEQgArNGYOj6wu1wIMkL9pEpJaDAoAoGCCqGSM49
-AwEHoUQDQgAEOcK0xMvOLSdZYTzAEXVTDeEpXdTqh026D8RbX10vG9x7U2cED8Gf
-ikWwDVYNp0JldJOOxiqH/xJRJQ9Zhweqlg==
------END EC PRIVATE KEY-----
diff --git a/sda-auth/tests/integration/__init__.py b/sda-auth/tests/integration/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/sda-auth/tests/integration/__pycache__/__init__.cpython-37.pyc b/sda-auth/tests/integration/__pycache__/__init__.cpython-37.pyc
deleted file mode 100644
index 2e81fd44b..000000000
Binary files a/sda-auth/tests/integration/__pycache__/__init__.cpython-37.pyc and /dev/null differ
diff --git a/sda-auth/tests/integration/__pycache__/test_auth.cpython-37-pytest-6.0.1.pyc b/sda-auth/tests/integration/__pycache__/test_auth.cpython-37-pytest-6.0.1.pyc
deleted file mode 100644
index f683d3323..000000000
Binary files a/sda-auth/tests/integration/__pycache__/test_auth.cpython-37-pytest-6.0.1.pyc and /dev/null differ
diff --git a/sda-auth/tests/integration/test_auth.py b/sda-auth/tests/integration/test_auth.py
deleted file mode 100644
index df7a09dd4..000000000
--- a/sda-auth/tests/integration/test_auth.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import unittest
-import requests
-
-
-class TestElixirAuth(unittest.TestCase):
-    """ElixirAuth.
-    Testing ElixirAuth."""
-
-    def setUp(self):
-        """Initialise authenticator."""
-        self.backend_url = "http://localhost:8080/elixir/"
-
-
-    def tearDown(self):
-        """Finalise test."""
-        print("Finishing test")
-
-
-    def test_valid_elixir_login(self):
-        """Test that the login endpoint is active."""
-
-        grant_response = requests.get(self.backend_url,
-                                allow_redirects=True)
-
-        print("Grant response")
-        location = grant_response.url
-        grant_id = location.split('/').pop()
-        print(grant_id)
-        print(location)
-
-        self.assertEqual(grant_response.status_code, 200)
-        self.assertIsNotNone(grant_id)
-
-        oidc_url = f'http://oidc:9090/interaction/{grant_id}/submit'
-        cookies = {"_grant": grant_id}
-        creds_payload = {"view":'login',
-                         "login":'dummy@example.com',
-                         "password":'dummy',
-                         "submit": ''}
-
-        oidc_response = requests.post(oidc_url,
-                                allow_redirects=True,
-                                data=creds_payload,
-                                cookies=cookies)
-
-        location = oidc_response.url
-        self.assertEqual(oidc_response.status_code, 200)
-        self.assertIs(self.backend_url in location, True)
-
-class TestEGAAuth(unittest.TestCase):
-    """EgaAuth.
-    Testing EgaAuth."""
-
-    def setUp(self):
-        """Initialise authenticator."""
-        self.backend_url = "http://localhost:8080/ega"
-
-
-    def tearDown(self):
-        """Finalise test."""
-        print("Finishing test")
-
-
-    def test_valid_ega_login(self):
-        """Test that the login is successful."""
-        creds_payload = { "username":'dummy@example.com',
-                         "password":'dummy',
-                         "submit": 'log+in' }
-
-        login_response = requests.post(self.backend_url,
-                                       allow_redirects=False,
-                                       data=creds_payload,
-                                       cookies=None)
-
-
-        self.assertEqual(login_response.status_code, 200)
-
-
-    def test_invalid_ega_login(self):
-        """Test that the login is not successful."""
-        creds_payload = { "username":'dummy@foo.bar',
-                         "password":'wrongpassword',
-                         "submit": 'log+in' }
-
-        login_response = requests.post(self.backend_url,
-                                       allow_redirects=False,
-                                       data=creds_payload,
-                                       cookies=None)
-
-
-        self.assertEqual(login_response.status_code, 303)
diff --git a/sda/Dockerfile b/sda/Dockerfile
index b44d7bed2..597e8e712 100644
--- a/sda/Dockerfile
+++ b/sda/Dockerfile
@@ -19,6 +19,7 @@ LABEL org.label-schema.vcs-url="https://github.com/neicnordic/sensitive-data-arc
 LABEL org.label-schema.vcs-ref=$SOURCE_COMMIT
 
 COPY --from=Build /go/sda-* /usr/local/bin/
+COPY --from=Build /go/cmd/auth/frontend /frontend
 COPY --from=Build /go/schemas /schemas
 
 USER 65534
@@ -34,6 +35,7 @@ LABEL org.label-schema.vcs-url="https://github.com/neicnordic/sensitive-data-arc
 LABEL org.label-schema.vcs-ref=$SOURCE_COMMIT
 
 COPY --from=Build /go/sda-* /usr/local/bin/
+COPY --from=Build /go/cmd/auth/frontend /frontend
 COPY --from=Build /go/schemas /schemas
 
 USER 65534
diff --git a/sda/cmd/auth/auth.md b/sda/cmd/auth/auth.md
new file mode 100644
index 000000000..44bc4b366
--- /dev/null
+++ b/sda/cmd/auth/auth.md
@@ -0,0 +1,98 @@
+# SDA authentication service
+
+This service allows users to log in both via LS-AAI (OIDC) or EGA (NSS).
+
+After successful authentication users will be able to get the `access token` and download the `S3 config file` needed in order to be able to upload files to the [S3Inbox service](../s3inbox/s3inbox.md).
+
+## Choosing provider login
+
+The `auth` allows for two different types of login providers: `EGA` and `LS_AAI` (OIDC). It is possible, to run the service using both or only one of the providers.
+
+In order to remove the `EGA` option, remove the `CEGA_ID` and `CEGA_SECRET` options from the configuration, while for removing the `LS-AAI` option, remove the `OIDC_ID` and `OIDC_SECRET` variables.
+
+## Configuration example for local testing
+
+The following settings can be configured for deploying the service, either by using environment variables or a YAML file.
+
+| Parameter               | Description                                                                          | Defined value                           |
+| ----------------------- | ------------------------------------------------------------------------------------ | --------------------------------------- |
+| `AUTH_CEGA_AUTHURL`     | CEGA server endpoint                                                                 | `http://cega:8443/lega/v1/legas/users/` |
+| `AUTH_CEGA_ID`          | CEGA server authentication id                                                        | `dummy`                                 |
+| `AUTH_CEGA_SECRET`      | CEGA server authentication secret                                                    | `dummy`                                 |
+| `AUTH_CORS_CREDENTIALS` | If cookies, authorization headers, and TLS client certificates are allowed over CORS | `false`                                 |
+| `AUTH_CORS_METHODS`     | Allowed Cross-Origin Resource Sharing (CORS) methods                                 | `""`                                    |
+| `AUTH_CORS_ORIGINS`     | Allowed Cross-Origin Resource Sharing (CORS) origins                                 | `""`                                    |
+| `AUTH_JWT_ISSUER`       | Issuer of JWT tokens                                                                 | `http://auth:8080`                      |
+| `AUTH_JWT_PRIVATEKEY`   | Path to private key for signing the JWT token                                        | `keys/sign-jwt.key`                     |
+| `AUTH_JWT_SIGNATUREALG` | Algorithm used to sign the JWT token. ES256 (ECDSA) or RS256 (RSA) are supported     | `ES256`                                 |
+| `AUTH_RESIGNJWT`        | Set to `false` to serve the raw OIDC JWT, i.e. without re-signing it                 | `""`                                    |
+| `AUTH_S3INBOX`          | S3 inbox host                                                                        | `http://s3.example.com`                 |
+| `LOG_LEVEL`             | Log level                                                                            | `info`                                  |
+| `OIDC_ID`               | OIDC authentication id                                                               | `XC56EL11xx`                            |
+| `OIDC_SECRET`           | OIDC authentication secret                                                           | `wHPVQaYXmdDHg`                         |
+| `OIDC_PROVIDER`         | OIDC issuer URL                                                                      | `http://oidc:8080`                      |
+| `OIDC_JWKPATH`          | JWK endpoint where the public key can be retrieved for token validation              | `/jwks`                                 |
+| `SERVER_CERT`           | Certificate file path                                                                | `""`                                    |
+| `SERVER_KEY`            | Private key file path                                                                | `""`                                    |
+
+## Running with Cross-Origin Resource Sharing (CORS)
+
+This service can be run as a backend only, and in the case where the frontend is running somewhere else, CORS is needed.
+
+Recommended CORS settings for a given host are:
+
+```txt
+AUTH_CORS_ORIGINS="https://"
+AUTH_CORS_METHODS="GET,OPTIONS,POST"
+AUTH_CORS_CREDENTIALS="true"
+```
+
+A minimal CORS login (for testing purposes) can look like this:
+
+```html
+
+
+
+
+    
+    
+    CORS login test page
+
+
+
+    Log in
+    
+ Reset + +
+

+
+
+
+
+
+```
diff --git a/sda-auth/cega.go b/sda/cmd/auth/cega.go
similarity index 89%
rename from sda-auth/cega.go
rename to sda/cmd/auth/cega.go
index 8f9cabedb..54f2919bf 100644
--- a/sda-auth/cega.go
+++ b/sda/cmd/auth/cega.go
@@ -6,6 +6,7 @@ import (
 	"net/http"
 	"strings"
 
+	"github.com/neicnordic/sensitive-data-archive/internal/config"
 	log "github.com/sirupsen/logrus"
 	bcrypt "golang.org/x/crypto/bcrypt"
 )
@@ -42,7 +43,7 @@ func verifyPassword(password, hash string) bool {
 }
 
 // Authenticate against CEGA
-func authenticateWithCEGA(conf CegaConfig, username string) (*http.Response, error) {
+func authenticateWithCEGA(conf config.CegaConfig, username string) (*http.Response, error) {
 	client := &http.Client{}
 	payload := strings.NewReader("")
 	req, err := http.NewRequest("GET", fmt.Sprintf("%s/%s", strings.TrimSuffix(conf.AuthURL, "/"), username), payload)
diff --git a/sda-auth/cega_test.go b/sda/cmd/auth/cega_test.go
similarity index 100%
rename from sda-auth/cega_test.go
rename to sda/cmd/auth/cega_test.go
diff --git a/sda-auth/frontend/static/EGA.png b/sda/cmd/auth/frontend/static/EGA.png
similarity index 100%
rename from sda-auth/frontend/static/EGA.png
rename to sda/cmd/auth/frontend/static/EGA.png
diff --git a/sda-auth/frontend/static/FEGA-logo-generic.svg b/sda/cmd/auth/frontend/static/FEGA-logo-generic.svg
similarity index 100%
rename from sda-auth/frontend/static/FEGA-logo-generic.svg
rename to sda/cmd/auth/frontend/static/FEGA-logo-generic.svg
diff --git a/sda-auth/frontend/static/Lifescience-RI.png b/sda/cmd/auth/frontend/static/Lifescience-RI.png
similarity index 100%
rename from sda-auth/frontend/static/Lifescience-RI.png
rename to sda/cmd/auth/frontend/static/Lifescience-RI.png
diff --git a/sda-auth/frontend/static/bootstrap.min.css b/sda/cmd/auth/frontend/static/bootstrap.min.css
similarity index 100%
rename from sda-auth/frontend/static/bootstrap.min.css
rename to sda/cmd/auth/frontend/static/bootstrap.min.css
diff --git a/sda-auth/frontend/static/bootstrap.min.js b/sda/cmd/auth/frontend/static/bootstrap.min.js
similarity index 100%
rename from sda-auth/frontend/static/bootstrap.min.js
rename to sda/cmd/auth/frontend/static/bootstrap.min.js
diff --git a/sda-auth/frontend/static/custom.css b/sda/cmd/auth/frontend/static/custom.css
similarity index 100%
rename from sda-auth/frontend/static/custom.css
rename to sda/cmd/auth/frontend/static/custom.css
diff --git a/sda-auth/frontend/static/jquery-3.5.1.min.js b/sda/cmd/auth/frontend/static/jquery-3.5.1.min.js
similarity index 100%
rename from sda-auth/frontend/static/jquery-3.5.1.min.js
rename to sda/cmd/auth/frontend/static/jquery-3.5.1.min.js
diff --git a/sda-auth/frontend/static/login.js b/sda/cmd/auth/frontend/static/login.js
similarity index 100%
rename from sda-auth/frontend/static/login.js
rename to sda/cmd/auth/frontend/static/login.js
diff --git a/sda-auth/frontend/templates/ega.html b/sda/cmd/auth/frontend/templates/ega.html
similarity index 100%
rename from sda-auth/frontend/templates/ega.html
rename to sda/cmd/auth/frontend/templates/ega.html
diff --git a/sda-auth/frontend/templates/elixir.html b/sda/cmd/auth/frontend/templates/elixir.html
similarity index 100%
rename from sda-auth/frontend/templates/elixir.html
rename to sda/cmd/auth/frontend/templates/elixir.html
diff --git a/sda-auth/frontend/templates/index.html b/sda/cmd/auth/frontend/templates/index.html
similarity index 100%
rename from sda-auth/frontend/templates/index.html
rename to sda/cmd/auth/frontend/templates/index.html
diff --git a/sda-auth/frontend/templates/loginform.html b/sda/cmd/auth/frontend/templates/loginform.html
similarity index 100%
rename from sda-auth/frontend/templates/loginform.html
rename to sda/cmd/auth/frontend/templates/loginform.html
diff --git a/sda-auth/info.go b/sda/cmd/auth/info.go
similarity index 92%
rename from sda-auth/info.go
rename to sda/cmd/auth/info.go
index 375bad2fe..ad7d75d15 100644
--- a/sda-auth/info.go
+++ b/sda/cmd/auth/info.go
@@ -34,7 +34,7 @@ func readPublicKeyFile(filename string) (key *[32]byte, err error) {
 
 // getInfo returns information needed by the client to authenticate
 func (auth AuthHandler) getInfo(ctx iris.Context) {
-	info := Info{ClientID: auth.OAuth2Config.ClientID, OidcURI: auth.Config.Elixir.Provider, PublicKey: auth.pubKey, InboxURI: auth.Config.S3Inbox}
+	info := Info{ClientID: auth.OAuth2Config.ClientID, OidcURI: auth.Config.OIDC.Provider, PublicKey: auth.pubKey, InboxURI: auth.Config.S3Inbox}
 
 	err := ctx.JSON(info)
 	if err != nil {
diff --git a/sda-auth/jwt.go b/sda/cmd/auth/jwt.go
similarity index 100%
rename from sda-auth/jwt.go
rename to sda/cmd/auth/jwt.go
diff --git a/sda-auth/jwt_test.go b/sda/cmd/auth/jwt_test.go
similarity index 100%
rename from sda-auth/jwt_test.go
rename to sda/cmd/auth/jwt_test.go
diff --git a/sda-auth/main.go b/sda/cmd/auth/main.go
similarity index 84%
rename from sda-auth/main.go
rename to sda/cmd/auth/main.go
index 94f46e7cd..ff4f1a166 100644
--- a/sda-auth/main.go
+++ b/sda/cmd/auth/main.go
@@ -16,6 +16,7 @@ import (
 	"github.com/kataras/iris/v12"
 	"github.com/kataras/iris/v12/sessions"
 	"github.com/lestrrat-go/jwx/v2/jwt"
+	"github.com/neicnordic/sensitive-data-archive/internal/config"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/oauth2"
 )
@@ -26,12 +27,12 @@ type LoginOption struct {
 }
 
 type OIDCData struct {
-	S3Conf   map[string]string
-	ElixirID ElixirIdentity
+	S3Conf map[string]string
+	OIDCID OIDCIdentity
 }
 
 type AuthHandler struct {
-	Config       *Config
+	Config       config.AuthConf
 	OAuth2Config oauth2.Config
 	OIDCProvider *oidc.Provider
 	htmlDir      string
@@ -84,9 +85,9 @@ func (auth AuthHandler) getMain(ctx iris.Context) {
 func (auth AuthHandler) getLoginOptions(ctx iris.Context) {
 
 	var response []LoginOption
-	// Only add the Elixir option if it has both id and secret
-	if auth.Config.Elixir.ID != "" && auth.Config.Elixir.Secret != "" {
-		response = append(response, LoginOption{Name: "Lifescience-RI", URL: "/elixir"})
+	// Only add the OIDC option if it has both id and secret
+	if auth.Config.OIDC.ID != "" && auth.Config.OIDC.Secret != "" {
+		response = append(response, LoginOption{Name: "Lifescience-RI", URL: "/oidc"})
 	}
 
 	// Only add the CEGA option if it has both id and secret
@@ -212,13 +213,13 @@ func (auth AuthHandler) getEGALogin(ctx iris.Context) {
 	}
 }
 
-// getEGAConf returns an s3config file for an elixir login
+// getEGAConf returns an s3config file for an oidc login
 func (auth AuthHandler) getEGAConf(ctx iris.Context) {
 	auth.getInboxConfig(ctx, "ega")
 }
 
-// getElixir redirects to the elixir page defined in auth.Config
-func (auth AuthHandler) getElixir(ctx iris.Context) {
+// getOIDC redirects to the oidc page defined in auth.Config
+func (auth AuthHandler) getOIDC(ctx iris.Context) {
 	state := uuid.New()
 	ctx.SetCookie(&http.Cookie{Name: "state", Value: state.String(), Secure: true})
 
@@ -231,9 +232,9 @@ func (auth AuthHandler) getElixir(ctx iris.Context) {
 	}
 }
 
-// elixirLogin authenticates the user with return values from the elixir
-// login page and returns the resulting data to the getElixirLogin page, or
-// getElixirCORSLogin endpoint.
+// elixirLogin authenticates the user with return values from the oidc
+// login page and returns the resulting data to the getOIDCLogin page, or
+// getOIDCCORSLogin endpoint.
 func (auth AuthHandler) elixirLogin(ctx iris.Context) *OIDCData {
 	state := ctx.Request().URL.Query().Get("state")
 	sessionState := ctx.GetCookie("state")
@@ -251,9 +252,9 @@ func (auth AuthHandler) elixirLogin(ctx iris.Context) *OIDCData {
 	}
 
 	code := ctx.Request().URL.Query().Get("code")
-	idStruct, err := authenticateWithOidc(auth.OAuth2Config, auth.OIDCProvider, code, auth.Config.Elixir.jwkURL)
+	idStruct, err := authenticateWithOidc(auth.OAuth2Config, auth.OIDCProvider, code, auth.Config.OIDC.JwkURL)
 	if err != nil {
-		log.WithFields(log.Fields{"authType": "elixir"}).Errorf("authentication failed: %s", err)
+		log.WithFields(log.Fields{"authType": "oidc"}).Errorf("authentication failed: %s", err)
 		_, err := ctx.Writef("Authentication failed. You may need to clear your session cookies and try again.")
 		if err != nil {
 			log.Error("Failed to write response: ", err)
@@ -279,14 +280,14 @@ func (auth AuthHandler) elixirLogin(ctx iris.Context) *OIDCData {
 		idStruct.ExpDate = expDate
 	}
 
-	log.WithFields(log.Fields{"authType": "elixir", "user": idStruct.User}).Infof("User was authenticated")
+	log.WithFields(log.Fields{"authType": "oidc", "user": idStruct.User}).Infof("User was authenticated")
 	s3conf := getS3ConfigMap(idStruct.Token, auth.Config.S3Inbox, idStruct.User)
 
-	return &OIDCData{S3Conf: s3conf, ElixirID: idStruct}
+	return &OIDCData{S3Conf: s3conf, OIDCID: idStruct}
 }
 
-// getElixirLogin renders the `elixir.html` template to the given iris context
-func (auth AuthHandler) getElixirLogin(ctx iris.Context) {
+// getOIDCLogin renders the `oidc.html` template to the given iris context
+func (auth AuthHandler) getOIDCLogin(ctx iris.Context) {
 
 	oidcData := auth.elixirLogin(ctx)
 	if oidcData == nil {
@@ -294,15 +295,15 @@ func (auth AuthHandler) getElixirLogin(ctx iris.Context) {
 	}
 
 	s := sessions.Get(ctx)
-	s.SetFlash("elixir", oidcData.S3Conf)
+	s.SetFlash("oidc", oidcData.S3Conf)
 	ctx.ViewData("infoUrl", auth.Config.InfoURL)
 	ctx.ViewData("infoText", auth.Config.InfoText)
-	ctx.ViewData("User", oidcData.ElixirID.User)
-	ctx.ViewData("Passport", oidcData.ElixirID.Passport)
-	ctx.ViewData("Token", oidcData.ElixirID.Token)
-	ctx.ViewData("ExpDate", oidcData.ElixirID.ExpDate)
+	ctx.ViewData("User", oidcData.OIDCID.User)
+	ctx.ViewData("Passport", oidcData.OIDCID.Passport)
+	ctx.ViewData("Token", oidcData.OIDCID.Token)
+	ctx.ViewData("ExpDate", oidcData.OIDCID.ExpDate)
 
-	err := ctx.View("elixir.html")
+	err := ctx.View("oidc.html")
 	if err != nil {
 		log.Error("Failed to view login form: ", err)
 
@@ -310,8 +311,8 @@ func (auth AuthHandler) getElixirLogin(ctx iris.Context) {
 	}
 }
 
-// getElixirCORSLogin returns the oidc data as JSON to the given iris context
-func (auth AuthHandler) getElixirCORSLogin(ctx iris.Context) {
+// getOIDCCORSLogin returns the oidc data as JSON to the given iris context
+func (auth AuthHandler) getOIDCCORSLogin(ctx iris.Context) {
 
 	oidcData := auth.elixirLogin(ctx)
 	if oidcData == nil {
@@ -326,9 +327,9 @@ func (auth AuthHandler) getElixirCORSLogin(ctx iris.Context) {
 	}
 }
 
-// getElixirConf returns an s3config file for an elixir login
-func (auth AuthHandler) getElixirConf(ctx iris.Context) {
-	auth.getInboxConfig(ctx, "elixir")
+// getOIDCConf returns an s3config file for an oidc login
+func (auth AuthHandler) getOIDCConf(ctx iris.Context) {
+	auth.getInboxConfig(ctx, "oidc")
 }
 
 // globalHeaders presets common response headers
@@ -355,7 +356,7 @@ func addCSPheaders(ctx iris.Context) {
 func main() {
 
 	// Initialise config
-	config, err := NewConfig()
+	config, err := config.NewConfig("auth")
 	if err != nil {
 		log.Errorf("Failed to generate config, reason: %v", err)
 		os.Exit(1)
@@ -364,14 +365,14 @@ func main() {
 	var oauth2Config oauth2.Config
 	var provider *oidc.Provider
 
-	if config.Elixir.ID != "" && config.Elixir.Secret != "" {
+	if config.Auth.OIDC.ID != "" && config.Auth.OIDC.Secret != "" {
 		// Initialise OIDC client
-		oauth2Config, provider = getOidcClient(config.Elixir)
+		oauth2Config, provider = getOidcClient(config.Auth.OIDC)
 	}
 
 	// Create handler struct for the web server
 	authHandler := AuthHandler{
-		Config:       config,
+		Config:       config.Auth,
 		OAuth2Config: oauth2Config,
 		OIDCProvider: provider,
 		htmlDir:      "./frontend/templates",
@@ -408,11 +409,11 @@ func main() {
 	app.Get("/ega/s3conf", authHandler.getEGAConf)
 	app.Get("/ega/login", addCSPheaders, authHandler.getEGALogin)
 
-	// Elixir endpoints
-	app.Get("/elixir", authHandler.getElixir)
-	app.Get("/elixir/s3conf", authHandler.getElixirConf)
-	app.Get("/elixir/login", authHandler.getElixirLogin)
-	app.Get("/elixir/cors_login", authHandler.getElixirCORSLogin)
+	// OIDC endpoints
+	app.Get("/oidc", authHandler.getOIDC)
+	app.Get("/oidc/s3conf", authHandler.getOIDCConf)
+	app.Get("/oidc/login", authHandler.getOIDCLogin)
+	app.Get("/oidc/cors_login", authHandler.getOIDCCORSLogin)
 
 	publicKey, err := readPublicKeyFile(authHandler.Config.PublicFile)
 	if err != nil {
diff --git a/sda-auth/elixir.go b/sda/cmd/auth/oidc.go
similarity index 88%
rename from sda-auth/elixir.go
rename to sda/cmd/auth/oidc.go
index 48a64c44a..19df6a136 100644
--- a/sda-auth/elixir.go
+++ b/sda/cmd/auth/oidc.go
@@ -9,12 +9,13 @@ import (
 	"github.com/lestrrat-go/jwx/v2/jwk"
 	"github.com/lestrrat-go/jwx/v2/jws"
 	"github.com/lestrrat-go/jwx/v2/jwt"
+	"github.com/neicnordic/sensitive-data-archive/internal/config"
 	log "github.com/sirupsen/logrus"
 	"golang.org/x/oauth2"
 )
 
-// ElixirIdentity represents an Elixir user instance
-type ElixirIdentity struct {
+// OIDCIdentity represents an OIDC user instance
+type OIDCIdentity struct {
 	User                 string
 	Passport             []string
 	Token                string
@@ -25,7 +26,7 @@ type ElixirIdentity struct {
 }
 
 // Configure an OpenID Connect aware OAuth2 client.
-func getOidcClient(conf ElixirConfig) (oauth2.Config, *oidc.Provider) {
+func getOidcClient(conf config.OIDCConfig) (oauth2.Config, *oidc.Provider) {
 	contx := context.Background()
 	provider, err := oidc.NewProvider(contx, conf.Provider)
 	if err != nil {
@@ -43,11 +44,11 @@ func getOidcClient(conf ElixirConfig) (oauth2.Config, *oidc.Provider) {
 	return oauth2Config, provider
 }
 
-// Authenticate with an Oidc client.against Elixir AAI
-func authenticateWithOidc(oauth2Config oauth2.Config, provider *oidc.Provider, code, jwkURL string) (ElixirIdentity, error) {
+// Authenticate with an Oidc client.against OIDC AAI
+func authenticateWithOidc(oauth2Config oauth2.Config, provider *oidc.Provider, code, jwkURL string) (OIDCIdentity, error) {
 	contx := context.Background()
 	defer contx.Done()
-	var idStruct ElixirIdentity
+	var idStruct OIDCIdentity
 
 	oauth2Token, err := oauth2Config.Exchange(contx, code)
 	if err != nil {
@@ -101,7 +102,7 @@ func authenticateWithOidc(oauth2Config oauth2.Config, provider *oidc.Provider, c
 		return idStruct, err
 	}
 
-	idStruct = ElixirIdentity{
+	idStruct = OIDCIdentity{
 		User:                 userInfo.Subject,
 		Token:                rawAccessToken,
 		Passport:             claims.PassportClaim,
@@ -114,7 +115,7 @@ func authenticateWithOidc(oauth2Config oauth2.Config, provider *oidc.Provider, c
 	return idStruct, err
 }
 
-// Validate raw (Elixir) jwt against public key from jwk. Return parsed jwt and its expiration date.
+// Validate raw (OIDC) jwt against public key from jwk. Return parsed jwt and its expiration date.
 func validateToken(rawJwt, jwksURL string) (*jwt.Token, string, error) {
 	set, err := jwk.Fetch(context.Background(), jwksURL)
 	if err != nil {
diff --git a/sda-auth/elixir_test.go b/sda/cmd/auth/oidc_test.go
similarity index 88%
rename from sda-auth/elixir_test.go
rename to sda/cmd/auth/oidc_test.go
index 84b993d8b..6a3d395cc 100644
--- a/sda-auth/elixir_test.go
+++ b/sda/cmd/auth/oidc_test.go
@@ -12,6 +12,7 @@ import (
 	"github.com/lestrrat-go/jwx/v2/jwa"
 	"github.com/lestrrat-go/jwx/v2/jwk"
 	"github.com/lestrrat-go/jwx/v2/jwt"
+	"github.com/neicnordic/sensitive-data-archive/internal/config"
 	"github.com/oauth2-proxy/mockoidc"
 	log "github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/assert"
@@ -19,26 +20,26 @@ import (
 	"golang.org/x/oauth2"
 )
 
-type ElixirTests struct {
+type OIDCTests struct {
 	suite.Suite
-	TempDir      string
-	ECKeyFile    *os.File
-	RSAKeyFile   *os.File
-	mockServer   *mockoidc.MockOIDC
-	ElixirConfig ElixirConfig
+	TempDir    string
+	ECKeyFile  *os.File
+	RSAKeyFile *os.File
+	mockServer *mockoidc.MockOIDC
+	OIDCConfig config.OIDCConfig
 }
 
-func TestElixirTestSuite(t *testing.T) {
-	suite.Run(t, new(ElixirTests))
+func TestOIDCTestSuite(t *testing.T) {
+	suite.Run(t, new(OIDCTests))
 }
 
-func (suite *ElixirTests) SetupTest() {
+func (suite *OIDCTests) SetupTest() {
 	var err error
 	suite.mockServer, err = mockoidc.Run()
 	assert.NoError(suite.T(), err)
 
 	// create an elixir config that has the needed endpoints set
-	suite.ElixirConfig = ElixirConfig{
+	suite.OIDCConfig = config.OIDCConfig{
 		ID:          suite.mockServer.ClientID,
 		Provider:    suite.mockServer.Issuer(),
 		RedirectURL: "http://redirect",
@@ -46,28 +47,28 @@ func (suite *ElixirTests) SetupTest() {
 	}
 }
 
-func (suite *ElixirTests) TearDownTest() {
+func (suite *OIDCTests) TearDownTest() {
 	err := suite.mockServer.Shutdown()
 	assert.NoError(suite.T(), err)
 }
 
-func (suite *ElixirTests) TestGetOidcClient() {
+func (suite *OIDCTests) TestGetOidcClient() {
 
 	expectedEndpoint := oauth2.Endpoint{
 		AuthURL:   suite.mockServer.AuthorizationEndpoint(),
 		TokenURL:  suite.mockServer.TokenEndpoint(),
 		AuthStyle: 0}
 
-	oauth2Config, provider := getOidcClient(suite.ElixirConfig)
+	oauth2Config, provider := getOidcClient(suite.OIDCConfig)
 	assert.Equal(suite.T(), suite.mockServer.ClientID, oauth2Config.ClientID, "ClientID was modified when creating the oauth2Config")
 	assert.Equal(suite.T(), suite.mockServer.ClientSecret, oauth2Config.ClientSecret, "ClientSecret was modified when creating the oauth2Config")
-	assert.Equal(suite.T(), suite.ElixirConfig.RedirectURL, oauth2Config.RedirectURL, "RedirectURL was modified when creating the oauth2Config")
+	assert.Equal(suite.T(), suite.OIDCConfig.RedirectURL, oauth2Config.RedirectURL, "RedirectURL was modified when creating the oauth2Config")
 	assert.Equal(suite.T(), expectedEndpoint, oauth2Config.Endpoint, "Issuer was modified when creating the oauth2Config")
 	assert.Equal(suite.T(), expectedEndpoint, provider.Endpoint(), "provider has the wrong endpoint")
 	assert.Equal(suite.T(), []string{"openid", "ga4gh_passport_v1 profile email eduperson_entitlement"}, oauth2Config.Scopes, "oauth2Config has the wrong scopes")
 }
 
-func (suite *ElixirTests) TestAuthenticateWithOidc() {
+func (suite *OIDCTests) TestAuthenticateWithOidc() {
 
 	// Create a code to authenticate
 
@@ -79,17 +80,17 @@ func (suite *ElixirTests) TestAuthenticateWithOidc() {
 	code := session.SessionID
 	jwkURL := suite.mockServer.JWKSEndpoint()
 
-	oauth2Config, provider := getOidcClient(suite.ElixirConfig)
+	oauth2Config, provider := getOidcClient(suite.OIDCConfig)
 
 	elixirIdentity, err := authenticateWithOidc(oauth2Config, provider, code, jwkURL)
 	assert.Nil(suite.T(), err, "Failed to authenticate with OIDC")
 	assert.NotEqual(suite.T(), "", elixirIdentity.Token, "Empty token returned from OIDC authentication")
 }
 
-func (suite *ElixirTests) TestValidateJwt() {
+func (suite *OIDCTests) TestValidateJwt() {
 	session, err := suite.mockServer.SessionStore.NewSession("openid email profile", "nonce", mockoidc.DefaultUser(), "", "")
 	assert.NoError(suite.T(), err)
-	oauth2Config, provider := getOidcClient(suite.ElixirConfig)
+	oauth2Config, provider := getOidcClient(suite.OIDCConfig)
 	jwkURL := suite.mockServer.JWKSEndpoint()
 	elixirIdentity, _ := authenticateWithOidc(oauth2Config, provider, session.SessionID, jwkURL)
 	elixirJWT := elixirIdentity.Token
diff --git a/sda-auth/s3conf.go b/sda/cmd/auth/s3conf.go
similarity index 100%
rename from sda-auth/s3conf.go
rename to sda/cmd/auth/s3conf.go
diff --git a/sda-auth/s3conf_test.go b/sda/cmd/auth/s3conf_test.go
similarity index 100%
rename from sda-auth/s3conf_test.go
rename to sda/cmd/auth/s3conf_test.go
diff --git a/sda/go.mod b/sda/go.mod
index 8559219d4..fa867ef33 100644
--- a/sda/go.mod
+++ b/sda/go.mod
@@ -10,15 +10,19 @@ require (
 	github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.7
 	github.com/aws/aws-sdk-go-v2/service/s3 v1.51.2
 	github.com/aws/smithy-go v1.20.1
+	github.com/coreos/go-oidc v2.2.1+incompatible
 	github.com/gin-gonic/gin v1.9.1
 	github.com/google/uuid v1.6.0
 	github.com/gorilla/mux v1.8.1
 	github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb
+	github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee
+	github.com/kataras/iris/v12 v12.2.10
 	github.com/lestrrat-go/jwx/v2 v2.0.20
 	github.com/lib/pq v1.10.9
 	github.com/minio/minio-go/v6 v6.0.57
 	github.com/mocktools/go-smtp-mock v1.10.0
 	github.com/neicnordic/crypt4gh v1.9.1
+	github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
 	github.com/ory/dockertest v3.3.5+incompatible
 	github.com/ory/dockertest/v3 v3.10.0
 	github.com/pkg/errors v0.9.1
@@ -29,6 +33,7 @@ require (
 	github.com/spf13/viper v1.18.2
 	github.com/stretchr/testify v1.9.0
 	golang.org/x/crypto v0.21.0
+	golang.org/x/oauth2 v0.16.0
 	google.golang.org/grpc v1.62.1
 	google.golang.org/protobuf v1.32.0
 )
@@ -36,8 +41,14 @@ require (
 require (
 	filippo.io/edwards25519 v1.1.0 // indirect
 	github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
+	github.com/BurntSushi/toml v1.3.2 // indirect
+	github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect
+	github.com/CloudyKit/jet/v6 v6.2.0 // indirect
+	github.com/Joker/jade v1.1.3 // indirect
 	github.com/Microsoft/go-winio v0.6.1 // indirect
 	github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
+	github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 // indirect
+	github.com/andybalholm/brotli v1.1.0 // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 // indirect
@@ -51,6 +62,7 @@ require (
 	github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 // indirect
 	github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 // indirect
 	github.com/aws/aws-sdk-go-v2/service/sts v1.28.2 // indirect
+	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bytedance/sonic v1.10.2 // indirect
 	github.com/cenkalti/backoff v2.2.1+incompatible // indirect
@@ -66,21 +78,36 @@ require (
 	github.com/docker/docker v24.0.7+incompatible // indirect
 	github.com/docker/go-connections v0.4.0 // indirect
 	github.com/docker/go-units v0.5.0 // indirect
+	github.com/fatih/structs v1.1.0 // indirect
+	github.com/flosch/pongo2/v4 v4.0.2 // indirect
 	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/go-jose/go-jose/v3 v3.0.1 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.17.0 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
+	github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
+	github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
+	github.com/gorilla/css v1.0.0 // indirect
 	github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/imdario/mergo v0.3.12 // indirect
+	github.com/iris-contrib/schema v0.0.6 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/kataras/blocks v0.0.8 // indirect
+	github.com/kataras/golog v0.1.11 // indirect
+	github.com/kataras/pio v0.0.13 // indirect
+	github.com/kataras/sitemap v0.0.6 // indirect
+	github.com/kataras/tunnel v0.0.4 // indirect
+	github.com/klauspost/compress v1.17.4 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
 	github.com/kr/fs v0.1.0 // indirect
 	github.com/leodido/go-urn v1.4.0 // indirect
@@ -90,7 +117,10 @@ require (
 	github.com/lestrrat-go/iter v1.0.2 // indirect
 	github.com/lestrrat-go/option v1.0.1 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mailgun/raymond/v2 v2.0.48 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/microcosm-cc/bluemonday v1.0.26 // indirect
 	github.com/minio/sha256-simd v1.0.1 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
@@ -101,23 +131,32 @@ require (
 	github.com/opencontainers/runc v1.1.12 // indirect
 	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
+	github.com/pquerna/cachecontrol v0.2.0 // indirect
 	github.com/prometheus/client_golang v1.18.0 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
 	github.com/prometheus/common v0.46.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	github.com/sagikazarmark/locafero v0.4.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
+	github.com/schollz/closestmatch v2.1.0+incompatible // indirect
 	github.com/segmentio/asm v1.2.0 // indirect
 	github.com/sourcegraph/conc v0.3.0 // indirect
 	github.com/spf13/afero v1.11.0 // indirect
 	github.com/spf13/cast v1.6.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
+	github.com/tdewolff/minify/v2 v2.20.14 // indirect
+	github.com/tdewolff/parse/v2 v2.7.8 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
+	github.com/valyala/bytebufferpool v1.0.0 // indirect
+	github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
+	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
+	github.com/yosssi/ace v0.0.5 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	golang.org/x/arch v0.7.0 // indirect
 	golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 // indirect
@@ -125,10 +164,13 @@ require (
 	golang.org/x/net v0.21.0 // indirect
 	golang.org/x/sys v0.18.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
+	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.17.0 // indirect
+	google.golang.org/appengine v1.6.8 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect
 	gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/square/go-jose.v2 v2.6.0 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	gotest.tools v2.2.0+incompatible // indirect
diff --git a/sda/go.sum b/sda/go.sum
index 8fd4b9e34..caf6d29f7 100644
--- a/sda/go.sum
+++ b/sda/go.sum
@@ -3,12 +3,28 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4
 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
 github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c=
+github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
+github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME=
+github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4=
 github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
 github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
+github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc=
+github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
+github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk=
+github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM=
 github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
 github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
 github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
+github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0=
+github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM=
+github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
+github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
+github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
 github.com/aws/aws-sdk-go-v2 v1.25.2 h1:/uiG1avJRgLGiQM9X3qJM8+Qa6KRGK5rRPuXE0HUM+w=
 github.com/aws/aws-sdk-go-v2 v1.25.2/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo=
 github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.1 h1:gTK2uhtAPtFcdRRJilZPx8uJLL2J85xK11nKtWL0wfU=
@@ -47,6 +63,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.28.2 h1:0YjXuWdYHvsm0HnT4vO8XpwG1D+i
 github.com/aws/aws-sdk-go-v2/service/sts v1.28.2/go.mod h1:jI+FWmYkSMn+4APWmZiZTgt0oM0TrvymD51FMqCnWgA=
 github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw=
 github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
 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/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
@@ -68,6 +86,8 @@ github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0
 github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
 github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
 github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
+github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk=
+github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
 github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
 github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -87,6 +107,12 @@ github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5Xh
 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
+github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
+github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
+github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
+github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw=
+github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8=
 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
@@ -97,6 +123,8 @@ 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-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
 github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
+github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -107,26 +135,42 @@ github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ
 github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
+github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
+github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 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/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47 h1:k4Tw0nt6lwro3Uin8eqoET7MDA4JnT8YgbCjc/g5E3k=
+github.com/gomarkdown/markdown v0.0.0-20231222211730-1d6d20845b47/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
 github.com/google/go-cmp v0.3.0/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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 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/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI=
 github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
 github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -135,17 +179,41 @@ github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb h1:tsEKRC3P
 github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb/go.mod h1:NtmN9h8vrTveVQRLHcX2HQ5wIPBDCsZ351TGbZWgg38=
 github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
 github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
+github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
+github.com/iris-contrib/httpexpect/v2 v2.15.2 h1:T9THsdP1woyAqKHwjkEsbCnMefsAFvk8iJJKokcJ3Go=
+github.com/iris-contrib/httpexpect/v2 v2.15.2/go.mod h1:JLDgIqnFy5loDSUv1OA2j0mb6p/rDhiCqigP22Uq9xE=
+github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee h1:srr0HUvI9IAximtqxhF0+WSy9cv9Dd5YlH8RMEkHtsw=
+github.com/iris-contrib/middleware/cors v0.0.0-20240111010557-e34016a4d6ee/go.mod h1:hAsE+pfCAz1M2cCfsEolTmaBwHCyzchRFyCk5I1+kDo=
+github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw=
+github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/kataras/blocks v0.0.8 h1:MrpVhoFTCR2v1iOOfGng5VJSILKeZZI+7NGfxEh3SUM=
+github.com/kataras/blocks v0.0.8/go.mod h1:9Jm5zx6BB+06NwA+OhTbHW1xkMOYxahnqTN5DveZ2Yg=
+github.com/kataras/golog v0.1.11 h1:dGkcCVsIpqiAMWTlebn/ZULHxFvfG4K43LF1cNWSh20=
+github.com/kataras/golog v0.1.11/go.mod h1:mAkt1vbPowFUuUGvexyQ5NFW6djEgGyxQBIARJ0AH4A=
+github.com/kataras/iris/v12 v12.2.10 h1:rEJVM7qMoyhv8wpgkA1yGxibFcONE0jkJ70LFLibTAA=
+github.com/kataras/iris/v12 v12.2.10/go.mod h1:z4+E+kLMqZ7U4WtDsYfFnG7BjMTXLkdzMAXLVMLnMNs=
+github.com/kataras/pio v0.0.13 h1:x0rXVX0fviDTXOOLOmr4MUxOabu1InVSTu5itF8CXCM=
+github.com/kataras/pio v0.0.13/go.mod h1:k3HNuSw+eJ8Pm2lA4lRhg3DiCjVgHlP8hmXApSej3oM=
+github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY=
+github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4=
+github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA=
+github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw=
 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/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
+github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
+github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
 github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
@@ -179,8 +247,16 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
 github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw=
+github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
+github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
 github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
 github.com/minio/minio-go/v6 v6.0.57 h1:ixPkbKkyD7IhnluRgQpGSpHdpvNVaW6OD5R9IAO/9Tw=
 github.com/minio/minio-go/v6 v6.0.57/go.mod h1:5+R/nM9Pwrh0vqF+HbYYDQ84wdUFPyXHkrdT4AIkifM=
@@ -188,6 +264,8 @@ github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl
 github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
 github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
+github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk=
@@ -202,6 +280,9 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/neicnordic/crypt4gh v1.9.1 h1:NKeOmsZ1/0xDpLvBDjN8Ltoh3ODbLB+NeKulDaNo4Oc=
 github.com/neicnordic/crypt4gh v1.9.1/go.mod h1:C8jHyUkt3bnQg0EwdRRswzvLXfu4dyLQOPARDIQTU24=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 h1:9bCMuD3TcnjeqjPT2gSlha4asp8NvgcFRYExCaikCxk=
+github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25/go.mod h1:eDjgYHYDJbPLBLsyZ6qRaugP0mX8vePOhZ5id1fdzJw=
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
@@ -222,6 +303,8 @@ github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Q
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pquerna/cachecontrol v0.2.0 h1:vBXSNuE5MYP9IJ5kjsdo8uq+w41jSPgvba2DEnkRx9k=
+github.com/pquerna/cachecontrol v0.2.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI=
 github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
 github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
 github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
@@ -234,15 +317,24 @@ github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc
 github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
 github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
 github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
 github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
+github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo=
+github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U=
 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
+github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk=
+github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
 github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
 github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
+github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
+github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
@@ -275,18 +367,40 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
+github.com/tdewolff/minify/v2 v2.20.14 h1:sktSuVixRwk0ryQjqvKBu/uYS+MWmkwEFMEWtFZ+TdE=
+github.com/tdewolff/minify/v2 v2.20.14/go.mod h1:qnIJbnG2dSzk7LIa/UUwgN2OjS8ir6RRlqc0T/1q2xY=
+github.com/tdewolff/parse/v2 v2.7.8 h1:1cnVqa8L63xFkc2vfRsZTM6Qy35nJpTvQ2Uvdv3vbvs=
+github.com/tdewolff/parse/v2 v2.7.8/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
+github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
+github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
+github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
 github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+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/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
+github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
+github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
+github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
+github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
+github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
+github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
+github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
+github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
+github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
 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.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
 go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
@@ -297,6 +411,7 @@ golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
 golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -307,34 +422,43 @@ golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3 h1:/RIbNt/Zr7rVhIkQhooTxCxFc
 golang.org/x/exp v0.0.0-20240205201215-2c58cdc269a3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
 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.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190327091125-710a502c58a2/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-20190620200207-3b0461eec859/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-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-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
+golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
 golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -350,16 +474,21 @@ golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
 golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 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.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
 golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
@@ -367,6 +496,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
 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/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
 google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
@@ -379,11 +510,14 @@ gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdr
 gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
 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-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 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/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
+gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -396,5 +530,7 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
 gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
 gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo=
 gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A=
+moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs=
+moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE=
 nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/sda/internal/config/config.go b/sda/internal/config/config.go
index 74cd796bc..80b2e0434 100644
--- a/sda/internal/config/config.go
+++ b/sda/internal/config/config.go
@@ -32,6 +32,7 @@ type ServerConfig struct {
 	Key           string
 	Jwtpubkeypath string
 	Jwtpubkeyurl  string
+	CORS          CORSConfig
 }
 
 // Config is a parent object for all the different configuration parts
@@ -48,6 +49,7 @@ type Config struct {
 	Sync         Sync
 	SyncAPI      SyncAPIConf
 	ReEncrypt    ReEncConfig
+	Auth         AuthConf
 }
 
 type ReEncConfig struct {
@@ -110,6 +112,42 @@ type OrchestratorConf struct {
 	ReleaseDelay   time.Duration
 }
 
+type AuthConf struct {
+	OIDC            OIDCConfig
+	Cega            CegaConfig
+	JwtIssuer       string
+	JwtPrivateKey   string
+	JwtSignatureAlg string
+	Server          ServerConfig
+	S3Inbox         string
+	ResignJwt       bool
+	InfoURL         string
+	InfoText        string
+	PublicFile      string
+}
+
+type OIDCConfig struct {
+	ID            string
+	Provider      string
+	RedirectURL   string
+	RevocationURL string
+	Secret        string
+	JwkURL        string
+}
+
+type CegaConfig struct {
+	AuthURL string
+	ID      string
+	Secret  string
+}
+
+type CORSConfig struct {
+	AllowOrigin      string
+	AllowMethods     string
+	AllowHeaders     string
+	AllowCredentials bool
+}
+
 // NewConfig initializes and parses the config file and/or environment using
 // the viper library.
 func NewConfig(app string) (*Config, error) {
@@ -143,6 +181,24 @@ func NewConfig(app string) (*Config, error) {
 		}
 	}
 
+	if viper.IsSet("log.format") {
+		if viper.GetString("log.format") == "json" {
+			log.SetFormatter(&log.JSONFormatter{})
+			log.Info("The logs format is set to JSON")
+		}
+	}
+
+	if viper.IsSet("log.level") {
+		stringLevel := viper.GetString("log.level")
+		intLevel, err := log.ParseLevel(stringLevel)
+		if err != nil {
+			log.Infof("Log level '%s' not supported, setting to 'trace'", stringLevel)
+			intLevel = log.TraceLevel
+		}
+		log.SetLevel(intLevel)
+		log.Infof("Setting log level to '%s'", stringLevel)
+	}
+
 	switch app {
 	case "api":
 		requiredConfVars = []string{
@@ -157,6 +213,24 @@ func NewConfig(app string) (*Config, error) {
 			"db.password",
 			"db.database",
 		}
+	case "auth":
+		requiredConfVars = []string{
+			"auth.s3Inbox",
+			"auth.publicFile",
+		}
+
+		if viper.GetString("auth.cega.id") != "" && viper.GetString("auth.cega.secret") != "" {
+			requiredConfVars = append(requiredConfVars, []string{"auth.cega.authUrl"}...)
+			viper.Set("auth.resignJwt", true)
+		}
+
+		if viper.GetString("oidc.id") != "" && viper.GetString("oidc.secret") != "" {
+			requiredConfVars = append(requiredConfVars, []string{"oidc.provider", "oidc.redirectUrl"}...)
+		}
+
+		if viper.GetBool("auth.resignJwt") {
+			requiredConfVars = append(requiredConfVars, []string{"auth.jwt.issuer", "auth.jwt.privateKey", "auth.jwt.signatureAlg"}...)
+		}
 	case "ingest":
 		requiredConfVars = []string{
 			"broker.host",
@@ -370,24 +444,6 @@ func NewConfig(app string) (*Config, error) {
 		}
 	}
 
-	if viper.IsSet("log.format") {
-		if viper.GetString("log.format") == "json" {
-			log.SetFormatter(&log.JSONFormatter{})
-			log.Info("The logs format is set to JSON")
-		}
-	}
-
-	if viper.IsSet("log.level") {
-		stringLevel := viper.GetString("log.level")
-		intLevel, err := log.ParseLevel(stringLevel)
-		if err != nil {
-			log.Infof("Log level '%s' not supported, setting to 'trace'", stringLevel)
-			intLevel = log.TraceLevel
-		}
-		log.SetLevel(intLevel)
-		log.Infof("Setting log level to '%s'", stringLevel)
-	}
-
 	c := &Config{}
 	switch app {
 	case "api":
@@ -410,6 +466,64 @@ func NewConfig(app string) (*Config, error) {
 		if err != nil {
 			return nil, err
 		}
+	case "auth":
+		c.Auth.Cega.AuthURL = viper.GetString("auth.cega.authUrl")
+		c.Auth.Cega.ID = viper.GetString("auth.cega.id")
+		c.Auth.Cega.Secret = viper.GetString("auth.cega.secret")
+
+		c.Auth.OIDC.ID = viper.GetString("oidc.id")
+		c.Auth.OIDC.Provider = viper.GetString("oidc.provider")
+		c.Auth.OIDC.RedirectURL = viper.GetString("oidc.redirectUrl")
+		c.Auth.OIDC.Secret = viper.GetString("oidc.secret")
+		if viper.IsSet("oidc.jwkPath") {
+			c.Auth.OIDC.JwkURL = c.Auth.OIDC.Provider + viper.GetString("oidc.jwkPath")
+		}
+
+		if (c.Auth.OIDC.ID == "" || c.Auth.OIDC.Secret == "") && (c.Auth.Cega.ID == "" || c.Auth.Cega.Secret == "") {
+			return nil, fmt.Errorf("neither cega or oidc login configured")
+		}
+
+		c.Auth.InfoURL = viper.GetString("auth.infoUrl")
+		c.Auth.InfoText = viper.GetString("auth.infoText")
+		c.Auth.PublicFile = viper.GetString("auth.publicFile")
+		if _, err := os.Stat(c.Auth.PublicFile); err != nil {
+			return nil, err
+		}
+
+		if viper.GetBool("auth.resignJwt") {
+			c.Auth.ResignJwt = viper.GetBool("auth.resignJwt")
+			c.Auth.JwtPrivateKey = viper.GetString("auth.jwt.privateKey")
+			c.Auth.JwtSignatureAlg = viper.GetString("auth.jwt.signatureAlg")
+			c.Auth.JwtIssuer = viper.GetString("auth.jwt.issuer")
+
+			if _, err := os.Stat(c.Auth.JwtPrivateKey); err != nil {
+				return nil, err
+			}
+		}
+
+		cors := CORSConfig{AllowCredentials: false}
+		if viper.IsSet("cors.origins") {
+			cors.AllowOrigin = viper.GetString("cors.origins")
+		}
+		if viper.IsSet("cors.methods") {
+			cors.AllowMethods = viper.GetString("cors.methods")
+		}
+		if viper.IsSet("cors.headers") {
+			cors.AllowHeaders = viper.GetString("cors.headers")
+		}
+		if viper.IsSet("cors.credentials") {
+			cors.AllowCredentials = viper.GetBool("cors.credentials")
+		}
+		c.Server.CORS = cors
+
+		if viper.IsSet("server.cert") {
+			c.Server.Cert = viper.GetString("server.cert")
+		}
+		if viper.IsSet("server.key") {
+			c.Server.Key = viper.GetString("server.key")
+		}
+
+		c.Auth.S3Inbox = viper.GetString("auth.s3Inbox")
 	case "finalize":
 		if viper.GetString("archive.type") != "" && viper.GetString("backup.type") != "" {
 			c.configArchive()
diff --git a/sda/internal/config/config_test.go b/sda/internal/config/config_test.go
index 2cb6d30bd..253f68db5 100644
--- a/sda/internal/config/config_test.go
+++ b/sda/internal/config/config_test.go
@@ -392,3 +392,60 @@ func (suite *ConfigTestSuite) TestConfigReEncryptServer() {
 	assert.Equal(suite.T(), certPath+"/ca.crt", config.ReEncrypt.CACert)
 	assert.Equal(suite.T(), certPath+"/tls.crt", config.ReEncrypt.ServerCert)
 }
+
+func (suite *ConfigTestSuite) TestConfigAuth_CEGA() {
+	suite.SetupTest()
+
+	ECPath, _ := os.MkdirTemp("", "EC")
+	if err := helper.CreateECkeys(ECPath, ECPath); err != nil {
+		suite.T().FailNow()
+	}
+	defer os.RemoveAll(ECPath)
+
+	noConfig, err := NewConfig("auth")
+	assert.Error(suite.T(), err)
+	assert.Nil(suite.T(), noConfig)
+
+	viper.Set("auth.s3Inbox", "http://inbox:8000")
+	viper.Set("auth.publicFile", "no-file")
+	viper.Set("auth.cega.authURL", "http://cega/auth")
+	viper.Set("auth.cega.id", "CegaID")
+	viper.Set("auth.cega.secret", "CegaSecret")
+	viper.Set("auth.jwt.Issuer", "http://auth:8080")
+	viper.Set("auth.Jwt.privateKey", "nonexistent-key-file")
+	viper.Set("auth.Jwt.signatureAlg", "ES256")
+	_, err = NewConfig("auth")
+	assert.ErrorContains(suite.T(), err, "no such file or directory")
+
+	viper.Set("auth.publicFile", ECPath+"/ec.pub")
+	viper.Set("auth.Jwt.privateKey", ECPath+"/ec")
+	c, err := NewConfig("auth")
+	assert.Equal(suite.T(), c.Auth.JwtPrivateKey, fmt.Sprintf("%s/ec", ECPath))
+	assert.NoError(suite.T(), err, "unexpected failure")
+}
+
+func (suite *ConfigTestSuite) TestConfigAuth_OIDC() {
+	suite.SetupTest()
+
+	ECPath, _ := os.MkdirTemp("", "EC")
+	if err := helper.CreateECkeys(ECPath, ECPath); err != nil {
+		suite.T().FailNow()
+	}
+	defer os.RemoveAll(ECPath)
+
+	noConfig, err := NewConfig("auth")
+	assert.Error(suite.T(), err)
+	assert.Nil(suite.T(), noConfig)
+
+	viper.Set("auth.s3Inbox", "http://inbox:8000")
+	viper.Set("auth.publicFile", ECPath+"/ec.pub")
+	viper.Set("oidc.id", "oidcTestID")
+	viper.Set("oidc.secret", "oidcTestIssuer")
+	_, err = NewConfig("auth")
+	assert.Error(suite.T(), err)
+
+	viper.Set("oidc.provider", "http://provider:9000")
+	viper.Set("oidc.redirectUrl", "http://auth/oidc/login")
+	_, err = NewConfig("auth")
+	assert.NoError(suite.T(), err, "unexpected failure")
+}
diff --git a/sda/sda.md b/sda/sda.md
index 909a56bb7..a9d1dec53 100644
--- a/sda/sda.md
+++ b/sda/sda.md
@@ -17,8 +17,9 @@ The SDA submission pipeline has four main steps:
 
 There are also additional support services:
 
-1. [Intercept](cmd/intercept/intercept.md) relays messages from `CentralEGA` to the system.
-2. [ReEncrypt](cmd/reencrypt/reencrypt.md) reencrypts a given file header with a given public key.
-3. [s3inbox](cmd/s3inbox/s3inbox.md) proxies uploads to the an S3 compatible storage backend.
-4. [sync](cmd/sync/sync.md) mirrors ingested data between sites in the [Bigpicture](https://bigpicture.eu/) project.
-5. [syncapi](cmd/syncapi/syncapi.md) is used in the [Bigpicture](https://bigpicture.eu/) project for mirroring data between two installations of SDA.
+1. [Auth](cmd/auth/auth.md) authentication service used in conjunction with the [s3inbox](cmd/s3inbox/s3inbox.md).
+2. [Intercept](cmd/intercept/intercept.md) relays messages from `CentralEGA` to the system.
+3. [ReEncrypt](cmd/reencrypt/reencrypt.md) reencrypts a given file header with a given public key.
+4. [s3inbox](cmd/s3inbox/s3inbox.md) proxies uploads to the an S3 compatible storage backend.
+5. [sync](cmd/sync/sync.md) mirrors ingested data between sites in the [Bigpicture](https://bigpicture.eu/) project.
+6. [syncapi](cmd/syncapi/syncapi.md) is used in the [Bigpicture](https://bigpicture.eu/) project for mirroring data between two installations of SDA.