2026-05-07 22:18:04 -06:00
|
|
|
# Changelog
|
|
|
|
|
|
|
|
|
|
All notable changes to `code.nochebuena.dev/go/httpauth-jwt` are documented here.
|
|
|
|
|
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
|
|
|
|
fix(httpauth-jwt): WithJSONNumber in Verify, JSON errors in AuthMiddleware, bump httpauth v1.0.2
Pass jwt.WithJSONNumber() to jwt.Parse in hmacSigner.Verify, rsaSigner.Verify, and
rsaPublicVerifier.Verify. JWT numbers (including permission bitmasks) are now decoded
as json.Number instead of float64 — exact parsing for all integers including
math.MaxInt64 (the ADMIN wildcard mask). Float64 would round MaxInt64 to 2^63 and
overflow back to math.MinInt64 on cast to int64.
AuthMiddleware now calls httpauth.WriteJSONError for all 401 responses instead of
http.Error. Standardizes the error format across the full httpauth-* middleware stack:
AuthMiddleware (httpauth-jwt), EnrichmentMiddleware, and AuthzMiddleware (httpauth)
all produce {"code":"UNAUTHENTICATED"|"PERMISSION_DENIED"|"INTERNAL","message":"..."}.
Update two existing tests (TestIssueTokenPair_CustomClaims,
TestRefreshTokenPair_CustomClaimsInNewToken) to assert json.Number instead of float64
— they were correct before WithJSONNumber, now reflect the actual decoded type.
Add TestVerify_JSONNumberPreservesMaxInt64: issues a token with math.MaxInt64 as a
bitmask, verifies it, and asserts the decoded value is exactly math.MaxInt64 via
json.Number.Int64() — proving no float64 round-trip occurs.
Add TestAuthMiddleware_UnauthorizedJSON: asserts the 401 response is
Content-Type: application/json with body {"code":"UNAUTHENTICATED","message":"..."}.
Bump httpauth dependency to v1.0.2.
2026-05-18 14:38:28 -06:00
|
|
|
## [1.0.2] — 2026-05-18
|
|
|
|
|
|
|
|
|
|
### Changed
|
|
|
|
|
|
|
|
|
|
- All three `Verify` implementations (`hmacSigner`, `rsaSigner`, `rsaPublicVerifier`)
|
|
|
|
|
now pass `jwt.WithJSONNumber()` to `jwt.Parse`. JWT numeric claims — including
|
|
|
|
|
permission mask bitmasks — are decoded as `json.Number` instead of `float64`,
|
|
|
|
|
preserving exact bit patterns for values up to and including `math.MaxInt64`.
|
|
|
|
|
- `AuthMiddleware` now returns JSON error bodies instead of plain-text `http.Error`
|
|
|
|
|
for missing/invalid/expired Bearer tokens. Uses `httpauth.WriteJSONError` — the
|
|
|
|
|
same helper as `EnrichmentMiddleware` and `AuthzMiddleware` — ensuring a consistent
|
|
|
|
|
`{"code":"UNAUTHENTICATED","message":"unauthorized"}` response across the full
|
|
|
|
|
middleware stack.
|
|
|
|
|
- Dependency `code.nochebuena.dev/go/httpauth` bumped to v1.0.2.
|
|
|
|
|
|
|
|
|
|
[1.0.2]: https://code.nochebuena.dev/go/httpauth-jwt/releases/tag/v1.0.2
|
|
|
|
|
|
2026-05-07 22:18:04 -06:00
|
|
|
## [1.0.0] — 2026-05-08
|
|
|
|
|
|
2026-05-07 23:51:16 -06:00
|
|
|
### Changed
|
|
|
|
|
|
|
|
|
|
- Package renamed from `jwtauth` to `httpauthjwt` — follows ecosystem convention
|
|
|
|
|
(`repo name = package name`, hyphens removed); import path is unchanged
|
|
|
|
|
(`code.nochebuena.dev/go/httpauth-jwt`); update all usages from `jwtauth.X` to
|
|
|
|
|
`httpauthjwt.X`
|
|
|
|
|
- Dependency `code.nochebuena.dev/go/httpauth` bumped to v1.0.0
|
|
|
|
|
- Dependency `code.nochebuena.dev/go/rbac` bumped to v1.0.0 (indirect)
|
|
|
|
|
|
2026-05-07 22:18:04 -06:00
|
|
|
### Added
|
|
|
|
|
|
|
|
|
|
**`Verifier` interface** — validates JWT strings. Narrowest interface; `AuthMiddleware`
|
|
|
|
|
accepts this so services that only verify (not issue) tokens pass a public-key verifier.
|
|
|
|
|
|
|
|
|
|
**`Signer` interface** — embeds `Verifier` and adds `Sign(jwt.Claims)`. Used by
|
|
|
|
|
`IssueTokenPair` and `RefreshTokenPair`.
|
|
|
|
|
|
|
|
|
|
**`NewHMACSigner(secret []byte) Signer`** — HMAC-SHA256. For single-service or
|
|
|
|
|
monolith deployments where one process both issues and verifies tokens.
|
|
|
|
|
|
|
|
|
|
**`NewRSASigner(privateKey *rsa.PrivateKey) Signer`** — RSA-SHA256 signer + verifier
|
|
|
|
|
backed by the private key (public key derived automatically).
|
|
|
|
|
|
|
|
|
|
**`NewRSASignerFromPEM(pemKey []byte) (Signer, error)`** — loads a PKCS#8 or PKCS#1
|
|
|
|
|
PEM-encoded RSA private key. Suitable for loading from environment variables or files.
|
|
|
|
|
|
|
|
|
|
**`NewRSAPublicKeyVerifier(publicKey *rsa.PublicKey) Verifier`** — RSA-SHA256
|
|
|
|
|
verifier backed by a public key only. For microservices that receive tokens from a
|
|
|
|
|
central issuer but never sign them.
|
|
|
|
|
|
|
|
|
|
**`NewRSAPublicKeyVerifierFromPEM(pemKey []byte) (Verifier, error)`** — loads a PKIX
|
|
|
|
|
or PKCS#1 PEM-encoded RSA public key.
|
|
|
|
|
|
|
|
|
|
**`TokenConfig`** — `AccessTTL`, `RefreshTTL`, `Issuer`.
|
|
|
|
|
|
|
|
|
|
**`TokenPair`** — `AccessToken`, `RefreshToken`, `ExpiresIn` (seconds).
|
|
|
|
|
|
|
|
|
|
**`IssueTokenPair(signer, uid, customClaims, cfg) (TokenPair, error)`** — issues
|
|
|
|
|
access + refresh tokens. `customClaims` are merged at the top level of the access
|
|
|
|
|
token (compatible with `httpauth.ClaimsPermissionProvider`). Refresh token carries
|
|
|
|
|
only `sub`, `iss`, `iat`, `exp`, `jti`, and `fam` (token family for rotation).
|
|
|
|
|
|
|
|
|
|
**`Blacklist` interface** — `IsRevoked(ctx, jti)` and `Revoke(ctx, jti, ttl)`.
|
|
|
|
|
Implementations are typically backed by Valkey or Redis.
|
|
|
|
|
|
|
|
|
|
**`ErrTokenRevoked`** — sentinel returned by `RefreshTokenPair` when the JTI is on
|
|
|
|
|
the blacklist. Callers should respond with 401 and prompt re-authentication.
|
|
|
|
|
|
|
|
|
|
**`RefreshTokenPair(ctx, signer, refreshToken, blacklist, cfg, customClaims) (TokenPair, error)`**
|
|
|
|
|
— validates the refresh token, checks the blacklist, revokes the old JTI with the
|
|
|
|
|
token's remaining TTL, and issues a new pair. `customClaims` in the new access token
|
|
|
|
|
allow callers to embed fresh permission masks reflecting any role changes since the
|
|
|
|
|
previous issue.
|
|
|
|
|
|
|
|
|
|
**`AuthMiddleware(verifier, publicPaths) func(http.Handler) http.Handler`** — verifies
|
|
|
|
|
the Bearer access token and calls `httpauth.SetTokenData(ctx, uid, claims)`. Accepts
|
|
|
|
|
`Verifier` so services with only the public key can participate. Public paths bypass
|
|
|
|
|
token verification via `path.Match` glob patterns.
|