diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24978ae..2d72856 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,7 +132,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.26.3" + go-version: "1.26.5" cache: true - name: Download dependencies @@ -189,7 +189,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.26.3" + go-version: "1.26.5" cache: true - name: Download dependencies diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6f64ccc..441ac84 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v6 with: - go-version: "1.26.3" + go-version: "1.26.5" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v7 diff --git a/CONTEXT.md b/CONTEXT.md index 15c203b..7785a4e 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -91,6 +91,26 @@ the default read. A background job that calls `Keyring.Rewrap` for every DEK not yet encrypted under the active KEK. Runs after `Keyring.RotateKek`. Idempotent. +### Route Module +Each feature (`auth`, `secrets`, `transit`, `tokenization`) owns a `Module` +in its `http` package that registers that feature's HTTP routes next to its +handlers. Constructed by the composition root with its handlers, the +`Authorizer`, and business metrics already bound, it implements +`RouteRegistrar`. `internal/http` imports no feature package; the import +direction is feature → `internal/http`. + +### RouteRegistrar +The seam `internal/http` exposes so it can mount features without knowing any +of them: `Register(v1 *gin.RouterGroup, mw RouteMiddlewares)`. `SetupRouter` +builds the global middleware chain and loops over a `[]RouteRegistrar`. Adding +an endpoint is a one-file change inside the owning feature. + +### RouteMiddlewares +The shared per-route middleware bundle passed to every `Register`: +`Auth` and `RateLimit`, both plain `gin.HandlerFunc` so the seam carries no +feature type. `RateLimit` is nil when disabled; the auth module's IP-based +token rate limiter is captured by that module, not carried here. + ## Authentication ### Client diff --git a/Dockerfile b/Dockerfile index 5cb4395..ba69f61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,13 +33,13 @@ # ============================================================================== # Go version for builder stage (matches go.mod) -ARG GO_VERSION=1.26.3 +ARG GO_VERSION=1.26.5 # ============================================================================== # Stage 1: Builder # ============================================================================== # Purpose: Compile the Go application into a static binary -# Base: golang:1.26.3-trixie (Debian 13 Trixie for glibc version consistency) +# Base: golang:1.26.5-trixie (Debian 13 Trixie for glibc version consistency) # Output: /app/bin/app (static binary with version metadata injected) FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-trixie AS builder diff --git a/docs/README.md b/docs/README.md index 76c7f69..8a657d9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -130,7 +130,7 @@ This section documents key architectural decisions with their context, rationale - ✅ Linux and macOS environments for local development and operations - ✅ Docker-based runtime recommended for all environments -- ✅ CI validates with Go `1.26.3` and PostgreSQL `16-alpine` +- ✅ CI validates with Go `1.26.5` and PostgreSQL `16-alpine` - ℹ️ Project compatibility targets include PostgreSQL `12+` ## 💡 Practical Examples diff --git a/go.mod b/go.mod index e3e26f4..2d9f39c 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/allisson/secrets -go 1.26.3 +go 1.26.5 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 @@ -16,49 +16,49 @@ require ( github.com/lib/pq v1.12.3 github.com/prometheus/client_golang v1.23.2 github.com/stretchr/testify v1.11.1 - github.com/urfave/cli/v3 v3.9.0 - go.opentelemetry.io/otel v1.43.0 - go.opentelemetry.io/otel/exporters/prometheus v0.65.0 - go.opentelemetry.io/otel/metric v1.43.0 - go.opentelemetry.io/otel/sdk v1.43.0 - go.opentelemetry.io/otel/sdk/metric v1.43.0 + github.com/urfave/cli/v3 v3.10.1 + go.opentelemetry.io/otel v1.44.0 + go.opentelemetry.io/otel/exporters/prometheus v0.66.0 + go.opentelemetry.io/otel/metric v1.44.0 + go.opentelemetry.io/otel/sdk v1.44.0 + go.opentelemetry.io/otel/sdk/metric v1.44.0 go.uber.org/goleak v1.3.0 - gocloud.dev v0.45.0 - gocloud.dev/secrets/hashivault v0.45.0 - golang.org/x/crypto v0.52.0 - golang.org/x/sync v0.20.0 + gocloud.dev v0.46.0 + gocloud.dev/secrets/hashivault v0.46.0 + golang.org/x/crypto v0.54.0 + golang.org/x/sync v0.22.0 golang.org/x/time v0.15.0 ) require ( cloud.google.com/go v0.123.0 // indirect - cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth v0.18.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect cloud.google.com/go/iam v1.5.3 // indirect - cloud.google.com/go/kms v1.23.2 // indirect - cloud.google.com/go/longrunning v0.7.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 // indirect + cloud.google.com/go/kms v1.26.0 // indirect + cloud.google.com/go/longrunning v0.8.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect - github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect - github.com/aws/aws-sdk-go-v2 v1.40.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.32.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.49.1 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 // indirect - github.com/aws/smithy-go v1.24.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.9 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.20 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.19 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.25 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.1.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.19 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 // indirect + github.com/aws/smithy-go v1.26.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.15.0 // indirect @@ -79,11 +79,11 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/wire v0.7.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect - github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect + github.com/googleapis/gax-go/v2 v2.19.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect @@ -112,27 +112,27 @@ require ( github.com/prometheus/otlptranslator v1.0.0 // indirect github.com/prometheus/procfs v0.20.1 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.59.0 // indirect + github.com/quic-go/quic-go v0.59.1 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.1 // indirect go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect golang.org/x/arch v0.23.0 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/text v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.40.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect - google.golang.org/api v0.256.0 // indirect - google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/api v0.272.0 // indirect + google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 // indirect google.golang.org/grpc v1.79.3 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index fdd2cf9..393c766 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,35 @@ cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE= cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= +cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/kms v1.23.2 h1:4IYDQL5hG4L+HzJBhzejUySoUOheh3Lk5YT4PCyyW6k= -cloud.google.com/go/kms v1.23.2/go.mod h1:rZ5kK0I7Kn9W4erhYVoIRPtpizjunlrfU4fUkumUp8g= -cloud.google.com/go/longrunning v0.7.0 h1:FV0+SYF1RIj59gyoWDRi45GiYUMM3K1qO51qoboQT1E= -cloud.google.com/go/longrunning v0.7.0/go.mod h1:ySn2yXmjbK9Ba0zsQqunhDkYi0+9rlXIwnoAf+h+TPY= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +cloud.google.com/go/kms v1.26.0 h1:cK9mN2cf+9V63D3H1f6koxTatWy39aTI/hCjz1I+adU= +cloud.google.com/go/kms v1.26.0/go.mod h1:pHKOdFJm63hxBsiPkYtowZPltu9dW0MWvBa6IA4HM58= +cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= +cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 h1:4iB+IesclUXdP0ICgAabvq2FYLXrJWKx1fJQ+GxSo3Y= +github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -40,36 +40,36 @@ github.com/allisson/go-pwdhash v0.4.0 h1:mmiKeXJbykz7xfEOZO+hlqbsxky6OjU3GEiSAbA github.com/allisson/go-pwdhash v0.4.0/go.mod h1:9gKQarlO+tD+rUst0gRT9PD8fgIKsjfV6KeByX9qogM= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go-v2 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= -github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= -github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk= -github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI= -github.com/aws/aws-sdk-go-v2/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4= -github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= -github.com/aws/aws-sdk-go-v2/service/kms v1.49.1 h1:U0asSZ3ifpuIehDPkRI2rxHbmFUMplDA2VeR9Uogrmw= -github.com/aws/aws-sdk-go-v2/service/kms v1.49.1/go.mod h1:NZo9WJqQ0sxQ1Yqu1IwCHQFQunTms2MlVgejg16S1rY= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= -github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= -github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/aws/aws-sdk-go-v2 v1.41.9 h1:/rYeyO2+HrMztAmxAq9++XJtFMqSIpSsNA0yDGALYq4= +github.com/aws/aws-sdk-go-v2 v1.41.9/go.mod h1:+HsoOEX80qAVUitj1A2DhCNTjmb3edVyuDypb6LNEeo= +github.com/aws/aws-sdk-go-v2/config v1.32.20 h1:8VMDnWc/kEzxsI/1ngGM9mG81a8IGmIHD8KLcYGwagc= +github.com/aws/aws-sdk-go-v2/config v1.32.20/go.mod h1:PuwEpciweIXGULWeOeSTXtSbH4CW9mWdWrhdCKQI1sM= +github.com/aws/aws-sdk-go-v2/credentials v1.19.19 h1:yuFzSV1U0aRNYCQGVaTY2zW2M/L93pYHnXnrJUphYhU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.19/go.mod h1:7y63L1kGzeoDlJaQ3Z578KrnmfBut96JjvJUzGwR+YE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25 h1:0w6dCiO8iez+YKwRhRBlL1CH/E3GTfdkuzrwj1by8vo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.25/go.mod h1:9FDWUothyr5RCRAHc45XOiVCzUR8n/IhCYX+uVqw6vk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25 h1:Uii3frf9ztec/ABM2/FSH9/z7PLzxfpG8h4RpkUFflQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.25/go.mod h1:G6kntsA2GorAxDPbap6xgB2F+amSLUF8GJTi7PUoX44= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25 h1:r1+/l6m+WaUJF9HISEsNOLHSNj5EXYQxK8VX6Cz9NlA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.25/go.mod h1:cKf+D+NMDK1LndD7BowHbBZPgR9V0/5HubH0PFWvA+c= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26 h1:A1PmWU2zfkIm9EyFlJncFXL4W4phML+h8KjltUsCvNQ= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.26/go.mod h1:dY4MRzXEizrD4hqtpKvWVGPX7QleSGGVY+EBolo1RmM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10 h1:d5/908OJ4bXg8lyjeMPvXetEKqoDoLi5Owy1zNue3yg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.10/go.mod h1:a57l7Hwh+FWI+we50g5NPJHYUKeJKfXbc4w8SyXu8Ig= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.25 h1:dD3dhHNglpd98gs72my22Ndqi1hqQGllFFg1F+twfxg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.25/go.mod h1:0yAbjPfd64gG7mj85RW+fMEYdfBgCRZw8g/oWcL1pjc= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3 h1:s/zDSG/a/Su9aX+v0Ld9cimUCdkr5FWPmBV8owaEbZY= +github.com/aws/aws-sdk-go-v2/service/kms v1.50.3/go.mod h1:/iSgiUor15ZuxFGQSTf3lA2FmKxFsQoc2tADOarQBSw= +github.com/aws/aws-sdk-go-v2/service/signin v1.1.1 h1:1VwbP3qMNfxUDEXWki4rCE5iA+44VA1lokTz9HasGzw= +github.com/aws/aws-sdk-go-v2/service/signin v1.1.1/go.mod h1:vUtyoSj0OPji3kjIVSc/GlKuWEiL33f/WFxl6dmpy/A= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.19 h1:N6pIsdFOW1Kd9S4KyFKXdGRBojPPxkP32+uHFWLv4Hc= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.19/go.mod h1:3gt5WJArFooNmyLONS+h/R4J+o86II8du38IgCwj9dE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 h1:hc+lBYiiTr8Zk4MTzIsQ92MeDWCIDvWGmzKUWOaBcOg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2/go.mod h1:hU6fqB3OJA6/ePheD47LQnxvjYk6br6PtQxs+Q9ojvk= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 h1:ErklX/7uhSbkAAeyQD/Y1OoQ9hO3SJXQNEgksORW3Js= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.3/go.mod h1:ULe4HCzfKPiR6R3HEurE3b1upEkuk8AkMrOKtaOxKO8= +github.com/aws/smithy-go v1.26.0 h1:9ouqbi+NyKP7fV3Te7UElCwdAb6Y8uk7LGwPE5tVe/s= +github.com/aws/smithy-go v1.26.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= @@ -86,8 +86,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w= -github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik= +github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -107,10 +107,10 @@ github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6 github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= -github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g= -github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98= -github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4= -github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA= +github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ= +github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A= +github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds= +github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -148,8 +148,8 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-migrate/migrate/v4 v4.19.1 h1:OCyb44lFuQfYXYLx1SCxPZQGU7mcaZ7gH9yH4jSFbBA= github.com/golang-migrate/migrate/v4 v4.19.1/go.mod h1:CTcgfjxhaUtsLipnLoQRWCrjYXycRz/g5+RWDuYgPrE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -169,10 +169,10 @@ 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/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8= +github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE= +github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -267,8 +267,8 @@ github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEy github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= -github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= +github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= 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/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= @@ -290,68 +290,70 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c= -github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY= +github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= 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/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE= -go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo= -go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= -go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= -go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= -go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= -go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/exporters/prometheus v0.66.0 h1:vkrK8PAznv2NKt2r+kdu252ccGzkEqLc2aSXbQIALYQ= +go.opentelemetry.io/otel/exporters/prometheus v0.66.0/go.mod h1:V/UB6D3vMF/UBOL5igAsAYnk1nG/bzYYTzvsB16cy7o= +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/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA= +go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk= +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/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +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= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -gocloud.dev v0.45.0 h1:WknIK8IbRdmynDvara3Q7G6wQhmEiOGwpgJufbM39sY= -gocloud.dev v0.45.0/go.mod h1:0kXKmkCLG6d31N7NyLZWzt7jDSQura9zD/mWgiB6THI= -gocloud.dev/secrets/hashivault v0.45.0 h1:PZfaKH6wfUoVyFGEIU57zzn9qN7in6vjW/HGaxuOf5Q= -gocloud.dev/secrets/hashivault v0.45.0/go.mod h1:ZfcZxE4948tl8o+bkxhHlOSghPB9JyoJCJGHGsTERRk= +gocloud.dev v0.46.0 h1:niIuZwSjMtBx8K+ITB2s5kZullB13PGOS2ZoQPZxQ4Q= +gocloud.dev v0.46.0/go.mod h1:ACQe+2qO+hEO+pdcvvsM+RB63r8TyGD1W3ESCLFyzvM= +gocloud.dev/secrets/hashivault v0.46.0 h1:gQlpb/1wEwVoOavi/RGeQs5WlnzYddcDW81b0kywI0w= +gocloud.dev/secrets/hashivault v0.46.0/go.mod h1:I9RZ3S1+v/o++SRTJzSJ904LUGaBx7cpKVrzobm2SIA= golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg= golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= -google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= -google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846 h1:dDbsTLIK7EzwUq36kCSAsk0slouq/S0tWHeeGi97cD8= -google.golang.org/genproto v0.0.0-20251124214823-79d6a2a48846/go.mod h1:PP0g88Dz3C7hRAfbQCQggeWAXjuqGsNPLE4s7jh0RGU= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA= +google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA= +google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE= +google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw= +google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5 h1:CogIeEXn4qWYzzQU0QqvYBM8yDF9cFYzDq9ojSpv0Js= +google.golang.org/genproto/googleapis/api v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5 h1:aJmi6DVGGIStN9Mobk/tZOOQUBbj0BPjZjjnOdoZKts= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/internal/app/di.go b/internal/app/di.go index 47380c0..62d2e70 100644 --- a/internal/app/di.go +++ b/internal/app/di.go @@ -10,6 +10,7 @@ import ( "sync" "github.com/allisson/go-pwdhash" + "github.com/gin-gonic/gin" authDomain "github.com/allisson/secrets/internal/auth/domain" authHTTP "github.com/allisson/secrets/internal/auth/http" @@ -346,21 +347,57 @@ func (c *Container) initHTTPServer(ctx context.Context) (*http.Server, error) { return nil, fmt.Errorf("failed to get business metrics: %w", err) } - server.SetupRouter(ctx, c.config, http.RouterDeps{ - ClientHandler: clientHandler, - TokenHandler: tokenHandler, - AuditLogHandler: auditLogHandler, - SecretHandler: secretHandler, - TransitKeyHandler: transitKeyHandler, - CryptoHandler: cryptoHandler, - TokenizationKeyHandler: tokenizationKeyHandler, - TokenizationHandler: tokenizationHandler, - TokenUseCase: tokenUseCase, - AuditLogUseCase: auditLogUseCase, - BusinessMetrics: businessMetrics, - MetricsProvider: metricsProvider, - MetricsNamespace: c.config.MetricsNamespace, - }) + // Build the shared per-route middleware. Authentication is always present; + // the two rate limiters are optional and stay nil when disabled. + authMiddleware := authHTTP.AuthenticationMiddleware(tokenUseCase, logger) + + var rateLimitMiddleware gin.HandlerFunc + if c.config.RateLimitEnabled { + rateLimitMiddleware = authHTTP.RateLimitMiddleware( + ctx, + c.config.RateLimitRequestsPerSec, + c.config.RateLimitBurst, + logger, + ) + } + + var tokenRateLimitMiddleware gin.HandlerFunc + if c.config.RateLimitTokenEnabled { + tokenRateLimitMiddleware = authHTTP.TokenRateLimitMiddleware( + ctx, + c.config.RateLimitTokenRequestsPerSec, + c.config.RateLimitTokenBurst, + logger, + ) + } + + // Build the per-route authorizer once; each module captures it so route + // registrations only carry the capability. + authz := authHTTP.NewAuthorizer(auditLogUseCase, logger) + + // Each feature owns its route registration; the composition root assembles + // the modules and the server mounts them without knowing any feature type. + registrars := []http.RouteRegistrar{ + authHTTP.NewModule( + clientHandler, + tokenHandler, + auditLogHandler, + authz, + businessMetrics, + tokenRateLimitMiddleware, + ), + secretsHTTP.NewModule(secretHandler, authz, businessMetrics), + transitHTTP.NewModule(transitKeyHandler, cryptoHandler, authz, businessMetrics), + tokenizationHTTP.NewModule(tokenizationKeyHandler, tokenizationHandler, authz, businessMetrics), + } + + server.SetupRouter( + c.config, + registrars, + http.RouteMiddlewares{Auth: authMiddleware, RateLimit: rateLimitMiddleware}, + metricsProvider, + c.config.MetricsNamespace, + ) return server, nil } diff --git a/internal/auth/http/module.go b/internal/auth/http/module.go new file mode 100644 index 0000000..dbe7cad --- /dev/null +++ b/internal/auth/http/module.go @@ -0,0 +1,132 @@ +package http + +import ( + "github.com/gin-gonic/gin" + + authDomain "github.com/allisson/secrets/internal/auth/domain" + nethttp "github.com/allisson/secrets/internal/http" + "github.com/allisson/secrets/internal/metrics" +) + +// Module registers the authentication routes (token, clients, audit logs) and +// owns the collaborators those routes need, bound at construction. It also owns +// the token endpoint's irregular layout: /token skips the shared authentication +// middleware and uses its own IP-based rate limiter. +type Module struct { + clientHandler *ClientHandler + tokenHandler *TokenHandler + auditLogHandler *AuditLogHandler + authz *Authorizer + bm metrics.BusinessMetrics + tokenRateLimit gin.HandlerFunc +} + +// NewModule creates an auth route module. tokenRateLimit may be nil when +// IP-based rate limiting of the token endpoint is disabled. +func NewModule( + clientHandler *ClientHandler, + tokenHandler *TokenHandler, + auditLogHandler *AuditLogHandler, + authz *Authorizer, + bm metrics.BusinessMetrics, + tokenRateLimit gin.HandlerFunc, +) *Module { + return &Module{ + clientHandler: clientHandler, + tokenHandler: tokenHandler, + auditLogHandler: auditLogHandler, + authz: authz, + bm: bm, + tokenRateLimit: tokenRateLimit, + } +} + +// Register configures the v1 authentication-related endpoints on the given group. +func (m *Module) Register(v1 *gin.RouterGroup, mw nethttp.RouteMiddlewares) { + // Token issuance endpoint (no authentication required, IP-based rate limiting) + if m.tokenRateLimit != nil { + v1.POST( + "/token", + m.tokenRateLimit, + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "token_issue"), + m.tokenHandler.IssueTokenHandler, + ) + } else { + v1.POST( + "/token", + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "token_issue"), + m.tokenHandler.IssueTokenHandler, + ) + } + + // Token revocation endpoint (requires authentication) + v1.DELETE( + "/token", + mw.Auth, + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "token_revoke"), + m.tokenHandler.RevokeTokenHandler, + ) + + // Client management endpoints + clients := v1.Group("/clients") + clients.Use(mw.Auth) + if mw.RateLimit != nil { + clients.Use(mw.RateLimit) + } + { + clients.POST("", + m.authz.Require(authDomain.WriteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_create"), + m.clientHandler.CreateHandler, + ) + clients.GET("", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_list"), + m.clientHandler.ListHandler, + ) + clients.GET("/:id", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_get"), + m.clientHandler.GetHandler, + ) + clients.PUT("/:id", + m.authz.Require(authDomain.WriteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_update"), + m.clientHandler.UpdateHandler, + ) + clients.DELETE("/:id", + m.authz.Require(authDomain.DeleteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_delete"), + m.clientHandler.DeleteHandler, + ) + clients.POST("/:id/unlock", + m.authz.Require(authDomain.WriteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_unlock"), + m.clientHandler.UnlockHandler, + ) + clients.POST("/:id/rotate-secret", + m.authz.Require(authDomain.RotateCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_rotate_secret"), + m.clientHandler.RotateSecretHandler, + ) + clients.DELETE("/:id/tokens", + m.authz.Require(authDomain.DeleteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "client_revoke_tokens"), + m.clientHandler.RevokeTokensHandler, + ) + } + + // Audit log endpoints + auditLogs := v1.Group("/audit-logs") + auditLogs.Use(mw.Auth) + if mw.RateLimit != nil { + auditLogs.Use(mw.RateLimit) + } + { + auditLogs.GET("", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "auth", "audit_log_list"), + m.auditLogHandler.ListHandler, + ) + } +} diff --git a/internal/http/server.go b/internal/http/server.go index fabf62b..1f121f0 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -21,14 +21,8 @@ import ( "github.com/google/uuid" "golang.org/x/sync/singleflight" - authDomain "github.com/allisson/secrets/internal/auth/domain" - authHTTP "github.com/allisson/secrets/internal/auth/http" - authUseCase "github.com/allisson/secrets/internal/auth/usecase" "github.com/allisson/secrets/internal/config" "github.com/allisson/secrets/internal/metrics" - secretsHTTP "github.com/allisson/secrets/internal/secrets/http" - tokenizationHTTP "github.com/allisson/secrets/internal/tokenization/http" - transitHTTP "github.com/allisson/secrets/internal/transit/http" ) // Server represents the HTTP server. @@ -62,28 +56,33 @@ func NewServer( } } -// RouterDeps bundles the handler and usecase dependencies required by SetupRouter. -// Using a struct instead of positional parameters prevents silent field-swap bugs -// and makes adding a new handler a named-field change rather than a positional one. -type RouterDeps struct { - ClientHandler *authHTTP.ClientHandler - TokenHandler *authHTTP.TokenHandler - AuditLogHandler *authHTTP.AuditLogHandler - SecretHandler *secretsHTTP.SecretHandler - TransitKeyHandler *transitHTTP.TransitKeyHandler - CryptoHandler *transitHTTP.CryptoHandler - TokenizationKeyHandler *tokenizationHTTP.TokenizationKeyHandler - TokenizationHandler *tokenizationHTTP.TokenizationHandler - TokenUseCase authUseCase.TokenUseCase - AuditLogUseCase authUseCase.AuditLogUseCase - BusinessMetrics metrics.BusinessMetrics - MetricsProvider *metrics.Provider - MetricsNamespace string +// RouteMiddlewares carries the shared per-route middleware a feature module +// applies to its route groups. Both fields are plain gin.HandlerFunc so this +// package stays free of any feature-specific types. RateLimit may be nil when +// rate limiting is disabled. +type RouteMiddlewares struct { + Auth gin.HandlerFunc + RateLimit gin.HandlerFunc } -// SetupRouter configures the Gin router with all routes and middleware. -// This method is called during server initialization with all required dependencies. -func (s *Server) SetupRouter(ctx context.Context, cfg *config.Config, deps RouterDeps) { +// RouteRegistrar is implemented by each feature's route module. The composition +// root builds the concrete modules (capturing their handlers, authorizer, and +// business metrics) and hands them to SetupRouter, which knows nothing about +// any feature package. +type RouteRegistrar interface { + Register(v1 *gin.RouterGroup, mw RouteMiddlewares) +} + +// SetupRouter configures the Gin router with the global middleware chain and +// mounts each feature module under /v1. All feature-specific wiring lives in the +// registrars; this method is feature-agnostic. +func (s *Server) SetupRouter( + cfg *config.Config, + registrars []RouteRegistrar, + mw RouteMiddlewares, + metricsProvider *metrics.Provider, + metricsNamespace string, +) { // Create Gin engine without default middleware router := gin.New() @@ -106,402 +105,23 @@ func (s *Server) SetupRouter(ctx context.Context, cfg *config.Config, deps Route router.Use(CustomLoggerMiddleware(s.logger)) // Custom slog logger // Add HTTP metrics middleware if metrics are enabled - if deps.MetricsProvider != nil { - router.Use(metrics.HTTPMetricsMiddleware(deps.MetricsProvider.MeterProvider(), deps.MetricsNamespace)) + if metricsProvider != nil { + router.Use(metrics.HTTPMetricsMiddleware(metricsProvider.MeterProvider(), metricsNamespace)) } // Health and readiness endpoints (outside API versioning) router.GET("/health", s.healthHandler) router.GET("/ready", s.readinessHandler) - // API v1 routes + // API v1 routes: each feature module mounts itself. v1 := router.Group("/v1") - { - // Create authentication middleware - authMiddleware := authHTTP.AuthenticationMiddleware( - deps.TokenUseCase, - s.logger, - ) - - // Create rate limit middleware - var rateLimitMiddleware gin.HandlerFunc - if cfg.RateLimitEnabled { - rateLimitMiddleware = authHTTP.RateLimitMiddleware( - ctx, - cfg.RateLimitRequestsPerSec, - cfg.RateLimitBurst, - s.logger, - ) - } - - // Build the per-route authorizer once; it pre-binds audit + logger so - // route registrations only carry the capability. - authz := authHTTP.NewAuthorizer(deps.AuditLogUseCase, s.logger) - - s.registerAuthRoutes( - ctx, - v1, - cfg, - deps.ClientHandler, - deps.TokenHandler, - deps.AuditLogHandler, - authMiddleware, - rateLimitMiddleware, - authz, - deps.BusinessMetrics, - ) - s.registerSecretRoutes( - v1, - deps.SecretHandler, - authMiddleware, - rateLimitMiddleware, - authz, - deps.BusinessMetrics, - ) - s.registerTransitRoutes( - v1, - deps.TransitKeyHandler, - deps.CryptoHandler, - authMiddleware, - rateLimitMiddleware, - authz, - deps.BusinessMetrics, - ) - s.registerTokenizationRoutes( - v1, - deps.TokenizationKeyHandler, - deps.TokenizationHandler, - authMiddleware, - rateLimitMiddleware, - authz, - deps.BusinessMetrics, - ) + for _, r := range registrars { + r.Register(v1, mw) } s.router = router } -// registerAuthRoutes configures the v1 authentication-related endpoints. -func (s *Server) registerAuthRoutes( - ctx context.Context, - v1 *gin.RouterGroup, - cfg *config.Config, - clientHandler *authHTTP.ClientHandler, - tokenHandler *authHTTP.TokenHandler, - auditLogHandler *authHTTP.AuditLogHandler, - authMiddleware gin.HandlerFunc, - rateLimitMiddleware gin.HandlerFunc, - authz *authHTTP.Authorizer, - bm metrics.BusinessMetrics, -) { - // Create token rate limit middleware (IP-based, for unauthenticated token endpoint) - var tokenRateLimitMiddleware gin.HandlerFunc - if cfg.RateLimitTokenEnabled { - tokenRateLimitMiddleware = authHTTP.TokenRateLimitMiddleware( - ctx, - cfg.RateLimitTokenRequestsPerSec, - cfg.RateLimitTokenBurst, - s.logger, - ) - } - - // Token issuance endpoint (no authentication required, IP-based rate limiting) - if tokenRateLimitMiddleware != nil { - v1.POST( - "/token", - tokenRateLimitMiddleware, - BusinessMetricsMiddleware(bm, "auth", "token_issue"), - tokenHandler.IssueTokenHandler, - ) - } else { - v1.POST( - "/token", - BusinessMetricsMiddleware(bm, "auth", "token_issue"), - tokenHandler.IssueTokenHandler, - ) - } - - // Token revocation endpoint (requires authentication) - v1.DELETE( - "/token", - authMiddleware, - BusinessMetricsMiddleware(bm, "auth", "token_revoke"), - tokenHandler.RevokeTokenHandler, - ) - - // Client management endpoints - clients := v1.Group("/clients") - clients.Use(authMiddleware) - if rateLimitMiddleware != nil { - clients.Use(rateLimitMiddleware) - } - { - clients.POST("", - authz.Require(authDomain.WriteCapability), - BusinessMetricsMiddleware(bm, "auth", "client_create"), - clientHandler.CreateHandler, - ) - clients.GET("", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "auth", "client_list"), - clientHandler.ListHandler, - ) - clients.GET("/:id", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "auth", "client_get"), - clientHandler.GetHandler, - ) - clients.PUT("/:id", - authz.Require(authDomain.WriteCapability), - BusinessMetricsMiddleware(bm, "auth", "client_update"), - clientHandler.UpdateHandler, - ) - clients.DELETE("/:id", - authz.Require(authDomain.DeleteCapability), - BusinessMetricsMiddleware(bm, "auth", "client_delete"), - clientHandler.DeleteHandler, - ) - clients.POST("/:id/unlock", - authz.Require(authDomain.WriteCapability), - BusinessMetricsMiddleware(bm, "auth", "client_unlock"), - clientHandler.UnlockHandler, - ) - clients.POST("/:id/rotate-secret", - authz.Require(authDomain.RotateCapability), - BusinessMetricsMiddleware(bm, "auth", "client_rotate_secret"), - clientHandler.RotateSecretHandler, - ) - clients.DELETE("/:id/tokens", - authz.Require(authDomain.DeleteCapability), - BusinessMetricsMiddleware(bm, "auth", "client_revoke_tokens"), - clientHandler.RevokeTokensHandler, - ) - } - - // Audit log endpoints - auditLogs := v1.Group("/audit-logs") - auditLogs.Use(authMiddleware) - if rateLimitMiddleware != nil { - auditLogs.Use(rateLimitMiddleware) - } - { - auditLogs.GET("", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "auth", "audit_log_list"), - auditLogHandler.ListHandler, - ) - } -} - -// registerSecretRoutes configures the v1 secret-related endpoints. -func (s *Server) registerSecretRoutes( - v1 *gin.RouterGroup, - secretHandler *secretsHTTP.SecretHandler, - authMiddleware gin.HandlerFunc, - rateLimitMiddleware gin.HandlerFunc, - authz *authHTTP.Authorizer, - bm metrics.BusinessMetrics, -) { - // Secret management endpoints - secrets := v1.Group("/secrets") - secrets.Use(authMiddleware) - if rateLimitMiddleware != nil { - secrets.Use(rateLimitMiddleware) - } - { - secrets.GET("", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "secrets", "secret_list"), - secretHandler.ListHandler, - ) - secrets.POST("/*path", - authz.Require(authDomain.EncryptCapability), - BusinessMetricsMiddleware(bm, "secrets", "secret_create_or_update"), - secretHandler.CreateOrUpdateHandler, - ) - secrets.GET("/*path", - authz.Require(authDomain.DecryptCapability), - BusinessMetricsMiddleware(bm, "secrets", "secret_get"), - secretHandler.GetHandler, - ) - secrets.DELETE("/*path", - authz.Require(authDomain.DeleteCapability), - BusinessMetricsMiddleware(bm, "secrets", "secret_delete"), - secretHandler.DeleteHandler, - ) - } -} - -// registerTransitRoutes configures the v1 transit-encryption-related endpoints. -func (s *Server) registerTransitRoutes( - v1 *gin.RouterGroup, - transitKeyHandler *transitHTTP.TransitKeyHandler, - cryptoHandler *transitHTTP.CryptoHandler, - authMiddleware gin.HandlerFunc, - rateLimitMiddleware gin.HandlerFunc, - authz *authHTTP.Authorizer, - bm metrics.BusinessMetrics, -) { - // Transit encryption endpoints - transit := v1.Group("/transit") - transit.Use(authMiddleware) - if rateLimitMiddleware != nil { - transit.Use(rateLimitMiddleware) - } - { - keys := transit.Group("/keys") - { - // List transit keys - keys.GET("", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "transit", "transit_key_list"), - transitKeyHandler.ListHandler, - ) - - // Get individual transit key - keys.GET("/:name", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "transit", "transit_key_get"), - transitKeyHandler.GetHandler, - ) - - // Create new transit key - keys.POST("", - authz.Require(authDomain.WriteCapability), - BusinessMetricsMiddleware(bm, "transit", "transit_key_create"), - transitKeyHandler.CreateHandler, - ) - - // Rotate transit key to new version - keys.POST("/:name/rotate", - authz.Require(authDomain.RotateCapability), - BusinessMetricsMiddleware(bm, "transit", "transit_key_rotate"), - transitKeyHandler.RotateHandler, - ) - - // Delete transit key - keys.DELETE("/:name", - authz.Require(authDomain.DeleteCapability), - BusinessMetricsMiddleware(bm, "transit", "transit_key_delete"), - transitKeyHandler.DeleteHandler, - ) - - // Encrypt plaintext with transit key - keys.POST("/:name/encrypt", - authz.Require(authDomain.EncryptCapability), - BusinessMetricsMiddleware(bm, "transit", "transit_encrypt"), - cryptoHandler.EncryptHandler, - ) - - // Decrypt ciphertext with transit key - keys.POST("/:name/decrypt", - authz.Require(authDomain.DecryptCapability), - BusinessMetricsMiddleware(bm, "transit", "transit_decrypt"), - cryptoHandler.DecryptHandler, - ) - } - } -} - -// registerTokenizationRoutes configures the v1 tokenization-related endpoints. -func (s *Server) registerTokenizationRoutes( - v1 *gin.RouterGroup, - tokenizationKeyHandler *tokenizationHTTP.TokenizationKeyHandler, - tokenizationHandler *tokenizationHTTP.TokenizationHandler, - authMiddleware gin.HandlerFunc, - rateLimitMiddleware gin.HandlerFunc, - authz *authHTTP.Authorizer, - bm metrics.BusinessMetrics, -) { - // Tokenization endpoints - tokenization := v1.Group("/tokenization") - tokenization.Use(authMiddleware) - if rateLimitMiddleware != nil { - tokenization.Use(rateLimitMiddleware) - } - { - keys := tokenization.Group("/keys") - { - // List tokenization keys - keys.GET("", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenization_key_list"), - tokenizationKeyHandler.ListHandler, - ) - - // Get individual tokenization key - keys.GET("/:name", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenization_key_get"), - tokenizationKeyHandler.GetByNameHandler, - ) - - // Create new tokenization key - keys.POST("", - authz.Require(authDomain.WriteCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenization_key_create"), - tokenizationKeyHandler.CreateHandler, - ) - - // Rotate tokenization key to new version - keys.POST("/:name/rotate", - authz.Require(authDomain.RotateCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenization_key_rotate"), - tokenizationKeyHandler.RotateHandler, - ) - - // Delete tokenization key - keys.DELETE("/:name", - authz.Require(authDomain.DeleteCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenization_key_delete"), - tokenizationKeyHandler.DeleteHandler, - ) - - // Tokenize plaintext with tokenization key - keys.POST("/:name/tokenize", - authz.Require(authDomain.EncryptCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenize"), - tokenizationHandler.TokenizeHandler, - ) - - // Tokenize batch of plaintexts with tokenization key - keys.POST("/:name/tokenize-batch", - authz.Require(authDomain.EncryptCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenize_batch"), - tokenizationHandler.TokenizeBatchHandler, - ) - } - - // Detokenize token to retrieve plaintext - tokenization.POST("/detokenize", - authz.Require(authDomain.DecryptCapability), - BusinessMetricsMiddleware(bm, "tokenization", "detokenize"), - tokenizationHandler.DetokenizeHandler, - ) - - // Detokenize batch of tokens to retrieve plaintexts - tokenization.POST("/detokenize-batch", - authz.Require(authDomain.DecryptCapability), - BusinessMetricsMiddleware(bm, "tokenization", "detokenize_batch"), - tokenizationHandler.DetokenizeBatchHandler, - ) - - // Validate token existence and validity - tokenization.POST("/validate", - authz.Require(authDomain.ReadCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenize_validate"), - tokenizationHandler.ValidateHandler, - ) - - // Revoke token to prevent further detokenization - tokenization.POST("/revoke", - authz.Require(authDomain.DeleteCapability), - BusinessMetricsMiddleware(bm, "tokenization", "tokenize_revoke"), - tokenizationHandler.RevokeHandler, - ) - } -} - // GetHandler returns the http.Handler for testing purposes. // Returns nil if SetupRouter has not been called yet. func (s *Server) GetHandler() http.Handler { diff --git a/internal/http/server_routes_test.go b/internal/http/server_routes_test.go new file mode 100644 index 0000000..3f129a0 --- /dev/null +++ b/internal/http/server_routes_test.go @@ -0,0 +1,102 @@ +package http_test + +import ( + "io" + "log/slog" + "sort" + "testing" + "time" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + authHTTP "github.com/allisson/secrets/internal/auth/http" + "github.com/allisson/secrets/internal/config" + nethttp "github.com/allisson/secrets/internal/http" + "github.com/allisson/secrets/internal/metrics" + secretsHTTP "github.com/allisson/secrets/internal/secrets/http" + tokenizationHTTP "github.com/allisson/secrets/internal/tokenization/http" + transitHTTP "github.com/allisson/secrets/internal/transit/http" +) + +// goldenRoutes is the complete method+path inventory the router must expose. +// It is the guardrail for behavior-preserving route refactors: any route that is +// dropped, renamed, or moved to a different verb fails TestRouteManifest. +var goldenRoutes = []string{ + "DELETE /v1/clients/:id", + "DELETE /v1/clients/:id/tokens", + "DELETE /v1/secrets/*path", + "DELETE /v1/token", + "DELETE /v1/tokenization/keys/:name", + "DELETE /v1/transit/keys/:name", + "GET /health", + "GET /ready", + "GET /v1/audit-logs", + "GET /v1/clients", + "GET /v1/clients/:id", + "GET /v1/secrets", + "GET /v1/secrets/*path", + "GET /v1/tokenization/keys", + "GET /v1/tokenization/keys/:name", + "GET /v1/transit/keys", + "GET /v1/transit/keys/:name", + "POST /v1/clients", + "POST /v1/clients/:id/rotate-secret", + "POST /v1/clients/:id/unlock", + "POST /v1/secrets/*path", + "POST /v1/token", + "POST /v1/tokenization/detokenize", + "POST /v1/tokenization/detokenize-batch", + "POST /v1/tokenization/keys", + "POST /v1/tokenization/keys/:name/rotate", + "POST /v1/tokenization/keys/:name/tokenize", + "POST /v1/tokenization/keys/:name/tokenize-batch", + "POST /v1/tokenization/revoke", + "POST /v1/tokenization/validate", + "POST /v1/transit/keys", + "POST /v1/transit/keys/:name/decrypt", + "POST /v1/transit/keys/:name/encrypt", + "POST /v1/transit/keys/:name/rotate", + "PUT /v1/clients/:id", +} + +// buildManifestRouter constructs the full router with nil-backed handlers. +// Route registration only binds handler method values; it never invokes them, +// so nil handler pointers are safe here and let the manifest test avoid mocks. +func buildManifestRouter(t *testing.T) *gin.Engine { + t.Helper() + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + srv := nethttp.NewServer(nil, "localhost", 0, time.Second, time.Second, time.Second, logger) + + bm := metrics.NewNopBusinessMetrics() + authz := authHTTP.NewAuthorizer(nil, logger) + registrars := []nethttp.RouteRegistrar{ + authHTTP.NewModule(nil, nil, nil, authz, bm, nil), + secretsHTTP.NewModule(nil, authz, bm), + transitHTTP.NewModule(nil, nil, authz, bm), + tokenizationHTTP.NewModule(nil, nil, authz, bm), + } + srv.SetupRouter(&config.Config{}, registrars, nethttp.RouteMiddlewares{}, nil, "") + + eng, ok := srv.GetHandler().(*gin.Engine) + require.True(t, ok, "expected *gin.Engine from GetHandler") + return eng +} + +// actualRoutes returns the sorted "METHOD path" inventory of the router. +func actualRoutes(eng *gin.Engine) []string { + routes := eng.Routes() + got := make([]string, 0, len(routes)) + for _, r := range routes { + got = append(got, r.Method+" "+r.Path) + } + sort.Strings(got) + return got +} + +// TestRouteManifest asserts the registered route set matches the golden inventory. +func TestRouteManifest(t *testing.T) { + got := actualRoutes(buildManifestRouter(t)) + assert.Equal(t, goldenRoutes, got) +} diff --git a/internal/secrets/http/module.go b/internal/secrets/http/module.go new file mode 100644 index 0000000..b209db8 --- /dev/null +++ b/internal/secrets/http/module.go @@ -0,0 +1,54 @@ +package http + +import ( + "github.com/gin-gonic/gin" + + authDomain "github.com/allisson/secrets/internal/auth/domain" + authHTTP "github.com/allisson/secrets/internal/auth/http" + nethttp "github.com/allisson/secrets/internal/http" + "github.com/allisson/secrets/internal/metrics" +) + +// Module registers the secret-management routes and owns the collaborators +// those routes need (authorizer + business metrics), bound at construction. +type Module struct { + handler *SecretHandler + authz *authHTTP.Authorizer + bm metrics.BusinessMetrics +} + +// NewModule creates a secrets route module. +func NewModule(handler *SecretHandler, authz *authHTTP.Authorizer, bm metrics.BusinessMetrics) *Module { + return &Module{handler: handler, authz: authz, bm: bm} +} + +// Register configures the v1 secret-related endpoints on the given group. +func (m *Module) Register(v1 *gin.RouterGroup, mw nethttp.RouteMiddlewares) { + secrets := v1.Group("/secrets") + secrets.Use(mw.Auth) + if mw.RateLimit != nil { + secrets.Use(mw.RateLimit) + } + { + secrets.GET("", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "secrets", "secret_list"), + m.handler.ListHandler, + ) + secrets.POST("/*path", + m.authz.Require(authDomain.EncryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "secrets", "secret_create_or_update"), + m.handler.CreateOrUpdateHandler, + ) + secrets.GET("/*path", + m.authz.Require(authDomain.DecryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "secrets", "secret_get"), + m.handler.GetHandler, + ) + secrets.DELETE("/*path", + m.authz.Require(authDomain.DeleteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "secrets", "secret_delete"), + m.handler.DeleteHandler, + ) + } +} diff --git a/internal/tokenization/http/module.go b/internal/tokenization/http/module.go new file mode 100644 index 0000000..5c0d2fc --- /dev/null +++ b/internal/tokenization/http/module.go @@ -0,0 +1,119 @@ +package http + +import ( + "github.com/gin-gonic/gin" + + authDomain "github.com/allisson/secrets/internal/auth/domain" + authHTTP "github.com/allisson/secrets/internal/auth/http" + nethttp "github.com/allisson/secrets/internal/http" + "github.com/allisson/secrets/internal/metrics" +) + +// Module registers the tokenization routes and owns the collaborators those +// routes need (authorizer + business metrics), bound at construction. +type Module struct { + keyHandler *TokenizationKeyHandler + tokenizationHandler *TokenizationHandler + authz *authHTTP.Authorizer + bm metrics.BusinessMetrics +} + +// NewModule creates a tokenization route module. +func NewModule( + keyHandler *TokenizationKeyHandler, + tokenizationHandler *TokenizationHandler, + authz *authHTTP.Authorizer, + bm metrics.BusinessMetrics, +) *Module { + return &Module{keyHandler: keyHandler, tokenizationHandler: tokenizationHandler, authz: authz, bm: bm} +} + +// Register configures the v1 tokenization-related endpoints on the given group. +func (m *Module) Register(v1 *gin.RouterGroup, mw nethttp.RouteMiddlewares) { + tokenization := v1.Group("/tokenization") + tokenization.Use(mw.Auth) + if mw.RateLimit != nil { + tokenization.Use(mw.RateLimit) + } + { + keys := tokenization.Group("/keys") + { + // List tokenization keys + keys.GET("", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenization_key_list"), + m.keyHandler.ListHandler, + ) + + // Get individual tokenization key + keys.GET("/:name", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenization_key_get"), + m.keyHandler.GetByNameHandler, + ) + + // Create new tokenization key + keys.POST("", + m.authz.Require(authDomain.WriteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenization_key_create"), + m.keyHandler.CreateHandler, + ) + + // Rotate tokenization key to new version + keys.POST("/:name/rotate", + m.authz.Require(authDomain.RotateCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenization_key_rotate"), + m.keyHandler.RotateHandler, + ) + + // Delete tokenization key + keys.DELETE("/:name", + m.authz.Require(authDomain.DeleteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenization_key_delete"), + m.keyHandler.DeleteHandler, + ) + + // Tokenize plaintext with tokenization key + keys.POST("/:name/tokenize", + m.authz.Require(authDomain.EncryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenize"), + m.tokenizationHandler.TokenizeHandler, + ) + + // Tokenize batch of plaintexts with tokenization key + keys.POST("/:name/tokenize-batch", + m.authz.Require(authDomain.EncryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenize_batch"), + m.tokenizationHandler.TokenizeBatchHandler, + ) + } + + // Detokenize token to retrieve plaintext + tokenization.POST("/detokenize", + m.authz.Require(authDomain.DecryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "detokenize"), + m.tokenizationHandler.DetokenizeHandler, + ) + + // Detokenize batch of tokens to retrieve plaintexts + tokenization.POST("/detokenize-batch", + m.authz.Require(authDomain.DecryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "detokenize_batch"), + m.tokenizationHandler.DetokenizeBatchHandler, + ) + + // Validate token existence and validity + tokenization.POST("/validate", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenize_validate"), + m.tokenizationHandler.ValidateHandler, + ) + + // Revoke token to prevent further detokenization + tokenization.POST("/revoke", + m.authz.Require(authDomain.DeleteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "tokenization", "tokenize_revoke"), + m.tokenizationHandler.RevokeHandler, + ) + } +} diff --git a/internal/transit/http/module.go b/internal/transit/http/module.go new file mode 100644 index 0000000..7334e3b --- /dev/null +++ b/internal/transit/http/module.go @@ -0,0 +1,91 @@ +package http + +import ( + "github.com/gin-gonic/gin" + + authDomain "github.com/allisson/secrets/internal/auth/domain" + authHTTP "github.com/allisson/secrets/internal/auth/http" + nethttp "github.com/allisson/secrets/internal/http" + "github.com/allisson/secrets/internal/metrics" +) + +// Module registers the transit-encryption routes and owns the collaborators +// those routes need (authorizer + business metrics), bound at construction. +type Module struct { + keyHandler *TransitKeyHandler + cryptoHandler *CryptoHandler + authz *authHTTP.Authorizer + bm metrics.BusinessMetrics +} + +// NewModule creates a transit route module. +func NewModule( + keyHandler *TransitKeyHandler, + cryptoHandler *CryptoHandler, + authz *authHTTP.Authorizer, + bm metrics.BusinessMetrics, +) *Module { + return &Module{keyHandler: keyHandler, cryptoHandler: cryptoHandler, authz: authz, bm: bm} +} + +// Register configures the v1 transit-encryption-related endpoints on the given group. +func (m *Module) Register(v1 *gin.RouterGroup, mw nethttp.RouteMiddlewares) { + transit := v1.Group("/transit") + transit.Use(mw.Auth) + if mw.RateLimit != nil { + transit.Use(mw.RateLimit) + } + { + keys := transit.Group("/keys") + { + // List transit keys + keys.GET("", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "transit", "transit_key_list"), + m.keyHandler.ListHandler, + ) + + // Get individual transit key + keys.GET("/:name", + m.authz.Require(authDomain.ReadCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "transit", "transit_key_get"), + m.keyHandler.GetHandler, + ) + + // Create new transit key + keys.POST("", + m.authz.Require(authDomain.WriteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "transit", "transit_key_create"), + m.keyHandler.CreateHandler, + ) + + // Rotate transit key to new version + keys.POST("/:name/rotate", + m.authz.Require(authDomain.RotateCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "transit", "transit_key_rotate"), + m.keyHandler.RotateHandler, + ) + + // Delete transit key + keys.DELETE("/:name", + m.authz.Require(authDomain.DeleteCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "transit", "transit_key_delete"), + m.keyHandler.DeleteHandler, + ) + + // Encrypt plaintext with transit key + keys.POST("/:name/encrypt", + m.authz.Require(authDomain.EncryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "transit", "transit_encrypt"), + m.cryptoHandler.EncryptHandler, + ) + + // Decrypt ciphertext with transit key + keys.POST("/:name/decrypt", + m.authz.Require(authDomain.DecryptCapability), + nethttp.BusinessMetricsMiddleware(m.bm, "transit", "transit_decrypt"), + m.cryptoHandler.DecryptHandler, + ) + } + } +}