Conversation
Bump gofiber/fiber v2->v3.4.0 and cut the /v3 module major. Consume the published betas lib-commons/v6 v6.0.0-beta.2 and lib-observability/v2 v2.0.0-beta.1 (no local replace). Authorize's handler is now func(c fiber.Ctx) (interface by value) and uses c.Context(); merged with develop's product-param rename. Bump golang.org/x/text to v0.39.0 (CVE-2026-56852). BREAKING CHANGE: module path is now github.com/LerianStudio/lib-auth/v3 and the Authorize middleware requires Fiber v3. X-Lerian-Ref: 0x1
Fiber v3 pulls golang.org/x/crypto via acme/autocert; the openpgp advisory has no fixed version and the package is not imported. X-Lerian-Ref: 0x1
feat(middleware)!: migrate to Fiber v3, cut /v3 major
## [3.0.0-beta.1](v2.9.0...v3.0.0-beta.1) (2026-07-15) ### ⚠ BREAKING CHANGES * **middleware:** module path is now github.com/LerianStudio/lib-auth/v3 and the Authorize middleware requires Fiber v3. X-Lerian-Ref: 0x1 ### Features * **middleware:** migrate to Fiber v3, cut /v3 major ([024a909](024a909))
deriveSubject now switches on the token type. application (M2M)
tokens use their real sub claim instead of the fabricated
admin/{product}-editor-role; the enforcer ignores that subject and
resolves roles dynamically. Any type outside {normal-user,
application}, including empty or absent, fails closed with 401 before
the authorize call, closing the asymmetry where an empty type fell
into the M2M path. normal-user is unchanged; product is forwarded
only for normal-user flows. gRPC telemetry mirrors the request body.
Part of Item 17 (Tier A), Phase 1.
X-Lerian-Ref: 0x1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat: real M2M subject and token type whitelist
## [3.0.0-beta.2](v3.0.0-beta.1...v3.0.0-beta.2) (2026-07-16) ### Features * real M2M subject and token type whitelist ([e31ac53](e31ac53))
M2M (application-token) authorization calls sent no product, so once Custom permission resources are prefixed with "{product}/" (Item 17 F2), a bare M2M request stops matching the stored resource and the auth service denies it (403).
Forward the route product for application tokens too, gated by AUTH_M2M_PRODUCT_FORWARD_ENABLED (default off) so deploy != release: prefix isolation activates by flipping the flag, with no consumer code change. Normal-user behavior is unchanged. A shared shouldForwardProduct helper keeps the request body and telemetry payload in lockstep.
X-Lerian-Ref: 0x1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rward feat(middleware): forward product on M2M auth (flag-gated)
## [3.0.0-beta.3](v3.0.0-beta.2...v3.0.0-beta.3) (2026-07-17) ### Features * **middleware:** forward product on M2M auth (flag-gated) ([93c44db](93c44db))
Add M2MAuthenticator, a Fiber v3 middleware (RequireM2M) that authenticates machine-to-machine callers by verifying a Casdoor-issued RS256 application token offline against an injected RSA public key (the issuer certificate), so any service can reuse it by supplying its own cert. No network round-trip and no Casdoor SDK dependency. It authenticates only: RS256 is pinned (closing none/HS256 alg substitution), expiry is required, and the token type must be "application". It does not authorize and does not bind the caller to a target slug; that ownership check is intentionally out of scope, with possession of a valid, manually-provisioned M2M token as the trust boundary. The authenticated identity (sub, azp) is exposed to downstream handlers via M2MSubjectFromContext / M2MClientIDFromContext. Every failure path fails closed (401/403); when disabled the middleware is a pass-through, and an enabled authenticator requires a parseable cert at construction. X-Lerian-Ref: 0x1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The M2M gate verified the RS256 signature but ignored the "iss" claim, so any application token signed by the same key passed regardless of issuer. This is safe in a single-issuer deployment but becomes cross-issuer token reuse if one signing key ever serves multiple issuers/orgs. NewM2MAuthenticator now takes an expectedIssuer: when non-empty it pins the token issuer via jwt.WithIssuer; empty keeps the prior behavior (no issuer check) for single-issuer/dev setups. Audience is deliberately not pinned here — a Casdoor client_credentials token carries the caller's own clientId as aud, so pinning specific audiences would be the app<->slug ownership binding that is intentionally out of scope (Option B). X-Lerian-Ref: 0x1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eware feat(middleware): add M2M authentication gate
## [3.0.0-beta.4](v3.0.0-beta.3...v3.0.0-beta.4) (2026-07-20) ### Features * **middleware:** add M2M authentication gate ([2034198](2034198)) * **middleware:** add optional issuer pinning to M2M gate ([5afd305](5afd305))
RequireM2M now stores the authenticated application identity on the request Go context (context.Context) via c.SetContext, in addition to setting the existing span attributes. Downstream consumers read it with a single framework-agnostic accessor, M2MIdentityFromContext(ctx), which works for fiber-native handlers (c.Context()), humafiber-derived Huma handlers, and gRPC alike. Motivation: a consumer (plugin-access-manager) registers a Huma operation via humafiber, whose handler receives a Go context derived from Fiber's c.Context(). The previous Fiber-Locals-only accessors (M2MSubjectFromContext / M2MClientIDFromContext, keyed by c.Locals) could not be reached cleanly from a Huma handler. Propagating the identity on the Go context resolves this with one typed accessor. The identity is added onto c.Context() (not the tracing/span context), so this change carries only the identity value and does not alter span topology. The Fiber-Locals accessors were introduced in this same beta line (3.0.0-beta.x, PR #125) and have no production consumers, so they are removed in favor of the single M2MIdentityFromContext(ctx) API. A fiber-native caller reads the identity via M2MIdentityFromContext(c.Context()). A typed, unexported context key avoids string-key collisions. Verification logic (RS256/issuer/expiry/type/fail-closed) is unchanged. X-Lerian-Ref: 0x1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M2MIdentityFromContext returned the stored (possibly half-populated) value alongside ok==false when the subject was empty, contradicting the documented (zero, false) contract. Return M2MIdentity{} on the absent/empty-subject path so a caller that ignores ok cannot observe partial identity data. Addresses a CodeRabbit review comment on PR #126.
X-Lerian-Ref: 0x1
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(middleware): expose M2M identity via request context
Adds an opt-in AUTH_REQUIRED switch (AuthClient.Required, read from the AUTH_REQUIRED env var at construction) that inverts the default fail-open posture. When set and auth is disabled or misconfigured (!Enabled || Address == ""), every protected route refuses to serve — HTTP 503 on the Fiber path and gRPC codes.Unavailable on the unary and stream interceptors — instead of silently passing the request through unauthenticated. A loud error is also logged at construction so the misconfiguration is visible. Default (false) preserves the prior pass-through behavior exactly, so this is backward-compatible. Enforcement lives at the handler/interceptor rather than as a constructor error, to avoid a breaking change to NewAuthClient's existing (non-erroring) signature. The tenant-claim propagation duplicated across the two gRPC interceptors is extracted into a shared tenantContext helper, keeping cognitive complexity under the linter threshold after the added fail-closed branch. Closes #107 Claude-Session: https://claude.ai/code/session_01RcQLK4qK1kbpvXWjuAR3Xh
Every authorized request made a synchronous POST /v1/authorize with no cache, breaker, or retry, so an authz-service blip or outage amplified straight into the request path. The call was also built with http.NewRequest (no context), so a caller's deadline/cancellation never reached it. Add three opt-in resilience layers (all default-off, so default behavior is byte-for-byte unchanged) plus the context fix, composed as breaker(retry(httpCall(ctx))) with the cache checked before the breaker: - Prereq fix: build the authz request with http.NewRequestWithContext, and wrap each call in context.WithTimeout(ctx, AUTH_TIMEOUT) (default 30s = behavior-neutral). The caller's cancellation now aborts the call, and the deadline caps the retry budget. - TTL decision cache (AUTH_CACHE_TTL > 0) keyed by (sub, resource, action, product) — never the raw token. Bounded, sharded, no-dependency map with lazy expiry (no background goroutine to leak). get never returns an expired entry. Documented staleness/revocation-lag window = TTL. - Circuit breaker (AUTH_BREAKER_ENABLED, sony/gobreaker promoted to direct). On open it serves ONLY a fresh positive cache hit (checked before the breaker), otherwise denies — never a stale grant. - Bounded retry (AUTH_RETRY_MAX, cenkalti/backoff/v5) on TRANSIENT failures only (network/timeout/5xx); authoritative 401/403 are never retried. Every fallback — breaker open, retry exhausted, timeout — denies (fail closed, 403 / PermissionDenied). This complements #107: misconfig at construction -> 503 (never mounts open); runtime outage -> deny. Two triggers, both fail-closed. Stacks on #107 (PR #127): built on its Required fail-closed flag. Must merge after #127; rebase onto develop and retarget base to develop once #127 lands. Closes #108 Claude-Session: https://claude.ai/code/session_01RcQLK4qK1kbpvXWjuAR3Xh
…sed-107 feat(middleware): add fail-closed AUTH_REQUIRED option
## [3.0.0-beta.6](v3.0.0-beta.5...v3.0.0-beta.6) (2026-07-21) ### Features * **middleware:** add fail-closed AUTH_REQUIRED option ([f7fff6c](f7fff6c)), closes [#107](#107)
feat(middleware): add authz cache, circuit breaker, and bounded retry
* feat(middleware): add opt-in local JWT signature verification The general authorization path parsed the bearer token with ParseUnverified and trusted its claims (type/owner/sub) to build the authz subject, relying solely on the network POST to /v1/authorize as the trust anchor. The M2M gate already verified tokens properly (RS256-pinned, expiry required, optional issuer) against a static injected cert. Converge on that hardened verifier instead of adding a second (JWKS-based) mechanism: extract m2m.go's crypto core into one package-level verifyToken used by BOTH RequireM2M and checkAuthorization. Hooking it at checkAuthorization covers Fiber and both gRPC interceptors from one audited place. Verification is opt-in and gated by presence of a cert, mirroring the M2M cert gate: - AUTH_JWT_VERIFY_CERT: newline-joined PEM bundle (or AUTH_JWT_VERIFY_CERT_PATH for a mounted file). Multiple certs enable zero-downtime key rotation — a token verified by ANY listed key is accepted (static-keyset-with-overlap). - AUTH_JWT_ISSUER: optional "iss" pin. When no cert is configured the historical ParseUnverified behavior is preserved exactly (backward compatible). When configured, the token is verified (signature/alg/exp/iss) before its claims are used; any failure denies with 401 (fail closed). A configured-but-unparseable cert is logged loud at ERROR and leaves verification disabled, never silently accepted, keeping NewAuthClient's no-error signature. Closes #106 Claude-Session: https://claude.ai/code/session_01RcQLK4qK1kbpvXWjuAR3Xh * refactor(middleware): source-neutral cert error + trim AUTH_JWT_VERIFY_CERT Reword the cert-parse failure log to be source-neutral since the PEM data can come from either AUTH_JWT_VERIFY_CERT or AUTH_JWT_VERIFY_CERT_PATH. Trim surrounding whitespace on the inline AUTH_JWT_VERIFY_CERT value before PEM parsing so a stray trailing newline no longer breaks cert parsing, mirroring the existing AUTH_JWT_VERIFY_CERT_PATH handling. Claude-Session: https://claude.ai/code/session_01RcQLK4qK1kbpvXWjuAR3Xh
… v6.0.0 Pin the observability and commons dependencies to their stable v2.0.0/v6.0.0 releases (were v2.0.0-beta.1 / v6.0.0-beta.2). No code changes; build passes. X-Lerian-Ref: 0x1
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
📝 WalkthroughWalkthroughChangesAuth v3 middleware
Sequence Diagram(s)sequenceDiagram
participant Client
participant AuthMiddleware
participant JWTVerifier
participant AuthorizationService
Client->>AuthMiddleware: Request with JWT
AuthMiddleware->>JWTVerifier: Extract and optionally verify claims
JWTVerifier-->>AuthMiddleware: Claims or unauthorized
AuthMiddleware->>AuthorizationService: POST authorization payload
AuthorizationService-->>AuthMiddleware: Authorization decision
AuthMiddleware-->>Client: Continue or authorization response
Possibly related PRs
✨ Finishing Touches✨ Simplify code
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Around line 1-8: Align all release artifacts for the intended v3.0.0 release:
update CHANGELOG.md lines 1-8 with the final 3.0.0 entry and revise lines 56-68
so the progression starts from beta.8; replace beta internal dependencies in
go.mod lines 22-23 with stable versions; and update README.md line 10 to use the
stable `@latest` installation only after publishing that tag, otherwise pin the
intended beta explicitly.
In `@README.md`:
- Around line 54-65: Update the authorization resilience documentation near
AUTH_CACHE_TTL to explicitly state that fresh positive decision-cache hits are
the exception to the fail-closed fallback rule and may be served during
authorization outages. Preserve the existing explanation of revocation lag and
availability tradeoffs.
- Around line 29-41: Update the JWT verification configuration flow described in
the README so malformed AUTH_JWT_VERIFY_CERT or AUTH_JWT_VERIFY_CERT_PATH
configuration cannot disable verifyKeys and fall back to ParseUnverified. Ensure
invalid configured keys fail closed with unauthorized requests, or require an
explicit, clearly monitored opt-out before allowing unverified parsing; preserve
the existing optional behavior only when verification is genuinely unset.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 87c0b8ea-37e8-4f7e-8c6f-fe202f1fc45b
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (15)
.trivyignoreCHANGELOG.mdREADME.mdauth/middleware/decisioncache.goauth/middleware/m2m.goauth/middleware/m2m_test.goauth/middleware/middleware.goauth/middleware/middlewareGRPC.goauth/middleware/middlewareGRPC_test.goauth/middleware/middleware_test.goauth/middleware/resilience.goauth/middleware/resilience_test.goauth/middleware/verification.goauth/middleware/verification_test.gogo.mod
chore(deps): point to stable observability v2.0.0 and commons v6.0.0
## [3.0.0-beta.9](v3.0.0-beta.8...v3.0.0-beta.9) (2026-07-21)
|
🎉 This PR is included in version 3.0.0-beta.9 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
qnen
approved these changes
Jul 22, 2026
|
🎉 This PR is included in version 3.0.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Release of the v3 major to
main(stable). Migrates to Fiber v3, cuts the/v3module path (github.com/LerianStudio/lib-auth/v3), and depends on the stablelib-observability/v2 v2.0.0+lib-commons/v6 v6.0.0.Expected release: v2.9.0 → v3.0.0 (single major bump; breaking commit
024a909 feat(middleware)!present in range;.releaserc.ymlmaps breaking→major).Type of Change
BREAKING CHANGEfeat/buildBreaking Changes
/v3— imports becomegithub.com/LerianStudio/lib-auth/v3/...; rungo mod tidy.gofiber/fiber/v3; consumers on Fiber v2 must migrate.Note
Merge the stable-deps bump into
developfirst so this release carriesobservability v2.0.0+commons v6.0.0.