Compare commits

...

45 commits
v0.9.1 ... main

Author SHA1 Message Date
eikendev
b2e0275917
Read binary version using BuildInfo 2025-02-22 23:57:57 +01:00
eikendev
c13ebca593
Update to Go 1.24 2025-02-22 22:58:54 +01:00
eikendev
83a9034be7
Sort imports with goimports 2025-02-22 22:58:13 +01:00
eikendev
b0699da1e9
Add nilaway and rework test setup 2025-02-16 00:15:50 +01:00
eikendev
e657884326
Add checks with gocyclo 2025-02-15 14:31:49 +01:00
eikendev
737701d116
Fix version label in docs metadata 2025-02-15 14:28:58 +01:00
eikendev
c8705b146e
Update Go dependencies 2025-02-15 14:23:35 +01:00
eikendev
6903bdb62e
Update Go to v1.23.6 2025-02-15 14:09:24 +01:00
Cubicroot
c871854a13
Fix deprecated GoReleaser option (#74) 2024-10-21 21:15:48 +02:00
Cubicroot
15ea2935be
Update to Go 1.23 (#73) 2024-10-20 23:34:05 +02:00
eikendev
35f957332b
Update Go version 2023-12-22 00:24:41 +01:00
eikendev
9852977baa
Update dependencies 2023-12-21 23:24:51 +01:00
eikendev
175228bbe4
Update dependencies 2023-10-21 23:36:52 +02:00
eikendev
e836956019
Bump version 2023-09-24 22:39:05 +02:00
eikendev
e426b46f28
Use env variable to specify Go version in CI 2023-09-24 22:36:06 +02:00
eikendev
78a99281a7
Update Go and dependencies (#69) 2023-09-24 22:28:03 +02:00
eikendev
61d5e04ecf
Add support for configuration of TLS 2023-07-15 23:25:34 +02:00
eikendev
833e666c37
Add Make target to spawn PostgreSQL debug instance 2023-07-15 19:25:21 +02:00
eikendev
77603a5c96
Update Go dependencies 2023-07-15 19:24:48 +02:00
eikendev
9f50df63fa
Update to Go version 1.20.6 2023-07-15 19:24:11 +02:00
eikendev
f69833aeaa
Merge pull request #67 from rafaelespinoza/feat-postgres
Add support for Postgres
2023-07-15 19:17:15 +02:00
Rafael Espinoza
e2fbfbb28a feat: Add support for Postgres
There aren't many connection string examples for the postgres GORM
driver, so a couple extra links are referenced.
Most changes to go.mod and go.sum are automated changes as a result of
running go mod tidy.
2023-06-20 18:26:33 -07:00
eikendev
551a706358
Merge pull request #66 from rafaelespinoza/actions-bump_go_version
ci: Bump go version to address vulnerabilities
2023-06-20 15:51:36 +02:00
Rafael Espinoza
b058c03c30
ci: Bump go version to address vulnerabilities
go version 1.20.2 has some vulnerabilities, details at:

- https://pkg.go.dev/vuln/GO-2023-1840
- https://pkg.go.dev/vuln/GO-2023-1705
- https://pkg.go.dev/vuln/GO-2023-1704
- https://pkg.go.dev/vuln/GO-2023-1753
- https://pkg.go.dev/vuln/GO-2023-1752
- https://pkg.go.dev/vuln/GO-2023-1751
- https://pkg.go.dev/vuln/GO-2023-1737
- https://pkg.go.dev/vuln/GO-2023-1703
- https://pkg.go.dev/vuln/GO-2023-1702

They've been addressed in the new go version.
2023-06-19 15:26:22 -07:00
eikendev
291b32856c
Merge pull request #65 from pushbits/newlinters
Add new code linters
2023-04-01 20:06:34 +02:00
eikendev
2c20e42a21
Add misspell, gocritic, and revive 2023-04-01 20:00:58 +02:00
eikendev
5e640800fe
Fix spelling using misspell 2023-04-01 17:24:48 +02:00
eikendev
50a92b6d22
Merge pull request #64 from pushbits/removesemgrep
Replace semgrep with gosec and govulncheck
2023-03-25 23:34:28 +01:00
eikendev
bc65c4661e
Remove Poetry 2023-03-25 23:24:45 +01:00
eikendev
f251b12fc8
Remove semgrep, use errcheck, gosec, govulncheck 2023-03-25 23:20:08 +01:00
eikendev
e078a30fe2
Update semgrep 2023-02-26 12:11:20 +01:00
eikendev
c9fbdfda80
Update Go dependencies 2023-02-26 12:11:00 +01:00
eikendev
64563989b5
Address CWE-703 in hibp.go 2023-02-11 23:58:47 +01:00
eikendev
ead4f364f7
Revert "Add setuptools as explicit dependency"
This reverts commit 3bc0f26b81.
2023-02-11 23:47:16 +01:00
eikendev
3bc0f26b81
Add setuptools as explicit dependency
This is a workaround to address an issue with poetry.
2023-02-11 23:42:46 +01:00
eikendev
2ddc0fbd74
Update to Go 1.19 2023-02-11 23:41:42 +01:00
eikendev
21a4d156e1
Update README 2023-02-11 19:34:19 +01:00
eikendev
f8bed7470c
Add Make target to build Docker image locally 2022-12-17 12:13:20 +01:00
eikendev
f739672e01
Include CLI tool in Docker image 2022-12-11 18:01:51 +01:00
Raphael Eikenberg
e355152330
Merge pull request #61 from pushbits/dependabot/pip/certifi-2022.12.7
Bump certifi from 2021.10.8 to 2022.12.7
2022-12-10 23:38:53 +01:00
dependabot[bot]
fca461aaf7
Bump certifi from 2021.10.8 to 2022.12.7
Bumps [certifi](https://github.com/certifi/python-certifi) from 2021.10.8 to 2022.12.7.
- [Release notes](https://github.com/certifi/python-certifi/releases)
- [Commits](https://github.com/certifi/python-certifi/compare/2021.10.08...2022.12.07)

---
updated-dependencies:
- dependency-name: certifi
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-08 13:40:37 +00:00
Raphael Eikenberg
7087cc6df9
Merge pull request #59 from pushbits/repairbehavior
Add option to control repair behavior
2022-12-05 21:09:23 +01:00
eikendev
77765e77a9
Add option to control repair behavior 2022-12-04 23:41:22 +01:00
eikendev
38b615a05d
Use "build" command as "bundle" is deprecated 2022-11-04 22:57:36 +01:00
eikendev
802eee5262
Build before generating docs 2022-11-04 22:47:42 +01:00
60 changed files with 971 additions and 1015 deletions

1
.envrc
View file

@ -1 +0,0 @@
layout_poetry

View file

@ -6,6 +6,7 @@ on:
- 'v[0-9]+.[0-9]+.[0-9]+'
env:
GO_VERSION: '1.24.0'
PB_BUILD_VERSION: unknown # Needed for using Make targets.
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
@ -13,17 +14,15 @@ jobs:
build_documentation:
name: Build documentation
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout the repository
uses: actions/checkout@v3
- name: Export GOBIN
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.18
- name: Install Poetry
run: curl -sSL https://install.python-poetry.org | python3 -
go-version: '${{env.GO_VERSION}}'
- name: Install dependencies
run: make setup
@ -35,7 +34,7 @@ jobs:
run: make swag
- name: Build static HTML
run: npx redoc-cli bundle docs/swagger.yaml --output index.html
run: npx redoc-cli build docs/swagger.yaml --output index.html
- name: Setup SSH keys and known_hosts
run: |

View file

@ -5,10 +5,14 @@ on:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
env:
GO_VERSION: '1.24.0'
jobs:
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v3
@ -16,25 +20,21 @@ jobs:
fetch-depth: 0 # Needed to describe git ref during build.
- name: Export GOBIN
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.18
- name: Install Poetry
run: curl -sSL https://install.python-poetry.org | python3 -
go-version: '${{env.GO_VERSION}}'
- name: Install dependencies
run: make setup
- name: Run tests
run: |
source $(poetry env info --path)/bin/activate
make test
run: make test
publish_docker_image:
name: Publish Docker image
needs: test
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
packages: write
env:
@ -79,6 +79,7 @@ jobs:
name: Publish GitHub Release
needs: test
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
steps:
@ -86,15 +87,15 @@ jobs:
uses: actions/checkout@v3
- name: Export GOBIN
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.18
go-version: '${{env.GO_VERSION}}'
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v4
with:
distribution: goreleaser
version: latest
args: release --rm-dist
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View file

@ -3,38 +3,34 @@ name: Test
on:
push:
branches:
- '**'
tags:
- '**'
- '!v[0-9]+.[0-9]+.[0-9]+'
- 'main'
pull_request:
branches:
- 'main'
env:
GO_VERSION: '1.24.0'
PB_BUILD_VERSION: pipeline-${{ github.sha }}
jobs:
test_build:
name: Test and build
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Export GOBIN
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.18
- name: Install Poetry
run: curl -sSL https://install.python-poetry.org | python3 -
go-version: '${{env.GO_VERSION}}'
- name: Install dependencies
run: make setup
- name: Run tests
run: |
source $(poetry env info --path)/bin/activate
make test
run: make test
- name: Build
run: make build

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "tests/semgrep-rules"]
path = tests/semgrep-rules
url = https://github.com/returntocorp/semgrep-rules

View file

@ -1,17 +1,22 @@
FROM docker.io/library/golang:alpine as builder
ARG PB_BUILD_VERSION
ARG CLI_VERSION=0.0.6
ARG CLI_PLATFORM=linux_amd64
WORKDIR /build
COPY . .
RUN set -ex \
&& apk add --no-cache build-base \
&& apk add --no-cache build-base ca-certificates curl \
&& go mod download \
&& go mod verify \
&& PB_BUILD_VERSION="$PB_BUILD_VERSION" make build \
&& chmod +x /build/out/pushbits
&& make build \
&& chmod +x /build/out/pushbits \
&& curl -q -s -S -L -o /tmp/pbcli_${CLI_VERSION}.tar.gz https://github.com/pushbits/cli/releases/download/v${CLI_VERSION}/pbcli_${CLI_VERSION}_${CLI_PLATFORM}.tar.gz \
&& tar -C /usr/local/bin -xvf /tmp/pbcli_${CLI_VERSION}.tar.gz pbcli \
&& chown root:root /usr/local/bin/pbcli \
&& chmod +x /usr/local/bin/pbcli
FROM docker.io/library/alpine
@ -25,6 +30,7 @@ WORKDIR /app
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /build/out/pushbits ./run
COPY --from=builder /usr/local/bin/pbcli /usr/local/bin/pbcli
RUN set -ex \
&& apk add --no-cache ca-certificates curl \

View file

@ -1,57 +1,69 @@
# References:
# [1] Needed so the Go files of semgrep-rules do not interfere with static analysis
DOCS_DIR := ./docs
OUT_DIR := ./out
TESTS_DIR := ./tests
GO_FILES := $(shell find . -type f \( -iname '*.go' ! -path "./tests/semgrep-rules/*" \))
PB_BUILD_VERSION ?= $(shell git describe --tags)
ifeq ($(PB_BUILD_VERSION),)
_ := $(error Cannot determine build version)
endif
SEMGREP_MODFILE := $(TESTS_DIR)/semgrep-rules/go.mod
GO_FILES := $(shell find . -type f \( -iname '*.go' \))
GO_MODULE := github.com/pushbits/server
.PHONY: build
build:
mkdir -p $(OUT_DIR)
go build -ldflags="-w -s -X main.version=$(PB_BUILD_VERSION)" -o $(OUT_DIR)/pushbits ./cmd/pushbits
go build -ldflags="-w -s" -o $(OUT_DIR)/pushbits ./cmd/pushbits
.PHONY: clean
clean:
rm -rf $(DOCS_DIR)
rm -rf $(OUT_DIR)
rm -rf $(SEMGREP_MODFILE)
.PHONY: test
test:
touch $(SEMGREP_MODFILE) # See [1].
stdout=$$(gofumpt -l $(GO_FILES) 2>&1); if [ "$$stdout" ]; then exit 1; fi
if [ -n "$$(gofumpt -l $(GO_FILES))" ]; then echo "Code is not properly formatted"; exit 1; fi
if [ -n "$$(goimports -l -local $(GO_MODULE) $(GO_FILES))" ]; then echo "Imports are not properly formatted"; exit 1; fi
go vet ./...
misspell -error $(GO_FILES)
gocyclo -over 10 $(GO_FILES)
staticcheck ./...
errcheck -ignoregenerated -exclude errcheck_excludes.txt ./...
gocritic check -disable='#experimental,#opinionated' -@ifElseChain.minThreshold 3 ./...
revive -set_exit_status -exclude ./docs ./...
nilaway ./...
go test -v -cover ./...
gosec -exclude-dir=tests ./...
semgrep --lang=go --config=tests/semgrep-rules/go --metrics=off
rm -rf $(SEMGREP_MODFILE) # See [1].
gosec -exclude-generated -exclude-dir=tests ./...
govulncheck ./...
@printf '\n%s\n' "> Test successful"
.PHONY: setup
setup:
git submodule update --init --recursive
go install github.com/client9/misspell/cmd/misspell@latest
go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
go install github.com/go-critic/go-critic/cmd/gocritic@latest
go install github.com/kisielk/errcheck@latest
go install github.com/mgechev/revive@latest
go install github.com/securego/gosec/v2/cmd/gosec@latest
go install github.com/swaggo/swag/cmd/swag@latest
go install go.uber.org/nilaway/cmd/nilaway@latest
go install golang.org/x/tools/cmd/goimports@latest
go install golang.org/x/vuln/cmd/govulncheck@latest
go install honnef.co/go/tools/cmd/staticcheck@latest
go install mvdan.cc/gofumpt@latest
poetry install
.PHONY: fmt
fmt:
gofumpt -l -w $(GO_FILES)
.PHONY: swag
swag:
swag: build
swag init --parseDependency=true --exclude $(TESTS_DIR) -g cmd/pushbits/main.go
.PHONY: docker_build_dev
docker_build_dev:
podman build \
-t local/pushbits .
.PHONY: run_postgres_debug
podman run \
--rm \
--name=postgres \
--network=host \
--env-file \
postgres-debug.env docker.io/library/postgres:15

View file

@ -14,7 +14,7 @@
</div>
<p align="center">
<a href="https://github.com/pushbits/server/actions"><img alt="Build status" src="https://img.shields.io/github/workflow/status/pushbits/server/Main"/></a>&nbsp;
<a href="https://github.com/pushbits/server/actions"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/pushbits/server/test.yml?branch=main"/></a>&nbsp;
<a href="https://www.pushbits.io/docs/"><img alt="Documentation" src="https://img.shields.io/badge/docs-online-success"/></a>&nbsp;
<a href="https://www.pushbits.io/api/"><img alt="API Documentation" src="https://img.shields.io/badge/api docs-online-success"/></a>&nbsp;
<a href="https://matrix.to/#/#pushbits:matrix.org"><img alt="Matrix" src="https://img.shields.io/matrix/pushbits:matrix.org"/></a>&nbsp;

View file

@ -1,8 +1,10 @@
// Package main provides the main function as a starting point of this tool.
package main
import (
"os"
"os/signal"
"runtime/debug"
"syscall"
"github.com/pushbits/server/internal/authentication/credentials"
@ -14,8 +16,6 @@ import (
"github.com/pushbits/server/internal/runner"
)
var version string
func setupCleanup(db *database.Database, dp *dispatcher.Dispatcher) {
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
@ -28,8 +28,18 @@ func setupCleanup(db *database.Database, dp *dispatcher.Dispatcher) {
}()
}
func printStarupMessage() {
buildInfo, ok := debug.ReadBuildInfo()
if !ok {
log.L.Fatalln("Build info not available")
return
}
log.L.Printf("Starting PushBits %s", buildInfo.Main.Version)
}
// @title PushBits Server API Documentation
// @version 0.7.2
// @version 0.10.5
// @description Documentation for the PushBits server API.
// @contact.name The PushBits Developers
@ -44,11 +54,7 @@ func setupCleanup(db *database.Database, dp *dispatcher.Dispatcher) {
// @securityDefinitions.basic BasicAuth
func main() {
if len(version) == 0 {
log.L.Panic("Version not set")
} else {
log.L.Printf("Starting PushBits %s", version)
}
printStarupMessage()
c := configuration.Get()
@ -62,6 +68,11 @@ func main() {
db, err := database.Create(cm, c.Database.Dialect, c.Database.Connection)
if err != nil {
log.L.Fatal(err)
return
}
if db == nil {
log.L.Fatal("db is nil but error was nil")
return
}
defer db.Close()
@ -72,23 +83,31 @@ func main() {
dp, err := dispatcher.Create(c.Matrix.Homeserver, c.Matrix.Username, c.Matrix.Password, c.Formatting)
if err != nil {
log.L.Fatal(err)
return
}
if dp == nil {
log.L.Fatal("dp is nil but error was nil")
return
}
defer dp.Close()
setupCleanup(db, dp)
err = db.RepairChannels(dp)
err = db.RepairChannels(dp, &c.RepairBehavior)
if err != nil {
log.L.Fatal(err)
return
}
engine, err := router.Create(c.Debug, c.HTTP.TrustedProxies, cm, db, dp, &c.Alertmanager)
if err != nil {
log.L.Fatal(err)
return
}
err = runner.Run(engine, c.HTTP.ListenAddress, c.HTTP.Port)
err = runner.Run(engine, c)
if err != nil {
log.L.Fatal(err)
return
}
}

View file

@ -16,12 +16,20 @@ http:
# What proxies to trust.
trustedproxies: []
# Filename of the TLS certificate.
certfile: ''
# Filename of the TLS private key.
keyfile: ''
database:
# Currently sqlite3 and mysql are supported.
# Currently sqlite3, mysql, and postgres are supported.
dialect: 'sqlite3'
# For sqlite3, specifies the database file. For mysql, specifies the connection string. Check out
# https://github.com/go-sql-driver/mysql#dsn-data-source-name for details.
# - For sqlite3, specify the database file.
# - For mysql specify the connection string. See details at https://github.com/go-sql-driver/mysql#dsn-data-source-name
# - For postgres, see https://github.com/jackc/pgx.
# Also consider the canonical docs at https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING.
connection: 'pushbits.db'
admin:
@ -70,3 +78,9 @@ alertmanager:
annotationtitle: title
# The name of the entry in the alerts annotations or labels that should be used for the message
annotationmessage: message
repairbehavior:
# Reset the room's name to what was initially set by PushBits.
resetroomname: true
# Reset the room's topic to what was initially set by PushBits.
resetroomtopic: true

1
errcheck_excludes.txt Normal file
View file

@ -0,0 +1 @@
(*github.com/gin-gonic/gin.Context).AbortWithError

90
go.mod
View file

@ -1,44 +1,70 @@
module github.com/pushbits/server
go 1.18
go 1.24
require (
github.com/alexedwards/argon2id v0.0.0-20201228115903-cf543ebc1f7b
github.com/gin-contrib/location v0.0.2
github.com/gin-gonic/gin v1.7.7
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd
github.com/jinzhu/configor v1.2.1
github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.7.0
github.com/alexedwards/argon2id v1.0.0
github.com/gin-contrib/location v1.0.2
github.com/gin-gonic/gin v1.10.0
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e
github.com/jinzhu/configor v1.2.2
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v2 v2.4.0
gorm.io/driver/mysql v1.0.4
gorm.io/driver/sqlite v1.2.6
gorm.io/gorm v1.22.3
maunium.net/go/mautrix v0.10.10
gorm.io/driver/mysql v1.5.7
gorm.io/driver/postgres v1.5.11
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.25.12
maunium.net/go/mautrix v0.23.0
)
require (
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/btcsuite/btcutil v1.0.2 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/bytedance/sonic v1.12.8 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.13.0 // indirect
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/golang/protobuf v1.3.3 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // 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.24.0 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.2 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.2 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/leodido/go-urn v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-sqlite3 v1.14.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v1.14.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ugorji/go/codec v1.1.7 // indirect
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/tidwall/gjson v1.18.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.mau.fi/util v0.8.4 // indirect
golang.org/x/arch v0.14.0 // indirect
golang.org/x/crypto v0.33.0 // indirect
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
golang.org/x/net v0.35.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

298
go.sum
View file

@ -1,141 +1,207 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alexedwards/argon2id v0.0.0-20201228115903-cf543ebc1f7b h1:jEg+fE+POnmUy40B+aSKEPqZDmsdl55hZU0YKXEzz1k=
github.com/alexedwards/argon2id v0.0.0-20201228115903-cf543ebc1f7b/go.mod h1:Kmn5t2Rb93Q4NTprN4+CCgARGvigKMJyxP0WckpTUp0=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gin-contrib/location v0.0.2 h1:QZKh1+K/LLR4KG/61eIO3b7MLuKi8tytQhV6texLgP4=
github.com/gin-contrib/location v0.0.2/go.mod h1:NGoidiRlf0BlA/VKSVp+g3cuSMeTmip/63PhEjRhUAc=
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.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd h1:0b8AqsWQb6A0jjx80UXLG/uMTXQkGD0IGuXWqsrNz1M=
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/location v1.0.2 h1:FQ3bYuxtXjv/E8unfwfqdUXPms9JR64qBEul8TOw6IA=
github.com/gin-contrib/location v1.0.2/go.mod h1:g+5CKBkpOHL+PkrH2j6wK1u46MTOKZvBM27Vg4/IFuc=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
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=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e h1:ESHlT0RVZphh4JGBz49I5R6nTdC8Qyc08vU25GQHzzQ=
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/configor v1.2.2 h1:sLgh6KMzpCmaQB4e+9Fu/29VErtBUqsS2t8C9BNIVsA=
github.com/jinzhu/configor v1.2.2/go.mod h1:iFFSfOBKP3kC2Dku0ZGB3t3aulfQgTGJknodhFavsU8=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
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/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.3/go.mod h1:5WdjKx3AQMvCJ4RG6/2UYT7dLrGvJUV1x4jdTAyGvZs=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
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/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mau.fi/util v0.8.4 h1:mVKlJcXWfVo8ZW3f4vqtjGpqtZqJvX4ETekxawt2vnQ=
go.mau.fi/util v0.8.4/go.mod h1:MOfGTs1CBuK6ERTcSL4lb5YU7/ujz09eOPVEDckuazY=
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
golang.org/x/arch v0.14.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-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM=
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs=
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.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-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/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 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
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.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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/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 h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.4 h1:TATTzt+kR+IV0+h3iUB3dHUe8omCvQ0rOkmfCsUBohk=
gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA=
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
maunium.net/go/maulogger/v2 v2.3.2/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.10.10 h1:aaEuVopM3rkgOxL8Ldn2E8vcIIfKDE+tBfX/uPCRFWs=
maunium.net/go/mautrix v0.10.10/go.mod h1:4XljZZGZiIlpfbQ+Tt2ykjapskJ8a7Z2i9y/+YaceF8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314=
gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
maunium.net/go/mautrix v0.23.0 h1:HNlR19eew5lvrNSL2muhExaGhYdaGk5FfEiA82QqUP4=
maunium.net/go/mautrix v0.23.0/go.mod h1:AGnnaz3ylGikUo1I1MJVn9QLsl2No1/ZNnGDyO0QD5s=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

View file

@ -1,3 +1,4 @@
// Package alertmanager provides definitions and functionality related to Alertmanager notifications.
package alertmanager
import (
@ -5,18 +6,21 @@ import (
"net/url"
"github.com/gin-gonic/gin"
"github.com/pushbits/server/internal/api"
"github.com/pushbits/server/internal/authentication"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
)
type AlertmanagerHandler struct {
// Handler holds information for processing alerts received via Alertmanager.
type Handler struct {
DP api.NotificationDispatcher
Settings AlertmanagerHandlerSettings
Settings HandlerSettings
}
type AlertmanagerHandlerSettings struct {
// HandlerSettings represents the settings for processing alerts received via Alertmanager.
type HandlerSettings struct {
TitleAnnotation string
MessageAnnotation string
}
@ -33,8 +37,12 @@ type AlertmanagerHandlerSettings struct {
// @Success 200 {object} []model.Notification
// @Failure 500,404,403 ""
// @Router /alert [post]
func (h *AlertmanagerHandler) CreateAlert(ctx *gin.Context) {
func (h *Handler) CreateAlert(ctx *gin.Context) {
application := authentication.GetApplication(ctx)
if application == nil {
return
}
log.L.Printf("Sending alert notification for application %s.", application.Name)
var hook model.AlertmanagerWebhook
@ -52,7 +60,7 @@ func (h *AlertmanagerHandler) CreateAlert(ctx *gin.Context) {
}
notification.ID = messageID
notification.UrlEncodedID = url.QueryEscape(messageID)
notification.URLEncodedID = url.QueryEscape(messageID)
notifications[i] = notification
}
ctx.JSON(http.StatusOK, &notifications)

101
internal/api/api_test.go Normal file
View file

@ -0,0 +1,101 @@
package api
import (
"fmt"
"os"
"testing"
"github.com/gin-gonic/gin"
"github.com/pushbits/server/internal/authentication/credentials"
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/database"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests/mockups"
)
// TestContext holds all test-related objects
type TestContext struct {
ApplicationHandler *ApplicationHandler
Users []*model.User
Database *database.Database
NotificationHandler *NotificationHandler
UserHandler *UserHandler
Config *configuration.Configuration
}
var GlobalTestContext *TestContext
func cleanup() {
err := os.Remove("pushbits-test.db")
if err != nil {
log.L.Warnln("Cannot delete test database: ", err)
}
}
func TestMain(m *testing.M) {
cleanup()
gin.SetMode(gin.TestMode)
GlobalTestContext = CreateTestContext(nil)
m.Run()
cleanup()
}
// GetTestContext initializes and verifies all required test components
func GetTestContext(_ *testing.T) *TestContext {
if GlobalTestContext == nil {
GlobalTestContext = CreateTestContext(nil)
}
return GlobalTestContext
}
// CreateTestContext initializes and verifies all required test components
func CreateTestContext(_ *testing.T) *TestContext {
ctx := &TestContext{}
config := configuration.Configuration{}
config.Database.Connection = "pushbits-test.db"
config.Database.Dialect = "sqlite3"
config.Crypto.Argon2.Iterations = 4
config.Crypto.Argon2.Parallelism = 4
config.Crypto.Argon2.Memory = 131072
config.Crypto.Argon2.SaltLength = 16
config.Crypto.Argon2.KeyLength = 32
config.Admin.Name = "user"
config.Admin.Password = "pushbits"
ctx.Config = &config
db, err := mockups.GetEmptyDatabase(ctx.Config.Crypto)
if err != nil {
cleanup()
panic(fmt.Errorf("cannot set up database: %w", err))
}
ctx.Database = db
ctx.ApplicationHandler = &ApplicationHandler{
DB: ctx.Database,
DP: &mockups.MockDispatcher{},
}
ctx.Users = mockups.GetUsers(ctx.Config)
ctx.NotificationHandler = &NotificationHandler{
DB: ctx.Database,
DP: &mockups.MockDispatcher{},
}
ctx.UserHandler = &UserHandler{
AH: ctx.ApplicationHandler,
CM: credentials.CreateManager(false, ctx.Config.Crypto),
DB: ctx.Database,
DP: &mockups.MockDispatcher{},
}
return ctx
}

View file

@ -5,6 +5,7 @@ import (
"net/http"
"github.com/pushbits/server/internal/authentication"
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
@ -27,9 +28,13 @@ func (h *ApplicationHandler) generateToken(compat bool) string {
}
func (h *ApplicationHandler) registerApplication(ctx *gin.Context, a *model.Application, u *model.User) error {
if a == nil || u == nil {
return errors.New("nil parameters provided")
}
log.L.Printf("Registering application %s.", a.Name)
channelID, err := h.DP.RegisterApplication(a.ID, a.Name, a.Token, u.MatrixID)
channelID, err := h.DP.RegisterApplication(a.ID, a.Name, u.MatrixID)
if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success {
return err
}
@ -45,6 +50,10 @@ func (h *ApplicationHandler) registerApplication(ctx *gin.Context, a *model.Appl
}
func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User, name string, compat bool) (*model.Application, error) {
if u == nil {
return nil, errors.New("nil parameters provided")
}
log.L.Printf("Creating application %s.", name)
application := model.Application{}
@ -70,6 +79,10 @@ func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User,
}
func (h *ApplicationHandler) deleteApplication(ctx *gin.Context, a *model.Application, u *model.User) error {
if a == nil || u == nil {
return errors.New("nil parameters provided")
}
log.L.Printf("Deleting application %s (ID %d).", a.Name, a.ID)
err := h.DP.DeregisterApplication(a, u)
@ -86,6 +99,10 @@ func (h *ApplicationHandler) deleteApplication(ctx *gin.Context, a *model.Applic
}
func (h *ApplicationHandler) updateApplication(ctx *gin.Context, a *model.Application, updateApplication *model.UpdateApplication) error {
if a == nil || updateApplication == nil {
return errors.New("nil parameters provided")
}
log.L.Printf("Updating application %s (ID %d).", a.Name, a.ID)
if updateApplication.Name != nil {
@ -104,7 +121,7 @@ func (h *ApplicationHandler) updateApplication(ctx *gin.Context, a *model.Applic
return err
}
err = h.DP.UpdateApplication(a)
err = h.DP.UpdateApplication(a, &configuration.RepairBehavior{ResetRoomName: true, ResetRoomTopic: true})
if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success {
return err
}
@ -120,7 +137,7 @@ func (h *ApplicationHandler) updateApplication(ctx *gin.Context, a *model.Applic
// @Accept json,mpfd
// @Produce json
// @Param name query string true "Name of the application"
// @Param strict_compatability query boolean false "Use strict compatability mode"
// @Param strict_compatibility query boolean false "Use strict compatibility mode"
// @Success 200 {object} model.Application
// @Failure 400 ""
// @Security BasicAuth
@ -185,7 +202,7 @@ func (h *ApplicationHandler) GetApplications(ctx *gin.Context) {
// @Router /application/{id} [get]
func (h *ApplicationHandler) GetApplication(ctx *gin.Context) {
application, err := getApplication(ctx, h.DB)
if err != nil {
if err != nil || application == nil {
return
}
@ -217,7 +234,7 @@ func (h *ApplicationHandler) GetApplication(ctx *gin.Context) {
// @Router /application/{id} [delete]
func (h *ApplicationHandler) DeleteApplication(ctx *gin.Context) {
application, err := getApplication(ctx, h.DB)
if err != nil {
if err != nil || application == nil {
return
}
@ -242,14 +259,14 @@ func (h *ApplicationHandler) DeleteApplication(ctx *gin.Context) {
// @Param id path int true "ID of the application"
// @Param name query string false "New name for the application"
// @Param refresh_token query bool false "Generate new refresh token for the application"
// @Param strict_compatability query bool false "Whether to use strict compataibility mode"
// @Param strict_compatibility query bool false "Whether to use strict compataibility mode"
// @Success 200 ""
// @Failure 500,404,403 ""
// @Security BasicAuth
// @Router /application/{id} [put]
func (h *ApplicationHandler) UpdateApplication(ctx *gin.Context) {
application, err := getApplication(ctx, h.DB)
if err != nil {
if err != nil || application == nil {
return
}

View file

@ -3,132 +3,64 @@ package api
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"io"
"testing"
"github.com/gin-gonic/gin"
"github.com/pushbits/server/internal/authentication/credentials"
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/database"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests"
"github.com/pushbits/server/tests/mockups"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
TestApplicationHandler *ApplicationHandler
TestUsers []*model.User
TestDatabase *database.Database
TestNotificationHandler *NotificationHandler
TestUserHandler *UserHandler
TestConfig *configuration.Configuration
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests"
)
// Collect all created applications to check & delete them later
var SuccessAplications map[uint][]model.Application
func TestMain(m *testing.M) {
cleanUp()
// Get main config and adapt
config := &configuration.Configuration{}
config.Database.Connection = "pushbits-test.db"
config.Database.Dialect = "sqlite3"
config.Crypto.Argon2.Iterations = 4
config.Crypto.Argon2.Parallelism = 4
config.Crypto.Argon2.Memory = 131072
config.Crypto.Argon2.SaltLength = 16
config.Crypto.Argon2.KeyLength = 32
config.Admin.Name = "user"
config.Admin.Password = "pushbits"
TestConfig = config
// Set up test environment
db, err := mockups.GetEmptyDatabase(config.Crypto)
if err != nil {
cleanUp()
log.L.Println("Can not set up database: ", err)
os.Exit(1)
}
TestDatabase = db
appHandler, err := getApplicationHandler(config)
if err != nil {
cleanUp()
log.L.Println("Can not set up application handler: ", err)
os.Exit(1)
}
TestApplicationHandler = appHandler
TestUsers = mockups.GetUsers(config)
SuccessAplications = make(map[uint][]model.Application)
TestNotificationHandler = &NotificationHandler{
DB: TestDatabase,
DP: &mockups.MockDispatcher{},
}
TestUserHandler = &UserHandler{
AH: TestApplicationHandler,
CM: credentials.CreateManager(false, config.Crypto),
DB: TestDatabase,
DP: &mockups.MockDispatcher{},
}
// Run
m.Run()
log.L.Println("Clean up after Test")
cleanUp()
}
var SuccessApplications = make(map[uint][]model.Application)
func TestApi_RegisterApplicationWithoutUser(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
gin.SetMode(gin.TestMode)
reqWoUser := tests.Request{Name: "Invalid JSON Data", Method: "POST", Endpoint: "/application", Data: `{"name": "test1", "strict_compatibility": true}`, Headers: map[string]string{"Content-Type": "application/json"}}
_, c, err := reqWoUser.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
assert.Panicsf(func() { TestApplicationHandler.CreateApplication(c) }, "CreateApplication did not panic altough user is not in context")
assert.Panicsf(func() { ctx.ApplicationHandler.CreateApplication(c) }, "CreateApplication did not panic although user is not in context")
}
func TestApi_RegisterApplication(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
testCases := make([]tests.Request, 0)
testCases = append(testCases, tests.Request{Name: "Invalid Form Data", Method: "POST", Endpoint: "/application", Data: "k=1&v=abc", ShouldStatus: 400})
testCases = append(testCases, tests.Request{Name: "Invalid JSON Data", Method: "POST", Endpoint: "/application", Data: `{"name": "test1", "strict_compatibility": "oh yes"}`, Headers: map[string]string{"Content-Type": "application/json"}, ShouldStatus: 400})
testCases = append(testCases, tests.Request{Name: "Valid JSON Data", Method: "POST", Endpoint: "/application", Data: `{"name": "test2", "strict_compatibility": true}`, Headers: map[string]string{"Content-Type": "application/json"}, ShouldStatus: 200})
for _, user := range TestUsers {
SuccessAplications[user.ID] = make([]model.Application, 0)
for _, user := range ctx.Users {
SuccessApplications[user.ID] = make([]model.Application, 0)
for _, req := range testCases {
var application model.Application
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("user", user)
TestApplicationHandler.CreateApplication(c)
ctx.ApplicationHandler.CreateApplication(c)
// Parse body only for successful requests
if req.ShouldStatus >= 200 && req.ShouldStatus < 300 {
body, err := ioutil.ReadAll(w.Body)
require.NoErrorf(err, "Can not read request body")
body, err := io.ReadAll(w.Body)
require.NoErrorf(err, "Cannot read request body")
err = json.Unmarshal(body, &application)
require.NoErrorf(err, "Can not unmarshal request body")
require.NoErrorf(err, "Cannot unmarshal request body")
SuccessAplications[user.ID] = append(SuccessAplications[user.ID], application)
SuccessApplications[user.ID] = append(SuccessApplications[user.ID], application)
}
assert.Equalf(w.Code, req.ShouldStatus, "CreateApplication (Test case: \"%s\") Expected status code %v but received %v.", req.Name, req.ShouldStatus, w.Code)
@ -137,37 +69,38 @@ func TestApi_RegisterApplication(t *testing.T) {
}
func TestApi_GetApplications(t *testing.T) {
ctx := GetTestContext(t)
var applications []model.Application
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
testCases := make([]tests.Request, 0)
testCases = append(testCases, tests.Request{Name: "Valid Request", Method: "GET", Endpoint: "/application", ShouldStatus: 200})
for _, user := range TestUsers {
for _, user := range ctx.Users {
for _, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("user", user)
TestApplicationHandler.GetApplications(c)
ctx.ApplicationHandler.GetApplications(c)
// Parse body only for successful requests
if req.ShouldStatus >= 200 && req.ShouldStatus < 300 {
body, err := ioutil.ReadAll(w.Body)
require.NoErrorf(err, "Can not read request body")
body, err := io.ReadAll(w.Body)
require.NoErrorf(err, "Cannot read request body")
err = json.Unmarshal(body, &applications)
require.NoErrorf(err, "Can not unmarshal request body")
require.NoErrorf(err, "Cannot unmarshal request body")
if err != nil {
continue
}
assert.Truef(validateAllApplications(user, applications), "Did not find application created previously")
assert.Equalf(len(applications), len(SuccessAplications[user.ID]), "Created %d application(s) but got %d back", len(SuccessAplications[user.ID]), len(applications))
assert.Equalf(len(applications), len(SuccessApplications[user.ID]), "Created %d application(s) but got %d back", len(SuccessApplications[user.ID]), len(applications))
}
assert.Equalf(w.Code, req.ShouldStatus, "GetApplications (Test case: \"%s\") Expected status code %v but received %v.", req.Name, req.ShouldStatus, w.Code)
@ -176,22 +109,24 @@ func TestApi_GetApplications(t *testing.T) {
}
func TestApi_GetApplicationsWithoutUser(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
gin.SetMode(gin.TestMode)
testCase := tests.Request{Name: "Valid Request", Method: "GET", Endpoint: "/application"}
_, c, err := testCase.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
assert.Panicsf(func() { TestApplicationHandler.GetApplications(c) }, "GetApplications did not panic altough user is not in context")
assert.Panicsf(func() { ctx.ApplicationHandler.GetApplications(c) }, "GetApplications did not panic although user is not in context")
}
func TestApi_GetApplicationErrors(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
gin.SetMode(gin.TestMode)
// Arbitrary test cases
testCases := make(map[uint]tests.Request)
@ -199,16 +134,16 @@ func TestApi_GetApplicationErrors(t *testing.T) {
testCases[5555] = tests.Request{Name: "Requesting unknown application 5555", Method: "GET", Endpoint: "/application/5555", ShouldStatus: 404}
testCases[99999999999999999] = tests.Request{Name: "Requesting unknown application 99999999999999999", Method: "GET", Endpoint: "/application/99999999999999999", ShouldStatus: 404}
for _, user := range TestUsers {
for _, user := range ctx.Users {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("user", user)
c.Set("id", id)
TestApplicationHandler.GetApplication(c)
ctx.ApplicationHandler.GetApplication(c)
assert.Equalf(w.Code, req.ShouldStatus, "GetApplication (Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
}
@ -216,32 +151,33 @@ func TestApi_GetApplicationErrors(t *testing.T) {
}
func TestApi_GetApplication(t *testing.T) {
ctx := GetTestContext(t)
var application model.Application
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
// Previously generated applications
for _, user := range TestUsers {
for _, app := range SuccessAplications[user.ID] {
for _, user := range ctx.Users {
for _, app := range SuccessApplications[user.ID] {
req := tests.Request{Name: fmt.Sprintf("Requesting application %s (%d)", app.Name, app.ID), Method: "GET", Endpoint: fmt.Sprintf("/application/%d", app.ID), ShouldStatus: 200}
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("user", user)
c.Set("id", app.ID)
TestApplicationHandler.GetApplication(c)
ctx.ApplicationHandler.GetApplication(c)
// Parse body only for successful requests
if req.ShouldStatus >= 200 && req.ShouldStatus < 300 {
body, err := ioutil.ReadAll(w.Body)
require.NoErrorf(err, "Can not read request body")
body, err := io.ReadAll(w.Body)
require.NoErrorf(err, "Cannot read request body")
err = json.Unmarshal(body, &application)
require.NoErrorf(err, "Can not unmarshal request body: %v", err)
require.NoErrorf(err, "Cannot unmarshal request body: %v", err)
assert.Equalf(application.ID, app.ID, "Application ID should be %d but is %d", app.ID, application.ID)
assert.Equalf(application.Name, app.Name, "Application Name should be %s but is %s", app.Name, application.Name)
@ -255,14 +191,15 @@ func TestApi_GetApplication(t *testing.T) {
}
func TestApi_UpdateApplication(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
for _, user := range TestUsers {
for _, user := range ctx.Users {
testCases := make(map[uint]tests.Request)
// Previously generated applications
for _, app := range SuccessAplications[user.ID] {
for _, app := range SuccessApplications[user.ID] {
newName := app.Name + "-new_name"
updateApp := model.UpdateApplication{
Name: &newName,
@ -282,12 +219,12 @@ func TestApi_UpdateApplication(t *testing.T) {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("user", user)
c.Set("id", id)
TestApplicationHandler.UpdateApplication(c)
ctx.ApplicationHandler.UpdateApplication(c)
assert.Equalf(w.Code, req.ShouldStatus, "UpdateApplication (Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
}
@ -295,13 +232,14 @@ func TestApi_UpdateApplication(t *testing.T) {
}
func TestApi_DeleteApplication(t *testing.T) {
assert := assert.New(t)
gin.SetMode(gin.TestMode)
ctx := GetTestContext(t)
for _, user := range TestUsers {
assert := assert.New(t)
for _, user := range ctx.Users {
testCases := make(map[uint]tests.Request)
// Previously generated applications
for _, app := range SuccessAplications[user.ID] {
for _, app := range SuccessApplications[user.ID] {
testCases[app.ID] = tests.Request{Name: fmt.Sprintf("Delete application %s (%d)", app.Name, app.ID), Method: "DELETE", Endpoint: fmt.Sprintf("/application/%d", app.ID), ShouldStatus: 200}
}
// Arbitrary test cases
@ -310,35 +248,25 @@ func TestApi_DeleteApplication(t *testing.T) {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("user", user)
c.Set("id", id)
TestApplicationHandler.DeleteApplication(c)
ctx.ApplicationHandler.DeleteApplication(c)
assert.Equalf(w.Code, req.ShouldStatus, "DeleteApplication (Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
}
}
}
// GetApplicationHandler creates and returns an application handler
func getApplicationHandler(c *configuration.Configuration) (*ApplicationHandler, error) {
dispatcher := &mockups.MockDispatcher{}
return &ApplicationHandler{
DB: TestDatabase,
DP: dispatcher,
}, nil
}
// True if all created applications are in list
func validateAllApplications(user *model.User, apps []model.Application) bool {
if _, ok := SuccessAplications[user.ID]; !ok {
if _, ok := SuccessApplications[user.ID]; !ok {
return len(apps) == 0
}
for _, successApp := range SuccessAplications[user.ID] {
for _, successApp := range SuccessApplications[user.ID] {
foundApp := false
for _, app := range apps {
if app.ID == successApp.ID {
@ -354,7 +282,3 @@ func validateAllApplications(user *model.User, apps []model.Application) bool {
return true
}
func cleanUp() {
os.Remove("pushbits-test.db")
}

View file

@ -12,7 +12,7 @@ import (
func getID(ctx *gin.Context) (uint, error) {
id, ok := ctx.MustGet("id").(uint)
if !ok {
err := errors.New("an error occured while retrieving ID from context")
err := errors.New("an error occurred while retrieving ID from context")
ctx.AbortWithError(http.StatusInternalServerError, err)
return 0, err
}
@ -23,7 +23,7 @@ func getID(ctx *gin.Context) (uint, error) {
func getMessageID(ctx *gin.Context) (string, error) {
id, ok := ctx.MustGet("messageid").(string)
if !ok {
err := errors.New("an error occured while retrieving messageID from context")
err := errors.New("an error occurred while retrieving messageID from context")
ctx.AbortWithError(http.StatusInternalServerError, err)
return "", err
}
@ -41,6 +41,11 @@ func getApplication(ctx *gin.Context, db Database) (*model.Application, error) {
if success := SuccessOrAbort(ctx, http.StatusNotFound, err); !success {
return nil, err
}
if application == nil {
err := errors.New("application not found")
ctx.AbortWithError(http.StatusNotFound, err)
return nil, err
}
return application, nil
}

View file

@ -3,18 +3,20 @@ package api
import (
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests"
"github.com/pushbits/server/tests/mockups"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestApi_getID(t *testing.T) {
GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
testValue := uint(1337)
testCases := make(map[interface{}]tests.Request)
@ -31,21 +33,21 @@ func TestApi_getID(t *testing.T) {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("id", id)
idReturned, err := getID(c)
if req.ShouldStatus >= 200 && req.ShouldStatus < 300 {
require.NoErrorf(err, "getId with id %v (%t) returned an error altough it should not: %v", id, id, err)
require.NoErrorf(err, "getId with id %v (%t) returned an error although it should not: %v", id, id, err)
idUint, ok := id.(uint)
if ok {
assert.Equalf(idReturned, idUint, "getApi id was set to %d but result is %d", idUint, idReturned)
}
} else {
assert.Errorf(err, "getId with id %v (%t) returned no error altough it should", id, id)
assert.Errorf(err, "getId with id %v (%t) returned no error although it should", id, id)
}
assert.Equalf(w.Code, req.ShouldStatus, "getApi id was set to %v (%T) and should result in status code %d but code is %d", id, id, req.ShouldStatus, w.Code)
@ -53,12 +55,17 @@ func TestApi_getID(t *testing.T) {
}
func TestApi_getApplication(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
applications := mockups.GetAllApplications()
mockups.AddApplicationsToDb(TestDatabase, applications)
err := mockups.AddApplicationsToDb(ctx.Database, applications)
if err != nil {
log.L.Fatalln("Cannot add mock applications to database: ", err)
}
// No testing of invalid ids as that is tested in TestApi_getID already
testCases := make(map[uint]tests.Request)
@ -69,30 +76,32 @@ func TestApi_getApplication(t *testing.T) {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("id", id)
app, err := getApplication(c, TestDatabase)
app, err := getApplication(c, ctx.Database)
if req.ShouldStatus >= 200 && req.ShouldStatus < 300 {
require.NoErrorf(err, "getApplication with id %v (%t) returned an error altough it should not: %v", id, id, err)
assert.Equalf(app.ID, id, "getApplication id was set to %d but resulting app id is %d", id, app.ID)
require.NoErrorf(err, "getApplication with id %v returned an unexpected error: %v", id, err)
require.NotNilf(app, "Expected a valid app for id %v, but got nil", id)
assert.Equalf(app.ID, id, "Expected app ID %d, but got %d", id, app.ID)
} else {
assert.Errorf(err, "getApplication with id %v (%t) returned no error altough it should", id, id)
require.Errorf(err, "Expected an error for id %v, but got none", id)
assert.Nilf(app, "Expected app to be nil for id %v, but got %+v", id, app)
}
assert.Equalf(w.Code, req.ShouldStatus, "getApplication id was set to %v (%T) and should result in status code %d but code is %d", id, id, req.ShouldStatus, w.Code)
assert.Equalf(w.Code, req.ShouldStatus, "Expected status code %d for id %v, but got %d", req.ShouldStatus, id, w.Code)
}
}
func TestApi_getUser(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
_, err := mockups.AddUsersToDb(TestDatabase, TestUsers)
_, err := mockups.AddUsersToDb(ctx.Database, ctx.Users)
assert.NoErrorf(err, "Adding users to database failed: %v", err)
// No testing of invalid ids as that is tested in TestApi_getID already
@ -104,20 +113,22 @@ func TestApi_getUser(t *testing.T) {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("id", id)
user, err := getUser(c, TestDatabase)
user, err := getUser(c, ctx.Database)
if req.ShouldStatus >= 200 && req.ShouldStatus < 300 {
require.NoErrorf(err, "getUser with id %v (%t) returned an error altough it should not: %v", id, id, err)
assert.Equalf(user.ID, id, "getUser id was set to %d but resulting app id is %d", id, user.ID)
require.NoErrorf(err, "getUser with id %v returned an unexpected error: %v", id, err)
require.NotNilf(user, "Expected a valid user for id %v, but got nil", id)
assert.Equalf(user.ID, id, "Expected user ID %d, but got %d", id, user.ID)
} else {
assert.Errorf(err, "getUser with id %v (%t) returned no error altough it should", id, id)
require.Errorf(err, "Expected an error for id %v, but got none", id)
assert.Nilf(user, "Expected user to be nil for id %v, but got %+v", id, user)
}
assert.Equalf(w.Code, req.ShouldStatus, "getUser id was set to %v (%T) and should result in status code %d but code is %d", id, id, req.ShouldStatus, w.Code)
assert.Equalf(w.Code, req.ShouldStatus, "Expected status code %d for id %v, but got %d", req.ShouldStatus, id, w.Code)
}
}

View file

@ -3,14 +3,17 @@ package api
import (
"testing"
"github.com/pushbits/server/tests"
"github.com/stretchr/testify/assert"
"github.com/pushbits/server/tests"
)
func TestApi_Health(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
handler := HealthHandler{
DB: TestDatabase,
DB: ctx.Database,
}
testCases := make([]tests.Request, 0)
@ -19,7 +22,7 @@ func TestApi_Health(t *testing.T) {
for _, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
handler.Health(c)

View file

@ -1,6 +1,8 @@
// Package api provides definitions and functionality related to the API.
package api
import (
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/model"
)
@ -26,9 +28,9 @@ type Database interface {
// The Dispatcher interface for relaying notifications.
type Dispatcher interface {
RegisterApplication(id uint, name, token, user string) (string, error)
RegisterApplication(id uint, name, user string) (string, error)
DeregisterApplication(a *model.Application, u *model.User) error
UpdateApplication(a *model.Application) error
UpdateApplication(a *model.Application, behavior *configuration.RepairBehavior) error
}
// The CredentialsManager interface for updating credentials.

View file

@ -4,18 +4,20 @@ import (
"github.com/gin-gonic/gin"
)
type idInURI struct {
// IDInURI is used to retrieve an ID from a context.
type IDInURI struct {
ID uint `uri:"id" binding:"required"`
}
type messageIdInURI struct {
// messageIDInURI is used to retrieve an message ID from a context.
type messageIDInURI struct {
MessageID string `uri:"messageid" binding:"required"`
}
// RequireIDInURI returns a Gin middleware which requires an ID to be supplied in the URI of the request.
func RequireIDInURI() gin.HandlerFunc {
return func(ctx *gin.Context) {
var requestModel idInURI
var requestModel IDInURI
if err := ctx.BindUri(&requestModel); err != nil {
return
@ -28,7 +30,7 @@ func RequireIDInURI() gin.HandlerFunc {
// RequireMessageIDInURI returns a Gin middleware which requires an messageID to be supplied in the URI of the request.
func RequireMessageIDInURI() gin.HandlerFunc {
return func(ctx *gin.Context) {
var requestModel messageIdInURI
var requestModel messageIDInURI
if err := ctx.BindUri(&requestModel); err != nil {
return

View file

@ -44,6 +44,10 @@ type NotificationHandler struct {
// @Router /message [post]
func (h *NotificationHandler) CreateNotification(ctx *gin.Context) {
application := authentication.GetApplication(ctx)
if application == nil {
return
}
log.L.Printf("Sending notification for application %s.", application.Name)
var notification model.Notification
@ -59,7 +63,7 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) {
}
notification.ID = messageID
notification.UrlEncodedID = url.QueryEscape(messageID)
notification.URLEncodedID = url.QueryEscape(messageID)
ctx.JSON(http.StatusOK, &notification)
}
@ -78,6 +82,10 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) {
// @Router /message/{message_id} [DELETE]
func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) {
application := authentication.GetApplication(ctx)
if application == nil {
return
}
log.L.Printf("Deleting notification for application %s.", application.Name)
id, err := getMessageID(ctx)

View file

@ -2,20 +2,21 @@ package api
import (
"encoding/json"
"io/ioutil"
"io"
"testing"
"github.com/gin-gonic/gin"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests"
)
func TestApi_CreateNotification(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
gin.SetMode(gin.TestMode)
testApplication := model.Application{
ID: 1,
@ -36,15 +37,15 @@ func TestApi_CreateNotification(t *testing.T) {
var notification model.Notification
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("app", &testApplication)
TestNotificationHandler.CreateNotification(c)
ctx.NotificationHandler.CreateNotification(c)
// Parse body only for successful requests
if req.ShouldStatus >= 200 && req.ShouldStatus < 300 {
body, err := ioutil.ReadAll(w.Body)
body, err := io.ReadAll(w.Body)
require.NoErrorf(err, "Can not read request body")
err = json.Unmarshal(body, &notification)
require.NoErrorf(err, "Can not unmarshal request body")
@ -64,8 +65,9 @@ func TestApi_CreateNotification(t *testing.T) {
}
func TestApi_DeleteNotification(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
gin.SetMode(gin.TestMode)
testApplication := model.Application{
ID: 1,
@ -83,12 +85,12 @@ func TestApi_DeleteNotification(t *testing.T) {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("app", &testApplication)
c.Set("messageid", id)
TestNotificationHandler.DeleteNotification(c)
ctx.NotificationHandler.DeleteNotification(c)
assert.Equalf(w.Code, req.ShouldStatus, "(Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
}

View file

@ -38,6 +38,10 @@ func (h *UserHandler) requireMultipleAdmins(ctx *gin.Context) error {
}
func (h *UserHandler) deleteApplications(ctx *gin.Context, u *model.User) error {
if ctx == nil || u == nil {
return errors.New("nil parameters provided")
}
applications, err := h.DB.GetApplications(u)
if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success {
return err
@ -55,6 +59,10 @@ func (h *UserHandler) deleteApplications(ctx *gin.Context, u *model.User) error
}
func (h *UserHandler) updateChannels(ctx *gin.Context, u *model.User, matrixID string) error {
if ctx == nil || u == nil {
return errors.New("nil parameters provided")
}
applications, err := h.DB.GetApplications(u)
if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success {
return err
@ -83,15 +91,7 @@ func (h *UserHandler) updateChannels(ctx *gin.Context, u *model.User, matrixID s
return nil
}
func (h *UserHandler) updateUser(ctx *gin.Context, u *model.User, updateUser model.UpdateUser) error {
if updateUser.MatrixID != nil && u.MatrixID != *updateUser.MatrixID {
if err := h.updateChannels(ctx, u, *updateUser.MatrixID); err != nil {
return err
}
}
log.L.Printf("Updating user %s.", u.Name)
func (h *UserHandler) updateUserFields(ctx *gin.Context, u *model.User, updateUser model.UpdateUser) error {
if updateUser.Name != nil {
u.Name = *updateUser.Name
}
@ -100,7 +100,6 @@ func (h *UserHandler) updateUser(ctx *gin.Context, u *model.User, updateUser mod
if success := SuccessOrAbort(ctx, http.StatusBadRequest, err); !success {
return err
}
u.PasswordHash = hash
}
if updateUser.MatrixID != nil {
@ -109,6 +108,25 @@ func (h *UserHandler) updateUser(ctx *gin.Context, u *model.User, updateUser mod
if updateUser.IsAdmin != nil {
u.IsAdmin = *updateUser.IsAdmin
}
return nil
}
func (h *UserHandler) updateUser(ctx *gin.Context, u *model.User, updateUser model.UpdateUser) error {
if u == nil {
return errors.New("nil parameters provided")
}
if updateUser.MatrixID != nil && u.MatrixID != *updateUser.MatrixID {
if err := h.updateChannels(ctx, u, *updateUser.MatrixID); err != nil {
return err
}
}
log.L.Printf("Updating user %s.", u.Name)
if err := h.updateUserFields(ctx, u, updateUser); err != nil {
return err
}
err := h.DB.UpdateUser(u)
if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success {
@ -149,10 +167,12 @@ func (h *UserHandler) CreateUser(ctx *gin.Context) {
log.L.Printf("Creating user %s.", createUser.Name)
user, err := h.DB.CreateUser(createUser)
if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success {
return
}
if user == nil {
return
}
ctx.JSON(http.StatusOK, user.IntoExternalUser())
}
@ -170,6 +190,10 @@ func (h *UserHandler) CreateUser(ctx *gin.Context) {
// @Security BasicAuth
// @Router /user [get]
func (h *UserHandler) GetUsers(ctx *gin.Context) {
if ctx == nil {
return
}
users, err := h.DB.GetUsers()
if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success {
return
@ -199,7 +223,7 @@ func (h *UserHandler) GetUsers(ctx *gin.Context) {
// @Router /user/{id} [get]
func (h *UserHandler) GetUser(ctx *gin.Context) {
user, err := getUser(ctx, h.DB)
if err != nil {
if err != nil || user == nil {
return
}
@ -221,7 +245,7 @@ func (h *UserHandler) GetUser(ctx *gin.Context) {
// @Router /user/{id} [delete]
func (h *UserHandler) DeleteUser(ctx *gin.Context) {
user, err := getUser(ctx, h.DB)
if err != nil {
if err != nil || user == nil {
return
}
@ -265,7 +289,7 @@ func (h *UserHandler) DeleteUser(ctx *gin.Context) {
// @Router /user/{id} [put]
func (h *UserHandler) UpdateUser(ctx *gin.Context) {
user, err := getUser(ctx, h.DB)
if err != nil {
if err != nil || user == nil {
return
}
@ -275,6 +299,9 @@ func (h *UserHandler) UpdateUser(ctx *gin.Context) {
}
requestingUser := authentication.GetUser(ctx)
if requestingUser == nil {
return
}
// Last privileged user must not be taken privileges. Assumes that the current user has privileges.
if user.ID == requestingUser.ID && updateUser.IsAdmin != nil && !(*updateUser.IsAdmin) {

View file

@ -2,28 +2,31 @@ package api
import (
"encoding/json"
"io/ioutil"
"io"
"strconv"
"testing"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pushbits/server/internal/model"
"github.com/pushbits/server/tests"
)
func TestApi_CreateUser(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
testCases := make([]tests.Request, 0)
// Add all test users
for _, user := range TestUsers {
for _, user := range ctx.Users {
createUser := &model.CreateUser{}
createUser.ExternalUser.Name = user.Name
createUser.ExternalUser.MatrixID = "@" + user.Name + ":matrix.org"
createUser.ExternalUser.IsAdmin = user.IsAdmin
createUser.UserCredentials.Password = TestConfig.Admin.Password
createUser.UserCredentials.Password = ctx.Config.Admin.Password
testCase := tests.Request{
Name: "Already existing user " + user.Name,
@ -45,16 +48,18 @@ func TestApi_CreateUser(t *testing.T) {
for _, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
TestUserHandler.CreateUser(c)
ctx.UserHandler.CreateUser(c)
assert.Equalf(w.Code, req.ShouldStatus, "(Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
}
}
func TestApi_GetUsers(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
@ -65,21 +70,21 @@ func TestApi_GetUsers(t *testing.T) {
w, c, err := request.GetRequest()
if err != nil {
t.Fatalf((err.Error()))
t.Fatalf("error getting request: %v", err)
}
TestUserHandler.GetUsers(c)
ctx.UserHandler.GetUsers(c)
assert.Equalf(w.Code, 200, "Response code should be 200 but is %d", w.Code)
// Get users from body
users := make([]model.ExternalUser, 0)
usersRaw, err := ioutil.ReadAll(w.Body)
usersRaw, err := io.ReadAll(w.Body)
require.NoErrorf(err, "Failed to parse response body")
err = json.Unmarshal(usersRaw, &users)
require.NoErrorf(err, "Can not unmarshal users")
// Check existence of all known users
for _, user := range TestUsers {
for _, user := range ctx.Users {
found := false
for _, userExt := range users {
if user.ID == userExt.ID && user.Name == userExt.Name {
@ -92,13 +97,15 @@ func TestApi_GetUsers(t *testing.T) {
}
func TestApi_UpdateUser(t *testing.T) {
assert := assert.New(t)
admin := getAdmin()
ctx := GetTestContext(t)
assert := assert.New(t)
admin := getAdmin(ctx)
testCases := make(map[uint]tests.Request)
// Add all test users
for _, user := range TestUsers {
for _, user := range ctx.Users {
updateUser := &model.UpdateUser{}
user.Name += "+1"
user.IsAdmin = !user.IsAdmin
@ -120,12 +127,12 @@ func TestApi_UpdateUser(t *testing.T) {
for id, req := range testCases {
w, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("id", id)
c.Set("user", admin)
TestUserHandler.UpdateUser(c)
ctx.UserHandler.UpdateUser(c)
assert.Equalf(w.Code, req.ShouldStatus, "(Test case: \"%s\") Expected status code %v but have %v.", req.Name, req.ShouldStatus, w.Code)
}
@ -134,15 +141,17 @@ func TestApi_UpdateUser(t *testing.T) {
for id, req := range testCases {
_, c, err := req.GetRequest()
if err != nil {
t.Fatalf(err.Error())
t.Fatal(err.Error())
}
c.Set("id", id)
assert.Panicsf(func() { TestUserHandler.UpdateUser(c) }, "User not set should panic but did not")
assert.Panicsf(func() { ctx.UserHandler.UpdateUser(c) }, "User not set should panic but did not")
}
}
func TestApi_GetUser(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
@ -151,7 +160,7 @@ func TestApi_GetUser(t *testing.T) {
testCases[uint(9999999)] = tests.Request{Name: "Unknown id", Method: "GET", Endpoint: "/user/99999999", ShouldStatus: 404}
// Check if we can get all existing users
for _, user := range TestUsers {
for _, user := range ctx.Users {
testCases[user.ID] = tests.Request{
Name: "Valid user " + user.Name,
Method: "GET",
@ -166,14 +175,14 @@ func TestApi_GetUser(t *testing.T) {
require.NoErrorf(err, "(Test case %s) Could not make request", testCase.Name)
c.Set("id", id)
TestUserHandler.GetUser(c)
ctx.UserHandler.GetUser(c)
assert.Equalf(testCase.ShouldStatus, w.Code, "(Test case %s) Expected status code %d but have %d", testCase.Name, testCase.ShouldStatus, w.Code)
// Check content for successful requests
if testCase.ShouldReturn == 200 {
user := &model.ExternalUser{}
userBytes, err := ioutil.ReadAll(w.Body)
userBytes, err := io.ReadAll(w.Body)
require.NoErrorf(err, "(Test case %s) Can not read body", testCase.Name)
err = json.Unmarshal(userBytes, user)
require.NoErrorf(err, "(Test case %s) Can not unmarshal body", testCase.Name)
@ -191,13 +200,16 @@ func TestApi_GetUser(t *testing.T) {
}
func TestApi_DeleteUser(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
testCases := make(map[interface{}]tests.Request)
testCases["abcde"] = tests.Request{Name: "Invalid user - string", Method: "DELETE", Endpoint: "/user/abcde", ShouldStatus: 500}
testCases[uint(999999)] = tests.Request{Name: "Unknown user", Method: "DELETE", Endpoint: "/user/999999", ShouldStatus: 404}
for _, user := range TestUsers {
for _, user := range ctx.Users {
shouldStatus := 200
testCases[user.ID] = tests.Request{
Name: "Valid user " + user.Name,
@ -212,14 +224,14 @@ func TestApi_DeleteUser(t *testing.T) {
require.NoErrorf(err, "(Test case %s) Could not make request", testCase.Name)
c.Set("id", id)
TestUserHandler.DeleteUser(c)
ctx.UserHandler.DeleteUser(c)
assert.Equalf(testCase.ShouldStatus, w.Code, "(Test case %s) Expected status code %d but have %d", testCase.Name, testCase.ShouldStatus, w.Code)
}
}
func getAdmin() *model.User {
for _, user := range TestUsers {
func getAdmin(ctx *TestContext) *model.User {
for _, user := range ctx.Users {
if user.IsAdmin {
return user
}

View file

@ -10,11 +10,12 @@ import (
"github.com/gin-gonic/gin"
)
// SuccessOrAbort is a convenience function to write a HTTP status code based on a given error.
func SuccessOrAbort(ctx *gin.Context, code int, err error) bool {
if err != nil {
// If we know the error force error code
switch err {
case pberrors.ErrorMessageNotFound:
case pberrors.ErrMessageNotFound:
ctx.AbortWithError(http.StatusNotFound, err)
default:
ctx.AbortWithError(code, err)
@ -24,13 +25,13 @@ func SuccessOrAbort(ctx *gin.Context, code int, err error) bool {
return err == nil
}
func isCurrentUser(ctx *gin.Context, ID uint) bool {
func isCurrentUser(ctx *gin.Context, id uint) bool {
user := authentication.GetUser(ctx)
if user == nil {
return false
}
if user.ID != ID {
if user.ID != id {
ctx.AbortWithError(http.StatusForbidden, errors.New("only owner can delete application"))
return false
}

View file

@ -5,12 +5,15 @@ import (
"fmt"
"testing"
"github.com/pushbits/server/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pushbits/server/tests"
)
func TestApi_SuccessOrAbort(t *testing.T) {
GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
@ -37,10 +40,12 @@ func TestApi_SuccessOrAbort(t *testing.T) {
}
func TestApi_IsCurrentUser(t *testing.T) {
ctx := GetTestContext(t)
assert := assert.New(t)
require := require.New(t)
for _, user := range TestUsers {
for _, user := range ctx.Users {
testCases := make(map[uint]tests.Request)
testCases[user.ID] = tests.Request{Name: fmt.Sprintf("User %s - success", user.Name), Endpoint: "/", ShouldStatus: 200}

View file

@ -1,3 +1,4 @@
// Package assert provides convenience function to make assertions at runtime.
package assert
import (

View file

@ -1,3 +1,4 @@
// Package authentication provides definitions and functionality related to user authentication.
package authentication
import (
@ -33,9 +34,9 @@ func (a *Authenticator) userFromBasicAuth(ctx *gin.Context) (*model.User, error)
return nil, err
} else if user != nil && credentials.ComparePassword(user.PasswordHash, []byte(password)) {
return user, nil
} else {
return nil, errors.New("credentials were invalid")
}
return nil, errors.New("credentials were invalid")
}
return nil, errors.New("no credentials were supplied")
@ -60,7 +61,7 @@ func (a *Authenticator) requireUserProperty(has hasUserProperty) gin.HandlerFunc
// RequireUser returns a Gin middleware which requires valid user credentials to be supplied with the request.
func (a *Authenticator) RequireUser() gin.HandlerFunc {
return a.requireUserProperty(func(user *model.User) bool {
return a.requireUserProperty(func(_ *model.User) bool {
return true
})
}

View file

@ -13,7 +13,7 @@ import (
func GetApplication(ctx *gin.Context) *model.Application {
app, ok := ctx.MustGet("app").(*model.Application)
if app == nil || !ok {
ctx.AbortWithError(http.StatusInternalServerError, errors.New("an error occured while retrieving application from context"))
ctx.AbortWithError(http.StatusInternalServerError, errors.New("an error occurred while retrieving application from context"))
return nil
}
@ -24,7 +24,7 @@ func GetApplication(ctx *gin.Context) *model.Application {
func GetUser(ctx *gin.Context) *model.User {
user, ok := ctx.MustGet("user").(*model.User)
if user == nil || !ok {
ctx.AbortWithError(http.StatusInternalServerError, errors.New("an error occured while retrieving user from context"))
ctx.AbortWithError(http.StatusInternalServerError, errors.New("an error occurred while retrieving user from context"))
return nil
}

View file

@ -1,3 +1,4 @@
// Package credentials provides definitions and functionality related to credential management.
package credentials
import (

View file

@ -3,7 +3,7 @@ package credentials
import (
"crypto/sha1" //#nosec G505 -- False positive, see the use below.
"fmt"
"io/ioutil"
"io"
"net/http"
"strings"
@ -22,7 +22,6 @@ func IsPasswordPwned(password string) (bool, error) {
return true, nil
}
// nosemgrep: tests.semgrep-rules.go.lang.security.audit.crypto.insecure-module-used, tests.semgrep-rules.go.lang.security.audit.crypto.use-of-sha1
hash := sha1.Sum([]byte(password)) //#nosec G401 -- False positive, only the first 5 bytes are transmitted.
hashStr := fmt.Sprintf("%X", hash)
lookup := hashStr[0:5]
@ -34,17 +33,24 @@ func IsPasswordPwned(password string) (bool, error) {
if err != nil {
return false, err
}
if resp == nil {
return false, fmt.Errorf("received nil response from http request")
}
if resp.StatusCode != http.StatusOK {
log.L.Fatalf("Request failed with HTTP %s.", resp.Status)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.L.Fatal(err)
}
err = resp.Body.Close()
if err != nil {
log.L.Warnf("Failed to close file: %s.", err)
}
bodyStr := string(bodyText)
lines := strings.Split(bodyStr, "\n")

View file

@ -13,9 +13,9 @@ var (
)
func randIntn(n int) int {
max := big.NewInt(int64(n))
maxValue := big.NewInt(int64(n))
res, err := rand.Int(rand.Reader, max)
res, err := rand.Int(rand.Reader, maxValue)
if err != nil {
panic("random source is not available")
}

View file

@ -11,7 +11,7 @@ const (
minRandomChars = 14
)
func isGoodToken(assert *assert.Assertions, require *require.Assertions, token string, compat bool) {
func isGoodToken(assert *assert.Assertions, _ *require.Assertions, token string, compat bool) {
tokenLength := len(token)
if compat {

View file

@ -1,7 +1,11 @@
// Package configuration provides definitions and functionality related to the configuration.
package configuration
import (
"github.com/jinzhu/configor"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/pberrors"
)
// testMode indicates if the package is run in test mode
@ -39,6 +43,12 @@ type Alertmanager struct {
AnnotationMessage string `default:"message"`
}
// RepairBehavior holds information on how repair applications.
type RepairBehavior struct {
ResetRoomName bool `default:"true"`
ResetRoomTopic bool `default:"true"`
}
// Configuration holds values that can be configured by the user.
type Configuration struct {
Debug bool `default:"false"`
@ -46,6 +56,8 @@ type Configuration struct {
ListenAddress string `default:""`
Port int `default:"8080"`
TrustedProxies []string `default:"[]"`
CertFile string `default:""`
KeyFile string `default:""`
}
Database struct {
Dialect string `default:"sqlite3"`
@ -60,9 +72,10 @@ type Configuration struct {
Security struct {
CheckHIBP bool `default:"false"`
}
Crypto CryptoConfig
Formatting Formatting
Alertmanager Alertmanager
Crypto CryptoConfig
Formatting Formatting
Alertmanager Alertmanager
RepairBehavior RepairBehavior
}
func configFiles() []string {
@ -72,6 +85,21 @@ func configFiles() []string {
return []string{"config.yml"}
}
func validateHTTPConfiguration(c *Configuration) error {
certAndKeyEmpty := (c.HTTP.CertFile == "" && c.HTTP.KeyFile == "")
certAndKeyPopulated := (c.HTTP.CertFile != "" && c.HTTP.KeyFile != "")
if !certAndKeyEmpty && !certAndKeyPopulated {
return pberrors.ErrConfigTLSFilesInconsistent
}
return nil
}
func validateConfiguration(c *Configuration) error {
return validateHTTPConfiguration(c)
}
// Get returns the configuration extracted from env variables or config file.
func Get() *Configuration {
config := &Configuration{}
@ -85,5 +113,9 @@ func Get() *Configuration {
panic(err)
}
if err := validateConfiguration(config); err != nil {
log.L.Fatal(err)
}
return config
}

View file

@ -2,7 +2,6 @@ package configuration
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
@ -10,6 +9,9 @@ import (
"github.com/jinzhu/configor"
"github.com/stretchr/testify/assert"
"gopkg.in/yaml.v2"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/pberrors"
)
type Pair struct {
@ -27,8 +29,7 @@ func TestMain(m *testing.M) {
func TestConfiguration_GetMinimal(t *testing.T) {
err := writeMinimalConfig()
if err != nil {
fmt.Println("Could not write minimal config: ", err)
os.Exit(1)
log.L.Fatalln("Cannot write minimal config file: ", err)
}
validateConfig(t)
@ -39,8 +40,7 @@ func TestConfiguration_GetValid(t *testing.T) {
err := writeValidConfig()
if err != nil {
fmt.Println("Could not write valid config: ", err)
os.Exit(1)
log.L.Fatalln("Cannot write valid config file: ", err)
}
validateConfig(t)
@ -64,17 +64,17 @@ func TestConfiguration_GetEmpty(t *testing.T) {
os.Exit(1)
}
assert.Panicsf(t, func() { Get() }, "Get() did not panic altough config is empty")
assert.Panicsf(t, func() { Get() }, "Get() did not panic although config is empty")
}
func TestConfiguration_GetInvalid(t *testing.T) {
err := writeInvalidConfig()
if err != nil {
fmt.Println("Could not write empty config: ", err)
fmt.Println("Could not write invalid config: ", err)
os.Exit(1)
}
assert.Panicsf(t, func() { Get() }, "Get() did not panic altough config is empty")
assert.Panicsf(t, func() { Get() }, "Get() did not panic although config is empty")
}
func TestConfiguaration_ConfigFiles(t *testing.T) {
@ -136,6 +136,7 @@ type InvalidConfiguration struct {
// Writes a minimal config to config.yml
func writeMinimalConfig() error {
cleanUp()
config := MinimalConfiguration{}
config.Admin.MatrixID = "000000"
config.Matrix.Username = "default-username"
@ -146,17 +147,26 @@ func writeMinimalConfig() error {
return err
}
return ioutil.WriteFile("config_unittest.yml", configString, 0o644)
err = os.WriteFile("config_unittest.yml", configString, 0o644)
if err != nil {
return err
}
return nil
}
// Writes a config with default values to config.yml
func writeValidConfig() error {
cleanUp()
err := writeMinimalConfig()
if err != nil {
return err
}
// Load minimal config to get default values
writeMinimalConfig()
config := &Configuration{}
err := configor.New(&configor.Config{
err = configor.New(&configor.Config{
Environment: "production",
ENVPrefix: "PUSHBITS",
ErrorOnUnmatchedKeys: true,
@ -174,18 +184,30 @@ func writeValidConfig() error {
return err
}
return ioutil.WriteFile("config_unittest.yml", configString, 0o644)
err = os.WriteFile("config_unittest.yml", configString, 0o644)
if err != nil {
return err
}
return nil
}
// Writes a config that is empty
func writeEmptyConfig() error {
cleanUp()
return ioutil.WriteFile("config_unittest.yml", []byte(""), 0o644)
err := os.WriteFile("config_unittest.yml", []byte(""), 0o644)
if err != nil {
return err
}
return nil
}
// Writes a config with invalid entries
func writeInvalidConfig() error {
cleanUp()
config := InvalidConfiguration{}
config.Debug = 1337
config.HTTP.ListenAddress = true
@ -197,9 +219,32 @@ func writeInvalidConfig() error {
return err
}
return ioutil.WriteFile("config_unittest.yml", configString, 0o644)
err = os.WriteFile("config_unittest.yml", configString, 0o644)
if err != nil {
return err
}
return nil
}
func cleanUp() error {
return os.Remove("config_unittest.yml")
func cleanUp() {
err := os.Remove("config_unittest.yml")
if err != nil {
log.L.Warnln("Cannot remove config file: ", err)
}
}
func TestConfigurationValidation_ConfigTLSFilesInconsistent(t *testing.T) {
assert := assert.New(t)
c := Configuration{}
c.Admin.MatrixID = "000000"
c.Matrix.Username = "default-username"
c.Matrix.Password = "default-password"
c.HTTP.CertFile = "populated"
c.HTTP.KeyFile = ""
is := validateConfiguration(&c)
should := pberrors.ErrConfigTLSFilesInconsistent
assert.Equal(is, should, "validateConfiguration() should return ConfigTLSFilesInconsistent")
}

View file

@ -25,16 +25,16 @@ func (d *Database) UpdateApplication(application *model.Application) error {
}
// GetApplicationByID returns the application with the given ID or nil.
func (d *Database) GetApplicationByID(ID uint) (*model.Application, error) {
func (d *Database) GetApplicationByID(id uint) (*model.Application, error) {
var application model.Application
err := d.gormdb.First(&application, ID).Error
err := d.gormdb.First(&application, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
assert.Assert(application.ID == ID)
assert.Assert(application.ID == id)
return &application, err
}

View file

@ -1,3 +1,4 @@
// Package database provides definitions and functionality related to the database.
package database
import (
@ -8,10 +9,12 @@ import (
"time"
"github.com/pushbits/server/internal/authentication/credentials"
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
@ -27,7 +30,6 @@ func createFileDir(file string) {
dir := filepath.Dir(file)
if _, err := os.Stat(dir); os.IsNotExist(err) {
// nosemgrep: tests.semgrep-rules.go.lang.correctness.permissions.incorrect-default-permission
if err := os.MkdirAll(dir, 0o750); err != nil {
panic(err)
}
@ -50,6 +52,8 @@ func Create(cm *credentials.Manager, dialect, connection string) (*Database, err
db, err = gorm.Open(sqlite.Open(connection), &gorm.Config{})
case "mysql":
db, err = gorm.Open(mysql.Open(connection), &gorm.Config{})
case "postgres":
db, err = gorm.Open(postgres.Open(connection), &gorm.Config{})
default:
message := "Database dialect is not supported"
return nil, errors.New(message)
@ -111,7 +115,7 @@ func (d *Database) Populate(name, password, matrixID string) error {
}
// RepairChannels resets channels that have been modified by a user.
func (d *Database) RepairChannels(dp Dispatcher) error {
func (d *Database) RepairChannels(dp Dispatcher, behavior *configuration.RepairBehavior) error {
log.L.Print("Repairing application channels.")
users, err := d.GetUsers()
@ -130,7 +134,7 @@ func (d *Database) RepairChannels(dp Dispatcher) error {
for _, application := range applications {
application := application // See https://stackoverflow.com/a/68247837
if err := dp.UpdateApplication(&application); err != nil {
if err := dp.UpdateApplication(&application, behavior); err != nil {
return err
}

View file

@ -1,13 +1,14 @@
package database
import (
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/model"
)
// The Dispatcher interface for constructing and destructing channels.
type Dispatcher interface {
DeregisterApplication(a *model.Application, u *model.User) error
UpdateApplication(a *model.Application) error
UpdateApplication(a *model.Application, behavior *configuration.RepairBehavior) error
IsOrphan(a *model.Application, u *model.User) (bool, error)
RepairApplication(a *model.Application, u *model.User) error
}

View file

@ -34,16 +34,16 @@ func (d *Database) UpdateUser(user *model.User) error {
}
// GetUserByID returns the user with the given ID or nil.
func (d *Database) GetUserByID(ID uint) (*model.User, error) {
func (d *Database) GetUserByID(id uint) (*model.User, error) {
var user model.User
err := d.gormdb.First(&user, ID).Error
err := d.gormdb.First(&user, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
assert.Assert(user.ID == ID)
assert.Assert(user.ID == id)
return &user, err
}

View file

@ -1,8 +1,10 @@
package dispatcher
import (
"context"
"fmt"
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/log"
"github.com/pushbits/server/internal/model"
@ -16,10 +18,10 @@ func buildRoomTopic(id uint) string {
}
// RegisterApplication creates a channel for an application.
func (d *Dispatcher) RegisterApplication(id uint, name, token, user string) (string, error) {
func (d *Dispatcher) RegisterApplication(id uint, name, user string) (string, error) {
log.L.Printf("Registering application %s, notifications will be relayed to user %s.\n", name, user)
resp, err := d.mautrixClient.CreateRoom(&mautrix.ReqCreateRoom{
resp, err := d.mautrixClient.CreateRoom(context.Background(), &mautrix.ReqCreateRoom{
Visibility: "private",
Invite: []mId.UserID{mId.UserID(user)},
IsDirect: true,
@ -43,7 +45,7 @@ func (d *Dispatcher) DeregisterApplication(a *model.Application, u *model.User)
// The user might have left the channel, but we can still try to remove them.
if _, err := d.mautrixClient.KickUser(mId.RoomID(a.MatrixID), &mautrix.ReqKickUser{
if _, err := d.mautrixClient.KickUser(context.Background(), mId.RoomID(a.MatrixID), &mautrix.ReqKickUser{
Reason: "This application was deleted",
UserID: mId.UserID(u.MatrixID),
}); err != nil {
@ -51,12 +53,12 @@ func (d *Dispatcher) DeregisterApplication(a *model.Application, u *model.User)
return err
}
if _, err := d.mautrixClient.LeaveRoom(mId.RoomID(a.MatrixID)); err != nil {
if _, err := d.mautrixClient.LeaveRoom(context.Background(), mId.RoomID(a.MatrixID)); err != nil {
log.L.Print(err)
return err
}
if _, err := d.mautrixClient.ForgetRoom(mId.RoomID(a.MatrixID)); err != nil {
if _, err := d.mautrixClient.ForgetRoom(context.Background(), mId.RoomID(a.MatrixID)); err != nil {
log.L.Print(err)
return err
}
@ -65,7 +67,7 @@ func (d *Dispatcher) DeregisterApplication(a *model.Application, u *model.User)
}
func (d *Dispatcher) sendRoomEvent(roomID, eventType string, content interface{}) error {
if _, err := d.mautrixClient.SendStateEvent(mId.RoomID(roomID), event.NewEventType(eventType), "", content); err != nil {
if _, err := d.mautrixClient.SendStateEvent(context.Background(), mId.RoomID(roomID), event.NewEventType(eventType), "", content); err != nil {
log.L.Print(err)
return err
}
@ -74,23 +76,31 @@ func (d *Dispatcher) sendRoomEvent(roomID, eventType string, content interface{}
}
// UpdateApplication updates a channel for an application.
func (d *Dispatcher) UpdateApplication(a *model.Application) error {
func (d *Dispatcher) UpdateApplication(a *model.Application, behavior *configuration.RepairBehavior) error {
log.L.Printf("Updating application %s (ID %d) with Matrix ID %s.\n", a.Name, a.ID, a.MatrixID)
content := map[string]interface{}{
"name": a.Name,
if behavior.ResetRoomName {
content := map[string]interface{}{
"name": a.Name,
}
if err := d.sendRoomEvent(a.MatrixID, "m.room.name", content); err != nil {
return err
}
} else {
log.L.Debugf("Not reseting room name as per configuration.\n")
}
if err := d.sendRoomEvent(a.MatrixID, "m.room.name", content); err != nil {
return err
}
if behavior.ResetRoomTopic {
content := map[string]interface{}{
"topic": buildRoomTopic(a.ID),
}
content = map[string]interface{}{
"topic": buildRoomTopic(a.ID),
}
if err := d.sendRoomEvent(a.MatrixID, "m.room.topic", content); err != nil {
return err
if err := d.sendRoomEvent(a.MatrixID, "m.room.topic", content); err != nil {
return err
}
} else {
log.L.Debugf("Not reseting room topic as per configuration.\n")
}
return nil
@ -98,7 +108,7 @@ func (d *Dispatcher) UpdateApplication(a *model.Application) error {
// IsOrphan checks if the user is still connected to the channel.
func (d *Dispatcher) IsOrphan(a *model.Application, u *model.User) (bool, error) {
resp, err := d.mautrixClient.JoinedMembers(mId.RoomID(a.MatrixID))
resp, err := d.mautrixClient.JoinedMembers(context.Background(), mId.RoomID(a.MatrixID))
if err != nil {
return false, err
}
@ -114,7 +124,7 @@ func (d *Dispatcher) IsOrphan(a *model.Application, u *model.User) (bool, error)
// RepairApplication re-invites the user to the channel.
func (d *Dispatcher) RepairApplication(a *model.Application, u *model.User) error {
_, err := d.mautrixClient.InviteUser(mId.RoomID(a.MatrixID), &mautrix.ReqInviteUser{
_, err := d.mautrixClient.InviteUser(context.Background(), mId.RoomID(a.MatrixID), &mautrix.ReqInviteUser{
UserID: mId.UserID(u.MatrixID),
})
if err != nil {

View file

@ -1,6 +1,9 @@
// Package dispatcher provides definitions and functionality related to executing Matrix requests.
package dispatcher
import (
"context"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/id"
@ -23,7 +26,7 @@ func Create(homeserver, username, password string, formatting configuration.Form
return nil, err
}
_, err = matrixClient.Login(&mautrix.ReqLogin{
_, err = matrixClient.Login(context.Background(), &mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: username},
Password: password,
@ -41,7 +44,7 @@ func Create(homeserver, username, password string, formatting configuration.Form
func (d *Dispatcher) Close() {
log.L.Printf("Logging out.")
_, err := d.mautrixClient.Logout()
_, err := d.mautrixClient.Logout(context.Background())
if err != nil {
log.L.Printf("Error while logging out: %s", err)
}

View file

@ -1,6 +1,7 @@
package dispatcher
import (
"context"
"fmt"
"html"
"strings"
@ -52,8 +53,8 @@ type NewContent struct {
Format MessageFormat `json:"format"`
}
// SendNotification sends a notification to the specified user.
func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) (eventId string, err error) {
// SendNotification sends a notification to a given user.
func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notification) (eventID string, err error) {
log.L.Printf("Sending notification to room %s.", a.MatrixID)
plainMessage := strings.TrimSpace(n.Message)
@ -71,7 +72,7 @@ func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notificatio
Format: MessageFormatHTML,
}
evt, err := d.mautrixClient.SendMessageEvent(mId.RoomID(a.MatrixID), event.EventMessage, &messageEvent)
evt, err := d.mautrixClient.SendMessageEvent(context.Background(), mId.RoomID(a.MatrixID), event.EventMessage, &messageEvent)
if err != nil {
log.L.Errorln(err)
return "", err
@ -80,7 +81,7 @@ func (d *Dispatcher) SendNotification(a *model.Application, n *model.Notificatio
return evt.EventID.String(), nil
}
// DeleteNotification sends a notification to the specified user that another notificaion is deleted
// DeleteNotification sends a notification to a given user that another notification is deleted
func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNotification) error {
log.L.Printf("Sending delete notification to room %s", a.MatrixID)
var oldFormattedBody string
@ -90,7 +91,7 @@ func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNot
deleteMessage, err := d.getMessage(a, n.ID)
if err != nil {
log.L.Println(err)
return pberrors.ErrorMessageNotFound
return pberrors.ErrMessageNotFound
}
oldBody, oldFormattedBody, err = bodiesFromMessage(deleteMessage)
@ -103,7 +104,6 @@ func (d *Dispatcher) DeleteNotification(a *model.Application, n *model.DeleteNot
newFormattedBody := fmt.Sprintf("<del>%s</del><br>- deleted", oldFormattedBody)
_, err = d.replaceMessage(a, newBody, newFormattedBody, deleteMessage.ID.String(), oldBody, oldFormattedBody)
if err != nil {
return err
}
@ -128,7 +128,7 @@ func (d *Dispatcher) getFormattedTitle(n *model.Notification) string {
// Converts different syntaxes to a HTML-formatted message
func (d *Dispatcher) getFormattedMessage(n *model.Notification) string {
trimmedMessage := strings.TrimSpace(n.Message)
message := strings.Replace(html.EscapeString(trimmedMessage), "\n", "<br />", -1) // default to text/plain
message := strings.ReplaceAll(html.EscapeString(trimmedMessage), "\n", "<br />") // default to text/plain
if optionsDisplayRaw, ok := n.Extras["client::display"]; ok {
optionsDisplay, ok := optionsDisplayRaw.(map[string]interface{})
@ -140,7 +140,7 @@ func (d *Dispatcher) getFormattedMessage(n *model.Notification) string {
switch contentType {
case "html", "text/html":
message = strings.Replace(trimmedMessage, "\n", "<br />", -1)
message = strings.ReplaceAll(trimmedMessage, "\n", "<br />")
case "markdown", "md", "text/md", "text/markdown":
// Allow HTML in Markdown
message = string(markdown.ToHTML([]byte(trimmedMessage), nil, nil))
@ -186,7 +186,7 @@ func (d *Dispatcher) getMessage(a *model.Application, id string) (*event.Event,
maxPages := 10 // Maximum pages to request (10 messages per page)
for i := 0; i < maxPages; i++ {
messages, err := d.mautrixClient.Messages(mId.RoomID(a.MatrixID), start, end, 'b', nil, 10)
messages, err := d.mautrixClient.Messages(context.Background(), mId.RoomID(a.MatrixID), start, end, 'b', nil, 10)
if err != nil {
return nil, err
}
@ -199,7 +199,7 @@ func (d *Dispatcher) getMessage(a *model.Application, id string) (*event.Event,
start = messages.End
}
return nil, pberrors.ErrorMessageNotFound
return nil, pberrors.ErrMessageNotFound
}
// Replaces the content of a matrix message
@ -225,7 +225,7 @@ func (d *Dispatcher) replaceMessage(a *model.Application, newBody, newFormattedB
Format: MessageFormatHTML,
}
sendEvent, err := d.mautrixClient.SendMessageEvent(mId.RoomID(a.MatrixID), event.EventMessage, &replaceEvent)
sendEvent, err := d.mautrixClient.SendMessageEvent(context.Background(), mId.RoomID(a.MatrixID), event.EventMessage, &replaceEvent)
if err != nil {
log.L.Errorln(err)
return nil, err
@ -260,7 +260,7 @@ func (d *Dispatcher) respondToMessage(a *model.Application, body, formattedBody
}
notificationEvent.RelatesTo = &notificationRelation
sendEvent, err := d.mautrixClient.SendMessageEvent(mId.RoomID(a.MatrixID), event.EventMessage, &notificationEvent)
sendEvent, err := d.mautrixClient.SendMessageEvent(context.Background(), mId.RoomID(a.MatrixID), event.EventMessage, &notificationEvent)
if err != nil {
log.L.Errorln(err)
return nil, err
@ -273,7 +273,7 @@ func (d *Dispatcher) respondToMessage(a *model.Application, body, formattedBody
func bodiesFromMessage(message *event.Event) (body, formattedBody string, err error) {
msgContent := message.Content.AsMessage()
if msgContent == nil {
return "", "", pberrors.ErrorMessageNotFound
return "", "", pberrors.ErrMessageNotFound
}
formattedBody = msgContent.Body

View file

@ -1,5 +1,6 @@
// Source: https://github.com/toorop/gin-logrus
// Package log provides a connector between gin and logrus.
package log
import (

View file

@ -1,3 +1,4 @@
// Package log provides functionality to configure the logger.
package log
import (
@ -6,6 +7,7 @@ import (
log "github.com/sirupsen/logrus"
)
// L is the global logger instance for PushBits.
var L *log.Logger
func init() {
@ -17,6 +19,7 @@ func init() {
})
}
// SetDebug sets the logger to output debug information.
func SetDebug() {
L.SetLevel(log.DebugLevel)
}

View file

@ -2,6 +2,7 @@ package model
import "strings"
// AlertmanagerWebhook is used to pass notifications over webhook pushes.
type AlertmanagerWebhook struct {
Version string `json:"version"`
GroupKey string `json:"groupKey"`
@ -13,6 +14,7 @@ type AlertmanagerWebhook struct {
Alerts []AlertmanagerAlert `json:"alerts"`
}
// AlertmanagerAlert holds information related to a single alert in a notification.
type AlertmanagerAlert struct {
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
@ -21,6 +23,7 @@ type AlertmanagerAlert struct {
Status string `json:"status"`
}
// ToNotification converts an Alertmanager alert into a Notification
func (alert *AlertmanagerAlert) ToNotification(titleAnnotation, messageAnnotation string) Notification {
title := strings.Builder{}
message := strings.Builder{}

View file

@ -1,3 +1,4 @@
// Package model contains structs used in the PushBits API and across the application.
package model
import (
@ -8,7 +9,7 @@ import (
// Notification holds information like the message, the title, and the priority of a notification.
type Notification struct {
ID string `json:"id"`
UrlEncodedID string `json:"id_url_encoded"`
URLEncodedID string `json:"id_url_encoded"`
ApplicationID uint `json:"appid"`
Message string `json:"message" form:"message" query:"message" binding:"required"`
Title string `json:"title" form:"title" query:"title"`
@ -20,7 +21,7 @@ type Notification struct {
// Sanitize sets explicit defaults for a notification.
func (n *Notification) Sanitize(application *Application) {
n.ID = ""
n.UrlEncodedID = ""
n.URLEncodedID = ""
n.ApplicationID = application.ID
if strings.TrimSpace(n.Title) == "" {
n.Title = application.Name

View file

@ -1,6 +1,10 @@
// Package pberrors defines errors specific to PushBits
package pberrors
import "errors"
// ErrorMessageNotFound indicates that a message does not exist
var ErrorMessageNotFound = errors.New("message not found")
// ErrMessageNotFound indicates that a message does not exist
var ErrMessageNotFound = errors.New("message not found")
// ErrConfigTLSFilesInconsistent indicates that either just a certfile or a keyfile was provided
var ErrConfigTLSFilesInconsistent = errors.New("TLS certfile and keyfile must either both be provided or omitted")

View file

@ -1,3 +1,4 @@
// Package router provides functions to configure the web server.
package router
import (
@ -28,7 +29,7 @@ func Create(debug bool, trustedProxies []string, cm *credentials.Manager, db *da
healthHandler := api.HealthHandler{DB: db}
notificationHandler := api.NotificationHandler{DB: db, DP: dp}
userHandler := api.UserHandler{AH: &applicationHandler, CM: cm, DB: db, DP: dp}
alertmanagerHandler := alertmanager.AlertmanagerHandler{DP: dp, Settings: alertmanager.AlertmanagerHandlerSettings{
alertmanagerHandler := alertmanager.Handler{DP: dp, Settings: alertmanager.HandlerSettings{
TitleAnnotation: alertmanagerConfig.AnnotationTitle,
MessageAnnotation: alertmanagerConfig.AnnotationMessage,
}}

View file

@ -1,17 +1,24 @@
// Package runner provides functions to run the web server.
package runner
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/pushbits/server/internal/configuration"
)
// Run starts the Gin engine.
func Run(engine *gin.Engine, address string, port int) error {
err := engine.Run(fmt.Sprintf("%s:%d", address, port))
if err != nil {
return err
func Run(engine *gin.Engine, c *configuration.Configuration) error {
var err error
address := fmt.Sprintf("%s:%d", c.HTTP.ListenAddress, c.HTTP.Port)
if c.HTTP.CertFile != "" && c.HTTP.KeyFile != "" {
err = engine.RunTLS(address, c.HTTP.CertFile, c.HTTP.KeyFile)
} else {
err = engine.Run(address)
}
return nil
return err
}

439
poetry.lock generated
View file

@ -1,439 +0,0 @@
[[package]]
name = "attrs"
version = "21.4.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "bracex"
version = "2.2.1"
description = "Bash style brace expander."
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "certifi"
version = "2021.10.8"
description = "Python package for providing Mozilla's CA Bundle."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "charset-normalizer"
version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "dev"
optional = false
python-versions = ">=3.5.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.0.3"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "click-option-group"
version = "0.5.3"
description = "Option groups missing in Click"
category = "dev"
optional = false
python-versions = ">=3.6,<4"
[package.dependencies]
Click = ">=7.0,<9"
[package.extras]
docs = ["sphinx (>=2.3,<3)", "pallets-sphinx-themes", "m2r"]
tests = ["coverage (<6)", "pytest", "pytest-cov", "coveralls"]
[[package]]
name = "colorama"
version = "0.4.4"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "dev"
optional = false
python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "4.11.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
perf = ["ipython"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"]
[[package]]
name = "importlib-resources"
version = "5.4.0"
description = "Read resources from Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"]
[[package]]
name = "jsonschema"
version = "4.4.0"
description = "An implementation of JSON Schema validation for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
attrs = ">=17.4.0"
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""}
pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"]
format_nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"]
[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "peewee"
version = "3.14.8"
description = "a little orm"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "pyparsing"
version = "3.0.7"
description = "Python parsing module"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "pyrsistent"
version = "0.18.1"
description = "Persistent/Functional/Immutable data structures"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "requests"
version = "2.27.1"
description = "Python HTTP for Humans."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
[[package]]
name = "ruamel.yaml"
version = "0.17.21"
description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
category = "dev"
optional = false
python-versions = ">=3"
[package.dependencies]
"ruamel.yaml.clib" = {version = ">=0.2.6", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.11\""}
[package.extras]
docs = ["ryd"]
jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"]
[[package]]
name = "ruamel.yaml.clib"
version = "0.2.6"
description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
category = "dev"
optional = false
python-versions = ">=3.5"
[[package]]
name = "semgrep"
version = "0.82.0"
description = "Lightweight static analysis for many languages. Find bug variants with patterns that look like source code."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
attrs = ">=19.3.0"
click = ">=8.0.1"
click-option-group = ">=0.5.3"
colorama = ">=0.4.3"
jsonschema = ">=3.2.0,<5"
packaging = ">=20.4"
peewee = ">=3.14.4,<3.15.0"
requests = ">=2.22.0"
"ruamel.yaml" = ">=0.16.0,<0.18"
tqdm = ">=4.46.1"
wcmatch = "8.3"
[[package]]
name = "tqdm"
version = "4.62.3"
description = "Fast, Extensible Progress Meter"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[package.extras]
dev = ["py-make (>=0.1.0)", "twine", "wheel"]
notebook = ["ipywidgets (>=6)"]
telegram = ["requests"]
[[package]]
name = "typing-extensions"
version = "4.1.0"
description = "Backported and Experimental Type Hints for Python 3.6+"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "urllib3"
version = "1.26.8"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "wcmatch"
version = "8.3"
description = "Wildcard/glob file name matcher."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
bracex = ">=2.1.1"
[[package]]
name = "zipp"
version = "3.7.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "792ec3aa3a1641a70a6cd82c2399211c85c8a5cad5522f260c86ef407f8482ce"
[metadata.files]
attrs = [
{file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"},
{file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"},
]
bracex = [
{file = "bracex-2.2.1-py3-none-any.whl", hash = "sha256:096c4b788bf492f7af4e90ef8b5bcbfb99759ae3415ea1b83c9d29a5ed8f9a94"},
{file = "bracex-2.2.1.tar.gz", hash = "sha256:1c8d1296e00ad9a91030ccb4c291f9e4dc7c054f12c707ba3c5ff3e9a81bcd21"},
]
certifi = [
{file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"},
{file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"},
]
charset-normalizer = [
{file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
{file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
click = [
{file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"},
{file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"},
]
click-option-group = [
{file = "click-option-group-0.5.3.tar.gz", hash = "sha256:a6e924f3c46b657feb5b72679f7e930f8e5b224b766ab35c91ae4019b4e0615e"},
{file = "click_option_group-0.5.3-py3-none-any.whl", hash = "sha256:9653a2297357335d7325a1827e71ac1245d91c97d959346a7decabd4a52d5354"},
]
colorama = [
{file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"},
{file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
importlib-metadata = [
{file = "importlib_metadata-4.11.0-py3-none-any.whl", hash = "sha256:6affcdb3aec542dd98df8211e730bba6c5f2bec8288d47bacacde898f548c9ad"},
{file = "importlib_metadata-4.11.0.tar.gz", hash = "sha256:9e5e553bbba1843cb4a00823014b907616be46ee503d2b9ba001d214a8da218f"},
]
importlib-resources = [
{file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"},
{file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"},
]
jsonschema = [
{file = "jsonschema-4.4.0-py3-none-any.whl", hash = "sha256:77281a1f71684953ee8b3d488371b162419767973789272434bbc3f29d9c8823"},
{file = "jsonschema-4.4.0.tar.gz", hash = "sha256:636694eb41b3535ed608fe04129f26542b59ed99808b4f688aa32dcf55317a83"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
peewee = [
{file = "peewee-3.14.8.tar.gz", hash = "sha256:01bd7f734defb08d7a3346a0c0ca7011bc8d0d685934ec0e001b3371d522ec53"},
]
pyparsing = [
{file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"},
{file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"},
]
pyrsistent = [
{file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"},
{file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"},
{file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"},
{file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"},
{file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"},
{file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"},
{file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"},
{file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"},
{file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"},
{file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"},
{file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"},
{file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"},
{file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"},
{file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"},
{file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"},
{file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"},
{file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"},
{file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"},
{file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"},
{file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"},
{file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"},
]
requests = [
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
]
"ruamel.yaml" = [
{file = "ruamel.yaml-0.17.21-py3-none-any.whl", hash = "sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7"},
{file = "ruamel.yaml-0.17.21.tar.gz", hash = "sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af"},
]
"ruamel.yaml.clib" = [
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6e7be2c5bcb297f5b82fee9c665eb2eb7001d1050deaba8471842979293a80b0"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:221eca6f35076c6ae472a531afa1c223b9c29377e62936f61bc8e6e8bdc5f9e7"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win32.whl", hash = "sha256:1070ba9dd7f9370d0513d649420c3b362ac2d687fe78c6e888f5b12bf8bc7bee"},
{file = "ruamel.yaml.clib-0.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:77df077d32921ad46f34816a9a16e6356d8100374579bc35e15bab5d4e9377de"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:cfdb9389d888c5b74af297e51ce357b800dd844898af9d4a547ffc143fa56751"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7b2927e92feb51d830f531de4ccb11b320255ee95e791022555971c466af4527"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win32.whl", hash = "sha256:ada3f400d9923a190ea8b59c8f60680c4ef8a4b0dfae134d2f2ff68429adfab5"},
{file = "ruamel.yaml.clib-0.2.6-cp35-cp35m-win_amd64.whl", hash = "sha256:de9c6b8a1ba52919ae919f3ae96abb72b994dd0350226e28f3686cb4f142165c"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d67f273097c368265a7b81e152e07fb90ed395df6e552b9fa858c6d2c9f42502"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:72a2b8b2ff0a627496aad76f37a652bcef400fd861721744201ef1b45199ab78"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win32.whl", hash = "sha256:9efef4aab5353387b07f6b22ace0867032b900d8e91674b5d8ea9150db5cae94"},
{file = "ruamel.yaml.clib-0.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:846fc8336443106fe23f9b6d6b8c14a53d38cef9a375149d61f99d78782ea468"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0847201b767447fc33b9c235780d3aa90357d20dd6108b92be544427bea197dd"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:78988ed190206672da0f5d50c61afef8f67daa718d614377dcd5e3ed85ab4a99"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win32.whl", hash = "sha256:a49e0161897901d1ac9c4a79984b8410f450565bbad64dbfcbf76152743a0cdb"},
{file = "ruamel.yaml.clib-0.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:bf75d28fa071645c529b5474a550a44686821decebdd00e21127ef1fd566eabe"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a32f8d81ea0c6173ab1b3da956869114cae53ba1e9f72374032e33ba3118c233"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7f7ecb53ae6848f959db6ae93bdff1740e651809780822270eab111500842a84"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win32.whl", hash = "sha256:89221ec6d6026f8ae859c09b9718799fea22c0e8da8b766b0b2c9a9ba2db326b"},
{file = "ruamel.yaml.clib-0.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:31ea73e564a7b5fbbe8188ab8b334393e06d997914a4e184975348f204790277"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc6a613d6c74eef5a14a214d433d06291526145431c3b964f5e16529b1842bed"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:1866cf2c284a03b9524a5cc00daca56d80057c5ce3cdc86a52020f4c720856f0"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win32.whl", hash = "sha256:3fb9575a5acd13031c57a62cc7823e5d2ff8bc3835ba4d94b921b4e6ee664104"},
{file = "ruamel.yaml.clib-0.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:825d5fccef6da42f3c8eccd4281af399f21c02b32d98e113dbc631ea6a6ecbc7"},
{file = "ruamel.yaml.clib-0.2.6.tar.gz", hash = "sha256:4ff604ce439abb20794f05613c374759ce10e3595d1867764dd1ae675b85acbd"},
]
semgrep = [
{file = "semgrep-0.82.0-cp37.cp38.cp39.py37.py38.py39-none-any.whl", hash = "sha256:1750f2b755ffc7db4690e6c5990e03e03729e6de4d4c4edf3f39435020244989"},
{file = "semgrep-0.82.0-cp37.cp38.cp39.py37.py38.py39-none-macosx_10_14_x86_64.whl", hash = "sha256:037124db822a0c9235ea6cfbc637f06771ca2882d181dfdbd460c430bf51d7ec"},
{file = "semgrep-0.82.0.tar.gz", hash = "sha256:aa6c57d08602ffde3bf36159c0325d6d72f70dc634cd237f30ff46f7d6b78be3"},
]
tqdm = [
{file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"},
{file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"},
]
typing-extensions = [
{file = "typing_extensions-4.1.0-py3-none-any.whl", hash = "sha256:c13180fbaa7cd97065a4915ceba012bdb31dc34743e63ddee16360161d358414"},
{file = "typing_extensions-4.1.0.tar.gz", hash = "sha256:ba97c5143e5bb067b57793c726dd857b1671d4b02ced273ca0538e71ff009095"},
]
urllib3 = [
{file = "urllib3-1.26.8-py2.py3-none-any.whl", hash = "sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed"},
{file = "urllib3-1.26.8.tar.gz", hash = "sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c"},
]
wcmatch = [
{file = "wcmatch-8.3-py3-none-any.whl", hash = "sha256:7141d2c85314253f16b38cb3d6cc0fb612918d407e1df3ccc2be7c86cc259c22"},
{file = "wcmatch-8.3.tar.gz", hash = "sha256:371072912398af61d1e4e78609e18801c6faecd3cb36c54c82556a60abc965db"},
]
zipp = [
{file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"},
{file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"},
]

3
postgres-debug.env Normal file
View file

@ -0,0 +1,3 @@
POSTGRES_PASSWORD=pushbits
POSTGRES_USER=pushbits
POSTGRES_DB=pushbits

View file

@ -1,11 +0,0 @@
[tool.poetry]
name = "pushbits"
version = "0.0.0"
description = ""
authors = []
[tool.poetry.dependencies]
python = "^3.7"
[tool.poetry.dev-dependencies]
semgrep = "^0.82.0"

View file

@ -1,3 +1,4 @@
// Package mockups contains mockup objects and functions for tests.
package mockups
import "github.com/pushbits/server/internal/model"

View file

@ -2,7 +2,6 @@ package mockups
import (
"errors"
"io/ioutil"
"os"
"github.com/pushbits/server/internal/configuration"
@ -22,12 +21,12 @@ func ReadConfig(filename string, removeFile bool) (config *configuration.Configu
return nil, errors.New("empty filename")
}
file, err := ioutil.ReadFile(filename)
file, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
err = ioutil.WriteFile("config.yml", file, 0o644)
err = os.WriteFile("config.yml", file, 0o644)
if err != nil {
return nil, err
}
@ -35,7 +34,10 @@ func ReadConfig(filename string, removeFile bool) (config *configuration.Configu
config = configuration.Get()
if removeFile {
os.Remove("config.yml")
err = os.Remove("config.yml")
if err != nil {
return nil, err
}
}
return config, nil

View file

@ -3,28 +3,34 @@ package mockups
import (
"fmt"
"github.com/pushbits/server/internal/configuration"
"github.com/pushbits/server/internal/model"
)
// MockDispatcher is a dispatcher used for testing - it does not need any storage interface
type MockDispatcher struct{}
func (d *MockDispatcher) RegisterApplication(id uint, name, token, user string) (string, error) {
// RegisterApplication mocks a functions to create a channel for an application.
func (*MockDispatcher) RegisterApplication(id uint, name, _ string) (string, error) {
return fmt.Sprintf("%d-%s", id, name), nil
}
func (d *MockDispatcher) DeregisterApplication(a *model.Application, u *model.User) error {
// DeregisterApplication mocks a function to delete a channel for an application.
func (*MockDispatcher) DeregisterApplication(_ *model.Application, _ *model.User) error {
return nil
}
func (d *MockDispatcher) UpdateApplication(a *model.Application) error {
// UpdateApplication mocks a function to update a channel for an application.
func (*MockDispatcher) UpdateApplication(_ *model.Application, _ *configuration.RepairBehavior) error {
return nil
}
func (d *MockDispatcher) SendNotification(a *model.Application, n *model.Notification) (id string, err error) {
// SendNotification mocks a function to send a notification to a given user.
func (*MockDispatcher) SendNotification(_ *model.Application, _ *model.Notification) (id string, err error) {
return randStr(15), nil
}
func (d *MockDispatcher) DeleteNotification(a *model.Application, n *model.DeleteNotification) error {
// DeleteNotification mocks a function to send a notification to a given user that another notification is deleted
func (*MockDispatcher) DeleteNotification(_ *model.Application, _ *model.DeleteNotification) error {
return nil
}

View file

@ -5,10 +5,16 @@ import (
"encoding/base64"
)
func randStr(len int) string {
buff := make([]byte, len)
rand.Read(buff)
func randStr(length int) string {
buff := make([]byte, length)
_, err := rand.Read(buff)
if err != nil {
panic("cannot read random data")
}
str := base64.StdEncoding.EncodeToString(buff)
// Base 64 can be longer than len
return str[:len]
return str[:length]
}

View file

@ -1,3 +1,4 @@
// Package tests provides definitions and functionality related to unit and integration tests.
package tests
import (
@ -23,14 +24,14 @@ type Request struct {
// GetRequest returns a ResponseRecorder and gin context according to the data set in the Request.
// String data is passed as is, all other data types are marshaled before.
func (r *Request) GetRequest() (w *httptest.ResponseRecorder, c *gin.Context, err error) {
var body io.Reader
w = httptest.NewRecorder()
var body io.Reader
switch r.Data.(type) {
switch data := r.Data.(type) {
case string:
body = strings.NewReader(r.Data.(string))
body = strings.NewReader(data)
default:
dataMarshaled, err := json.Marshal(r.Data)
dataMarshaled, err := json.Marshal(data)
if err != nil {
return nil, nil, err
}

@ -1 +0,0 @@
Subproject commit 46e040f844c2b1c40599c8d4f1cdf277b197329e