Shared Gin middleware for the IlonaPay API
gateway. The package provides:
- required merchant integration-header checks;
- merchant API-key validation through a consumer-owned repository;
- public-client IP allowlisting through a consumer-owned repository; and
- typed helpers for API-key context.
The package does not own database models, open database connections, register
routes, or authenticate the merchant integration headers.
Installation
For a published version:
go get github.com/mawarpay/pkg-gwmiddleware@latest
API
Middleware
| Function |
Result |
RequireHeaders(names...) |
Requires every named header to contain a non-whitespace value |
RequireMerchantAPIHeaders() |
Requires X-MERCHANT-ID, X-PAY-METHOD-ID, and X-USER-ID |
APIKey(repo, cfg) |
Validates the configured API-key header and adds merchant data to the Gin context |
IPWhitelist(repo) |
Bypasses loopback/private addresses and checks public addresses with the repository |
Context helpers
These helpers return data set by a successful APIKey call:
| Function |
Result |
GetMerchantID(c) |
Validated merchant_id and whether it exists with the expected uint64 type |
GetApiKeyRecord(c) |
Consumer-specific ApiKeyInfo.Record and whether it exists |
| Constant |
Value |
HeaderMerchantID |
X-MERCHANT-ID |
HeaderPayMethodID |
X-PAY-METHOD-ID |
HeaderUserID |
X-USER-ID |
DefaultAPIKeyHeader |
X-API-KEY |
Usage
Implement the repository interfaces in the consuming gateway. This package
deliberately stays independent of gateway entities and database packages.
type apiKeyRepo struct {
// Site-database dependency for api_keys.
}
func (r *apiKeyRepo) GetByKeyAndValidAt(
ctx context.Context,
key string,
at time.Time,
) (gwmiddleware.ApiKeyInfo, error) {
// Translate the gateway entity into ApiKeyInfo.
}
func (r *apiKeyRepo) UpdateLastUsedAt(
ctx context.Context,
id uint64,
at time.Time,
) error {
// Persist last_used_at.
}
type ipWhitelistRepo struct {
// Site-database dependency for ip_whitelists.
}
func (r *ipWhitelistRepo) IsAllowed(
ctx context.Context,
ip netip.Addr,
at time.Time,
) (bool, error) {
// Return true only for an active matching entry.
}
Mount only the policies required by a route or route group:
import gwmiddleware "github.com/mawarpay/pkg-gwmiddleware"
merchant := router.Group("/api/v2")
// Presence validation only; see Security boundaries below.
merchant.Use(gwmiddleware.RequireMerchantAPIHeaders())
// Optional policies after their repository adapters are wired.
merchant.Use(gwmiddleware.IPWhitelist(ipWhitelistRepo))
merchant.Use(gwmiddleware.APIKey(apiKeyRepo, gwmiddleware.APIKeyConfig{
Header: gwmiddleware.DefaultAPIKeyHeader,
UpdateUsage: true,
}))
ApiKeyInfo.Record may contain a gateway-specific entity. Retrieve it with
GetApiKeyRecord and type-assert it in the consumer.
Runtime behaviour
| Middleware |
Condition |
Result |
RequireHeaders |
A header is absent, empty, or whitespace-only |
Aborts with 422 on the first missing header |
APIKey |
Repository is nil |
Aborts with 503 |
APIKey |
API-key header is missing |
Aborts with 422 |
APIKey |
Lookup fails or returns ApiKeyInfo.ID == 0 |
Aborts with 422 as invalid or expired |
APIKey |
UpdateUsage is enabled |
Updates usage synchronously with a 300 ms timeout |
APIKey |
Usage update fails |
Logs the failure and continues the request |
IPWhitelist |
Address is loopback or private |
Continues without a repository lookup |
IPWhitelist |
Client IP cannot be parsed |
Aborts with 403 |
IPWhitelist |
Public-IP lookup fails or denies access |
Aborts with 403 |
IPWhitelist requires a non-nil repository for requests from public
addresses. Repository errors fail closed.
Security boundaries
RequireMerchantAPIHeaders validates presence only. It does not verify
header values, compare X-MERCHANT-ID with API-key context, or authenticate
the caller.
APIKey validates the key and stores merchant context, but it does not
rewrite or validate the three merchant integration headers.
IPWhitelist uses Gin's ClientIP(). The gateway must configure trusted
proxies correctly before relying on forwarded client-IP headers.
- Loopback and private addresses bypass the IP repository by design. Account
for that trust boundary in the deployment network.
- Do not log API-key values. Current middleware logs only key and merchant IDs.
Current gateway integration
The source of truth is
api-gateway/internal/routes/routes.go.
At present, merchant routes use RequireMerchantAPIHeaders() through the
authMerchantHeaders policy. APIKey and IPWhitelist are available here but
are not mounted by the gateway; they require repository adapters and gateway
tests before activation.
AI contributor context
Use this section when an AI coding agent or a new contributor changes this
package.
Package map
| File |
Responsibility |
headers.go |
Integration-header constants and presence middleware |
api_key.go |
API-key validation, usage updates, and context helpers |
ip_whitelist.go |
Client-IP parsing and allowlist decisions |
types.go |
Public configuration, repository interfaces, and context types |
*_test.go |
Executable behaviour contract with repository fakes |
../../api-gateway/internal/routes/routes.go |
Actual gateway policy wiring |
Invariants
- Keep storage and service entities outside this module; extend the small
repository interfaces only when required by middleware behaviour.
- Treat
routes.go, not README route examples, as the source of truth for
active gateway policies.
- Do not describe merchant integration headers as authentication. Presence
checks and API-key validation are separate controls.
- Preserve fail-closed behaviour for public-IP lookup errors and invalid
addresses.
- Keep context keys private and expose context data through helper functions.
- Add or update focused unit tests for every status, control-flow, or context
behaviour change.
- If gateway wiring changes, update the gateway route tests and the matching
platform documentation in the monorepo.
Verification
From this directory:
gofmt -w *.go
go vet ./...
go test ./...
Tests use mocked repositories and require neither MySQL nor Redis.