Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5100d53c76 | ||
|
|
01f30cfd2b | ||
|
|
9d1b981644 | ||
|
|
3347d598a4 | ||
|
|
6bdf6e1cd3 | ||
|
|
f5e9eb52a7 | ||
|
|
85d29c1196 | ||
|
|
37d9282f7e | ||
|
|
5c2477eded | ||
|
|
bfd5fd4c16 | ||
|
|
dfc666ebb0 | ||
|
|
75f87fd618 | ||
|
|
752f80ea96 | ||
|
|
08269c034b | ||
|
|
89fa8928dc | ||
|
|
6fccd2010c | ||
|
|
4296334275 | ||
|
|
daa836e97d | ||
|
|
f9a89b64be | ||
|
|
775d25cb59 | ||
|
|
ef992cb9db | ||
|
|
c3b8a3f1ce | ||
|
|
45512115c5 | ||
|
|
fe0abd62c8 | ||
|
|
a54cf45a4b | ||
|
|
f9a5ef7085 | ||
|
|
200e7cf963 | ||
|
|
110f6206f9 | ||
|
|
c53d80792c | ||
|
|
ebc0c3bb8e | ||
|
|
cb59762fc9 | ||
|
|
a82466cb27 | ||
|
|
29eab978f7 | ||
|
|
f3166426b6 | ||
|
|
3171c53393
|
||
|
|
7af8e00b4c | ||
|
|
0c0f321b33
|
||
|
|
87805f1552 | ||
|
|
74ee30bccc | ||
|
|
646e4f31c4 | ||
|
|
881fac379f | ||
|
|
2c2bd2798f | ||
|
|
204b108ece | ||
|
|
fa795a58cf | ||
|
|
60650b9c04 | ||
|
|
60d9eea9c9
|
||
|
|
dd571f8d85 | ||
|
|
1f822b2957 |
No files matched your search
@@ -10,18 +10,69 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Run tests
|
||||
run: go test -race -coverprofile=coverage.txt ./...
|
||||
|
||||
- name: Check coverage
|
||||
id: coverage
|
||||
run: |
|
||||
go install github.com/vladopajic/go-test-coverage/v2@latest
|
||||
go-test-coverage --config ./.testcoverage.yml --github-action-output
|
||||
- name: Download baseline coverage
|
||||
if: gitea.event_name == 'pull_request'
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: coverage-baseline
|
||||
path: ./baseline
|
||||
continue-on-error: true
|
||||
- name: Compare coverage
|
||||
if: gitea.event_name == 'pull_request'
|
||||
run: |
|
||||
CURRENT="${{ steps.coverage.outputs.total-coverage }}"
|
||||
if [ -f ./baseline/coverage.txt ]; then
|
||||
BASE=$(cat ./baseline/coverage.txt)
|
||||
echo "Base coverage: ${BASE}%"
|
||||
echo "Current coverage: ${CURRENT}%"
|
||||
if [ "$(echo "$CURRENT < $BASE" | bc -l)" -eq 1 ]; then
|
||||
echo "::error::Coverage decreased from ${BASE}% to ${CURRENT}%"
|
||||
exit 1
|
||||
fi
|
||||
echo "Coverage maintained or improved: ${BASE}% -> ${CURRENT}%"
|
||||
else
|
||||
echo "No baseline coverage found, skipping comparison"
|
||||
echo "Current coverage: ${CURRENT}%"
|
||||
fi
|
||||
- name: Save coverage baseline
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
run: echo "${{ steps.coverage.outputs.total-coverage }}" > coverage.txt
|
||||
- name: Upload coverage baseline
|
||||
if: gitea.ref == 'refs/heads/main'
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-baseline
|
||||
path: coverage.txt
|
||||
retention-days: 90
|
||||
- name: Post coverage comment
|
||||
if: gitea.event_name == 'pull_request'
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
GITEA_URL: ${{ gitea.server_url }}
|
||||
run: |
|
||||
COVERAGE="${{ steps.coverage.outputs.total-coverage }}"
|
||||
curl -X POST "${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/issues/${{ gitea.event.pull_request.number }}/comments" \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"body\": \"## Coverage Report\n\nTotal coverage: **${COVERAGE}%**\"}"
|
||||
|
||||
vulnerabilities:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: 'stable'
|
||||
- name: Check vulnerabilities
|
||||
|
||||
@@ -13,11 +13,11 @@ jobs:
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: stable
|
||||
- uses: actions/setup-python@v5
|
||||
- uses: actions/setup-python@v7
|
||||
with:
|
||||
python-version: '3.14'
|
||||
- name: Install goimports
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
include:
|
||||
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
|
||||
- project: unboundsoftware/ci-templates
|
||||
file: Defaults.gitlab-ci.yml
|
||||
- project: unboundsoftware/ci-templates
|
||||
file: Release.gitlab-ci.yml
|
||||
- project: unboundsoftware/ci-templates
|
||||
file: Pre-Commit-Go.gitlab-ci.yml
|
||||
|
||||
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
|
||||
|
||||
stages:
|
||||
- deps
|
||||
- test
|
||||
|
||||
deps:
|
||||
stage: deps
|
||||
script:
|
||||
- go mod download
|
||||
|
||||
test:
|
||||
stage: test
|
||||
dependencies:
|
||||
- deps
|
||||
script:
|
||||
- CGO_ENABLED=1 go test -mod=readonly -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=$(go list ./... | tr '\n' , | sed 's/,$//') ./...
|
||||
- go tool cover -html=coverage.txt -o coverage.html
|
||||
- go tool cover -func=coverage.txt
|
||||
- curl -Os https://uploader.codecov.io/latest/linux/codecov
|
||||
- chmod +x codecov
|
||||
- ./codecov -t ${CODECOV_TOKEN} -R $CI_PROJECT_DIR -C $CI_COMMIT_SHA -r $CI_PROJECT_PATH
|
||||
|
||||
vulnerabilities:
|
||||
stage: test
|
||||
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
|
||||
script:
|
||||
- go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
- govulncheck ./...
|
||||
@@ -11,7 +11,7 @@ repos:
|
||||
- --allow-multiple-documents
|
||||
- id: check-added-large-files
|
||||
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
||||
rev: v9.23.0
|
||||
rev: v9.26.0
|
||||
hooks:
|
||||
- id: commitlint
|
||||
stages: [ commit-msg ]
|
||||
@@ -23,17 +23,17 @@ repos:
|
||||
- id: go-imports
|
||||
args:
|
||||
- -local
|
||||
- git.unbound.se/shiny/authz_client
|
||||
- gitea.unbound.se/shiny/authz_client
|
||||
- repo: https://github.com/lietu/go-pre-commit
|
||||
rev: v1.0.0
|
||||
hooks:
|
||||
- id: go-test
|
||||
- id: gofumpt
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v2.8.0
|
||||
rev: v2.13.2
|
||||
hooks:
|
||||
- id: golangci-lint-full
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.30.0
|
||||
rev: v8.30.1
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
@@ -0,0 +1,13 @@
|
||||
# Coverage configuration for go-test-coverage
|
||||
# https://github.com/vladopajic/go-test-coverage
|
||||
|
||||
profile: coverage.txt
|
||||
|
||||
threshold:
|
||||
file: 0
|
||||
package: 0
|
||||
total: 0
|
||||
|
||||
exclude:
|
||||
paths:
|
||||
- _test\.go$
|
||||
@@ -2,6 +2,77 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.6.1] - 2026-09-16
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- Keep existing privileges when User.Added is processed late (#331)
|
||||
|
||||
## [0.6.0] - 2026-09-14
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- [**breaking**] Consume privilege events with go-messaging-amqp (#327)
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.13.2 (#325)
|
||||
- Bump the minor version for breaking changes before 1.0.0 (#328)
|
||||
|
||||
## [0.5.1] - 2026-08-29
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- *(ci)* Use go-test-coverage binary directly to fix Gitea Actions (#303)
|
||||
- *(deps)* Update module github.com/stretchr/testify to v1.12.0 (#319)
|
||||
- *(deps)* Update module github.com/stretchr/testify to v1.12.1 (#321)
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update pre-commit hook gitleaks/gitleaks to v8.30.1 (#296)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.4 (#298)
|
||||
- *(deps)* Update dependency go to v1.26.2 (#300)
|
||||
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.25.0 (#304)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.0 (#306)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.1 (#308)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.12.2 (#309)
|
||||
- *(deps)* Update actions/checkout action to v7 (#311)
|
||||
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.26.0 (#313)
|
||||
- *(deps)* Update actions/setup-go action to v7 (#315)
|
||||
- *(deps)* Update actions/setup-python action to v7 (#317)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.13.1 (#322)
|
||||
|
||||
## [0.5.0] - 2026-03-12
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
- *(client)* Add API key authentication for /authz endpoint (#294)
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update golang:1.25.5 docker digest to 3a01526 (#271)
|
||||
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.24.0 (#273)
|
||||
- *(deps)* Update dependency go to v1.25.6 (#274)
|
||||
- *(deps)* Update golang docker tag to v1.25.6 (#275)
|
||||
- Remove GitLab CI configuration
|
||||
- Add code coverage integration
|
||||
- *(deps)* Update dependency go to v1.25.7 (#279)
|
||||
- *(deps)* Update dependency go to v1.26.0 (#280)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.9.0 (#281)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.10.0 (#282)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.10.1 (#283)
|
||||
- *(deps)* Update dependency go to v1.26.1 (#286)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.1 (#288)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.2 (#290)
|
||||
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.3 (#292)
|
||||
|
||||
## [0.4.1] - 2026-01-09
|
||||
|
||||
### ⚙️ Miscellaneous Tasks
|
||||
|
||||
- *(deps)* Update actions/setup-python action to v6
|
||||
- Migrate module path to gitea.unbound.se
|
||||
|
||||
## [0.4.0] - 2026-01-09
|
||||
|
||||
### 🚀 Features
|
||||
|
||||
@@ -17,7 +17,7 @@ Provides a client for the authz-service, handling privilege management for users
|
||||
### Usage
|
||||
|
||||
```go
|
||||
import client "git.unbound.se/shiny/authz_client"
|
||||
import client "gitea.unbound.se/shiny/authz_client"
|
||||
|
||||
// Create handler with options
|
||||
handler := client.New(client.WithBaseURL("http://authz-service"))
|
||||
@@ -43,4 +43,4 @@ The `CompanyPrivileges` struct contains permission flags:
|
||||
|
||||
### Event Handling
|
||||
|
||||
Implements `goamqp` message handlers to receive privilege update events from the authz-service, keeping the local privilege cache up-to-date.
|
||||
Registers per-replica (transient) go-messaging-amqp consumers for privilege update events from the authz-service (`Setup()`), keeping the local privilege cache up-to-date. Each routing key gets its own queue, so events for one company arrive in any order — `Process` must never discard state it didn't itself record. `User.Added` therefore creates the company entry only when it is missing: a `Privilege.Added` processed ahead of it would otherwise be overwritten, and nothing re-reads the privilege until the next `Fetch()`. The converse doesn't hold yet — `setPrivileges` still creates an entry for a company it never saw a `User.Added` for, so a `Privilege.*` delivered after `User.Removed` resurrects an all-false membership. Don't combine `Setup()` with go-messaging-amqp's `WithReconnect`: a reconnect declares new per-replica queues, so revocations published during the outage are lost unless `Fetch()` runs again. Services exit on connection loss (`CloseListener`) and re-fetch on start.
|
||||
@@ -1,6 +1,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -8,7 +9,8 @@ import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/sparetimecoders/goamqp"
|
||||
goamqp "codeberg.org/messaging/go-messaging-amqp"
|
||||
spec "codeberg.org/messaging/messaging"
|
||||
)
|
||||
|
||||
// CompanyPrivileges contains the privileges for a combination of email address and company id
|
||||
@@ -28,6 +30,7 @@ type PrivilegeHandler struct {
|
||||
*sync.RWMutex
|
||||
client *http.Client
|
||||
baseURL string
|
||||
apiKey string
|
||||
privileges map[string]map[string]*CompanyPrivileges
|
||||
}
|
||||
|
||||
@@ -41,6 +44,13 @@ func WithBaseURL(url string) OptsFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// WithAPIKey sets an API key used as a Bearer token when fetching privileges
|
||||
func WithAPIKey(key string) OptsFunc {
|
||||
return func(handler *PrivilegeHandler) {
|
||||
handler.apiKey = key
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new PrivilegeHandler. Pass OptsFuncs to configure.
|
||||
func New(opts ...OptsFunc) *PrivilegeHandler {
|
||||
handler := &PrivilegeHandler{
|
||||
@@ -57,7 +67,16 @@ func New(opts ...OptsFunc) *PrivilegeHandler {
|
||||
|
||||
// Fetch the initial set of privileges from an authz-service
|
||||
func (h *PrivilegeHandler) Fetch() error {
|
||||
resp, err := h.client.Get(fmt.Sprintf("%s/authz", h.baseURL))
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/authz", h.baseURL), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if h.apiKey != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+h.apiKey)
|
||||
}
|
||||
|
||||
resp, err := h.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -78,47 +97,59 @@ func (h *PrivilegeHandler) Fetch() error {
|
||||
|
||||
func (h *PrivilegeHandler) Setup() []goamqp.Setup {
|
||||
return []goamqp.Setup{
|
||||
goamqp.TransientEventStreamConsumer("User.Added", h.Process, UserAdded{}),
|
||||
goamqp.TransientEventStreamConsumer("User.Removed", h.Process, UserRemoved{}),
|
||||
goamqp.TransientEventStreamConsumer("Privilege.Added", h.Process, PrivilegeAdded{}),
|
||||
goamqp.TransientEventStreamConsumer("Privilege.Removed", h.Process, PrivilegeRemoved{}),
|
||||
goamqp.TransientEventStreamConsumer("User.Added", process[UserAdded](h)),
|
||||
goamqp.TransientEventStreamConsumer("User.Removed", process[UserRemoved](h)),
|
||||
goamqp.TransientEventStreamConsumer("Privilege.Added", process[PrivilegeAdded](h)),
|
||||
goamqp.TransientEventStreamConsumer("Privilege.Removed", process[PrivilegeRemoved](h)),
|
||||
}
|
||||
}
|
||||
|
||||
// privilegeEvent is the set of events Process handles.
|
||||
type privilegeEvent interface {
|
||||
UserAdded | UserRemoved | PrivilegeAdded | PrivilegeRemoved
|
||||
}
|
||||
|
||||
// process adapts Process to a typed go-messaging-amqp handler.
|
||||
func process[T privilegeEvent](h *PrivilegeHandler) spec.EventHandler[T] {
|
||||
return func(_ context.Context, event spec.ConsumableEvent[T]) error {
|
||||
return h.Process(&event.Payload)
|
||||
}
|
||||
}
|
||||
|
||||
// Process privilege-related events and update the internal state
|
||||
func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface{}, error) {
|
||||
func (h *PrivilegeHandler) Process(msg any) error {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
|
||||
switch ev := msg.(type) {
|
||||
case *UserAdded:
|
||||
if priv, exists := h.privileges[ev.Email]; exists {
|
||||
priv[ev.CompanyID] = &CompanyPrivileges{}
|
||||
} else {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
h.privileges[ev.Email] = map[string]*CompanyPrivileges{
|
||||
ev.CompanyID: {},
|
||||
}
|
||||
// Keep the privileges already recorded for the company. Each routing key has
|
||||
// its own transient queue, so a Privilege.Added published after this event can
|
||||
// be processed before it; overwriting the entry here would drop that privilege
|
||||
// until the next Fetch, which only runs at start.
|
||||
priv, exists := h.privileges[ev.Email]
|
||||
if !exists {
|
||||
priv = map[string]*CompanyPrivileges{}
|
||||
h.privileges[ev.Email] = priv
|
||||
}
|
||||
return nil, nil
|
||||
if _, exists := priv[ev.CompanyID]; !exists {
|
||||
priv[ev.CompanyID] = &CompanyPrivileges{}
|
||||
}
|
||||
return nil
|
||||
case *UserRemoved:
|
||||
if priv, exists := h.privileges[ev.Email]; exists {
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
delete(priv, ev.CompanyID)
|
||||
}
|
||||
return nil, nil
|
||||
return nil
|
||||
case *PrivilegeAdded:
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
|
||||
return nil, nil
|
||||
return nil
|
||||
case *PrivilegeRemoved:
|
||||
h.Lock()
|
||||
defer h.Unlock()
|
||||
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
|
||||
return nil, nil
|
||||
return nil
|
||||
default:
|
||||
fmt.Printf("Got unexpected message type (%s): '%+v'\n", reflect.TypeOf(msg).String(), msg)
|
||||
return nil, fmt.Errorf("unexpected event type: '%s'", reflect.TypeOf(msg))
|
||||
return fmt.Errorf("unexpected event type: '%s'", reflect.TypeOf(msg))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+162
-47
@@ -1,6 +1,7 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -8,28 +9,28 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/sparetimecoders/goamqp"
|
||||
goamqp "codeberg.org/messaging/go-messaging-amqp"
|
||||
spec "codeberg.org/messaging/messaging"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPrivilegeHandler_Process_InvalidType(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
result, err := handler.Process("abc", goamqp.Headers{})
|
||||
err := handler.Process("abc")
|
||||
|
||||
assert.Nil(t, result)
|
||||
assert.EqualError(t, err, "unexpected event type: 'string'")
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
result, err := handler.Process(&PrivilegeAdded{
|
||||
err := handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeAdmin,
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -38,12 +39,11 @@ func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) {
|
||||
|
||||
assert.Equal(t, []string{"abc-123"}, companies)
|
||||
|
||||
result, err = handler.Process(&PrivilegeRemoved{
|
||||
err = handler.Process(&PrivilegeRemoved{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeAdmin,
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -56,18 +56,16 @@ func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) {
|
||||
func TestPrivilegeHandler_Process_UserAdded_And_UserRemoved(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
result, err := handler.Process(&UserAdded{
|
||||
err := handler.Process(&UserAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err = handler.Process(&UserAdded{
|
||||
err = handler.Process(&UserAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-456",
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -76,18 +74,16 @@ func TestPrivilegeHandler_Process_UserAdded_And_UserRemoved(t *testing.T) {
|
||||
sort.Strings(companies)
|
||||
assert.Equal(t, []string{"abc-123", "abc-456"}, companies)
|
||||
|
||||
result, err = handler.Process(&UserRemoved{
|
||||
err = handler.Process(&UserRemoved{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
result, err = handler.Process(&UserRemoved{
|
||||
err = handler.Process(&UserRemoved{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-456",
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -96,6 +92,50 @@ func TestPrivilegeHandler_Process_UserAdded_And_UserRemoved(t *testing.T) {
|
||||
assert.Empty(t, companies)
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_Process_UserAdded_Keeps_Existing_Privileges(t *testing.T) {
|
||||
// Each routing key has its own transient queue, so Privilege.Added can be
|
||||
// processed before the User.Added published before it. The privilege must
|
||||
// survive either order: nothing re-reads it until the next Fetch at start.
|
||||
userAdded := &UserAdded{Email: "jim@example.org", CompanyID: "abc-123"}
|
||||
privilegeAdded := &PrivilegeAdded{Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin}
|
||||
for name, order := range map[string][]any{
|
||||
"user added first": {userAdded, privilegeAdded},
|
||||
"privilege added first": {privilegeAdded, userAdded},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
for _, event := range order {
|
||||
require.NoError(t, handler.Process(event))
|
||||
}
|
||||
|
||||
assert.True(t, handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||
return privileges.Admin
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_Process_UserAdded_After_UserRemoved_Starts_Empty(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
assert.NoError(t, handler.Process(&UserAdded{Email: "jim@example.org", CompanyID: "abc-123"}))
|
||||
assert.NoError(t, handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeAdmin,
|
||||
}))
|
||||
assert.NoError(t, handler.Process(&UserRemoved{Email: "jim@example.org", CompanyID: "abc-123"}))
|
||||
assert.NoError(t, handler.Process(&UserAdded{Email: "jim@example.org", CompanyID: "abc-123"}))
|
||||
|
||||
// Membership is back, without the privileges the removal took away.
|
||||
assert.Equal(t, []string{"abc-123"}, handler.CompaniesByUser("jim@example.org", func(CompanyPrivileges) bool {
|
||||
return true
|
||||
}))
|
||||
assert.False(t, handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||
return privileges.Admin
|
||||
}))
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
@@ -109,11 +149,10 @@ func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) {
|
||||
func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
result, err := handler.Process(&UserAdded{
|
||||
err := handler.Process(&UserAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -128,11 +167,10 @@ func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) {
|
||||
|
||||
assert.Equal(t, []string{"abc-123"}, companies)
|
||||
|
||||
result, err = handler.Process(&UserRemoved{
|
||||
err = handler.Process(&UserRemoved{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -144,12 +182,11 @@ func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) {
|
||||
func TestPrivilegeHandler_GetCompanies_Company_With_Company_Access_Found(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
result, err := handler.Process(&PrivilegeAdded{
|
||||
err := handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeCompany,
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -162,12 +199,11 @@ func TestPrivilegeHandler_GetCompanies_Company_With_Company_Access_Found(t *test
|
||||
func TestPrivilegeHandler_GetCompanies_Company_With_Admin_Access_Found(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
result, err := handler.Process(&PrivilegeAdded{
|
||||
err := handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeConsumer,
|
||||
}, goamqp.Headers{})
|
||||
assert.Nil(t, result)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||
@@ -190,11 +226,11 @@ func TestPrivilegeHandler_IsAllowed_Return_False_If_No_Privileges(t *testing.T)
|
||||
func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
_, _ = handler.Process(&PrivilegeAdded{
|
||||
_ = handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeTime,
|
||||
}, goamqp.Headers{})
|
||||
})
|
||||
|
||||
result := handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||
return privileges.Time
|
||||
@@ -202,11 +238,11 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T
|
||||
|
||||
assert.True(t, result)
|
||||
|
||||
_, _ = handler.Process(&PrivilegeAdded{
|
||||
_ = handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeInvoicing,
|
||||
}, goamqp.Headers{})
|
||||
})
|
||||
|
||||
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||
return privileges.Invoicing
|
||||
@@ -214,11 +250,11 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T
|
||||
|
||||
assert.True(t, result)
|
||||
|
||||
_, _ = handler.Process(&PrivilegeAdded{
|
||||
_ = handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeAccounting,
|
||||
}, goamqp.Headers{})
|
||||
})
|
||||
|
||||
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||
return privileges.Accounting
|
||||
@@ -226,11 +262,11 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T
|
||||
|
||||
assert.True(t, result)
|
||||
|
||||
_, _ = handler.Process(&PrivilegeAdded{
|
||||
_ = handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeSupplier,
|
||||
}, goamqp.Headers{})
|
||||
})
|
||||
|
||||
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||
return privileges.Supplier
|
||||
@@ -238,11 +274,11 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T
|
||||
|
||||
assert.True(t, result)
|
||||
|
||||
_, _ = handler.Process(&PrivilegeAdded{
|
||||
_ = handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: "abc-123",
|
||||
Privilege: PrivilegeSalary,
|
||||
}, goamqp.Headers{})
|
||||
})
|
||||
|
||||
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||
return privileges.Salary
|
||||
@@ -251,6 +287,39 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T
|
||||
assert.True(t, result)
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_Fetch_Sends_Authorization_Header_When_APIKey_Set(t *testing.T) {
|
||||
var receivedAuth string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedAuth = r.Header.Get("Authorization")
|
||||
_, _ = w.Write([]byte("{}"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
handler := New(
|
||||
WithBaseURL(server.URL),
|
||||
WithAPIKey("my-secret-key"),
|
||||
)
|
||||
|
||||
err := handler.Fetch()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Bearer my-secret-key", receivedAuth)
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_Fetch_No_Authorization_Header_Without_APIKey(t *testing.T) {
|
||||
var receivedAuth string
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
receivedAuth = r.Header.Get("Authorization")
|
||||
_, _ = w.Write([]byte("{}"))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
handler := New(WithBaseURL(server.URL))
|
||||
|
||||
err := handler.Fetch()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, receivedAuth)
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
@@ -489,11 +558,11 @@ func TestPrivilegeHandler_Concurrent_Process_And_Read(t *testing.T) {
|
||||
companyID := fmt.Sprintf("company-%d", i%10)
|
||||
go func(id string) {
|
||||
defer wg.Done()
|
||||
_, _ = handler.Process(&PrivilegeAdded{
|
||||
_ = handler.Process(&PrivilegeAdded{
|
||||
Email: "jim@example.org",
|
||||
CompanyID: id,
|
||||
Privilege: PrivilegeAdmin,
|
||||
}, goamqp.Headers{})
|
||||
})
|
||||
}(companyID)
|
||||
}
|
||||
|
||||
@@ -565,11 +634,11 @@ func TestPrivilegeHandler_Concurrent_Multiple_Operations(t *testing.T) {
|
||||
wg.Add(1)
|
||||
go func(idx int) {
|
||||
defer wg.Done()
|
||||
_, _ = handler.Process(&PrivilegeAdded{
|
||||
_ = handler.Process(&PrivilegeAdded{
|
||||
Email: "jane@example.org",
|
||||
CompanyID: fmt.Sprintf("company-%d", idx%5),
|
||||
Privilege: PrivilegeCompany,
|
||||
}, goamqp.Headers{})
|
||||
})
|
||||
}(i)
|
||||
}
|
||||
|
||||
@@ -615,3 +684,49 @@ func TestPrivilegeHandler_Concurrent_Multiple_Operations(t *testing.T) {
|
||||
expectedJane := []string{"company-0", "company-1", "company-2", "company-3", "company-4"}
|
||||
assert.Equal(t, expectedJane, janeCompanies)
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_Setup(t *testing.T) {
|
||||
handler := New(WithBaseURL("base"))
|
||||
|
||||
topology, err := goamqp.CollectTopology("some-service", handler.Setup()...)
|
||||
assert.NoError(t, err)
|
||||
wiring := map[string]string{}
|
||||
for _, e := range topology.Endpoints {
|
||||
assert.Equal(t, spec.DirectionConsume, e.Direction)
|
||||
assert.True(t, e.Ephemeral, "%s must be a per-replica consumer", e.RoutingKey)
|
||||
wiring[e.RoutingKey] = e.MessageType
|
||||
}
|
||||
// A key wired to the wrong type could turn a revocation into a grant.
|
||||
assert.Equal(t, map[string]string{
|
||||
"User.Added": "client.UserAdded",
|
||||
"User.Removed": "client.UserRemoved",
|
||||
"Privilege.Added": "client.PrivilegeAdded",
|
||||
"Privilege.Removed": "client.PrivilegeRemoved",
|
||||
}, wiring)
|
||||
}
|
||||
|
||||
func TestPrivilegeHandler_process(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
handler := New(WithBaseURL("base"))
|
||||
admin := func(p CompanyPrivileges) bool { return p.Admin }
|
||||
|
||||
assert.NoError(t, process[UserAdded](handler)(ctx, spec.ConsumableEvent[UserAdded]{
|
||||
Payload: UserAdded{Email: "jim@example.org", CompanyID: "abc-123"},
|
||||
}))
|
||||
assert.False(t, handler.IsAllowed("jim@example.org", "abc-123", admin))
|
||||
|
||||
assert.NoError(t, process[PrivilegeAdded](handler)(ctx, spec.ConsumableEvent[PrivilegeAdded]{
|
||||
Payload: PrivilegeAdded{Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin},
|
||||
}))
|
||||
assert.True(t, handler.IsAllowed("jim@example.org", "abc-123", admin))
|
||||
|
||||
assert.NoError(t, process[PrivilegeRemoved](handler)(ctx, spec.ConsumableEvent[PrivilegeRemoved]{
|
||||
Payload: PrivilegeRemoved{Email: "jim@example.org", CompanyID: "abc-123", Privilege: PrivilegeAdmin},
|
||||
}))
|
||||
assert.False(t, handler.IsAllowed("jim@example.org", "abc-123", admin))
|
||||
|
||||
assert.NoError(t, process[UserRemoved](handler)(ctx, spec.ConsumableEvent[UserRemoved]{
|
||||
Payload: UserRemoved{Email: "jim@example.org", CompanyID: "abc-123"},
|
||||
}))
|
||||
assert.Empty(t, handler.CompaniesByUser("jim@example.org", func(CompanyPrivileges) bool { return true }))
|
||||
}
|
||||
@@ -78,3 +78,7 @@ filter_commits = false
|
||||
topo_order = false
|
||||
# sort the commits inside sections by oldest/newest order
|
||||
sort_commits = "oldest"
|
||||
|
||||
[bump]
|
||||
# Before 1.0.0, a breaking change bumps the minor version instead of the major.
|
||||
breaking_always_bump_major = false
|
||||
@@ -1,19 +1,31 @@
|
||||
module git.unbound.se/shiny/authz_client
|
||||
module gitea.unbound.se/shiny/authz_client
|
||||
|
||||
go 1.22.12
|
||||
|
||||
toolchain go1.25.5
|
||||
go 1.26.2
|
||||
|
||||
require (
|
||||
github.com/sparetimecoders/goamqp v0.3.3
|
||||
github.com/stretchr/testify v1.11.1
|
||||
codeberg.org/messaging/go-messaging-amqp v0.0.5
|
||||
codeberg.org/messaging/messaging v0.0.5
|
||||
github.com/stretchr/testify v1.12.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/rabbitmq/amqp091-go v1.15.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
google.golang.org/protobuf v1.36.8 // indirect
|
||||
)
|
||||
@@ -1,20 +1,72 @@
|
||||
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=
|
||||
codeberg.org/messaging/go-messaging-amqp v0.0.5 h1:zzcJ+FVWgBPKyTkq9RXF11yvnhs8IN5BALBC+mps1zA=
|
||||
codeberg.org/messaging/go-messaging-amqp v0.0.5/go.mod h1:6bSCIkKH0V/oI7vaIq8ttINKYwov9c8ckZrwjfvp7kc=
|
||||
codeberg.org/messaging/messaging v0.0.5 h1:/ueH90F4RNPUeeJIwdG9oVhDW7+XjCzwOYq8xc0kfQk=
|
||||
codeberg.org/messaging/messaging v0.0.5/go.mod h1:xyWLUcfaVzcN6GWk9uaQsxPVUC2Vm2S89mYZGdiWTWg=
|
||||
codeberg.org/messaging/messaging/tck v0.0.3 h1:a4Nr7ytFqEJloTOyVeAtMNgO9Fig1ZUirjW4FVlK0lc=
|
||||
codeberg.org/messaging/messaging/tck v0.0.3/go.mod h1:RiOsKXGAhNQK2STBVYGjhN0CFtmcrsEne1qUe15n8Q4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw=
|
||||
github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/sparetimecoders/goamqp v0.3.3 h1:z/nfTPmrjeU/rIVuNOgsVLCimp3WFoNFvS3ZzXRJ6HE=
|
||||
github.com/sparetimecoders/goamqp v0.3.3/go.mod h1:W9NRCpWLE+Vruv2dcRSbszNil2O826d2Nv6kAkETW5o=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/nats-io/nats.go v1.52.0 h1:n3avV4VBsCgsdwh71TppsTwtv+QdPs7ntSKM8qJLGsc=
|
||||
github.com/nats-io/nats.go v1.52.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno=
|
||||
github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
|
||||
github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/rabbitmq/amqp091-go v1.15.0 h1:LEQL4/yp48/Wigt6A6XOu18RQRo8ZHtB5I/KZJn+gkw=
|
||||
github.com/rabbitmq/amqp091-go v1.15.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
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=
|
||||
Reference in new issue
Block a user