Skip to content

feat(middleware): admit one calling service by its sourceService claim - #229

Merged
fredcamaral merged 1 commit into
developfrom
feat/require-source-service
Oct 9, 2026
Merged

fredcamaral merged 1 commit into
developfrom
feat/require-source-service

Conversation

@fredcamaral

Copy link
Copy Markdown
Member

What changes

middleware.RequireSourceService(service string) fiber.Handler, a sibling of RequireApplication. Mount it after Authorize. It admits only an application principal whose sourceService claim equals service exactly:

Caller Answer
no Principal published 401 (fiber.ErrUnauthorized)
application, sourceService == service passes
application with another source, undeclared, empty, or a different case/spacing 403 (fiber.ErrForbidden)
normal-user (a person), whatever claims it carries 403
service empty or the undeclared marker admits nobody

The guard returns Fiber errors, as the type guards do, so a service's own ErrorHandler keeps its envelope.

Why it reads the stored Principal instead of PrincipalFromContext

When AUTH_M2M_INVERSION_ENABLED is off (the default, and how every Lerian engine runs today), Authorize authorizes an application under the fabricated admin/<product>-editor-role. PrincipalFromContext therefore reports no application principal at all. The token's claims are attested in both models anyway: the authorization service introspects the token on every cache miss, and both decision caches key on the token digest. The guard decides on the claims, never on Subject, so the rule that a legacy Subject is not an identity still holds. The SourceService docstring and the README now say this.

Who needs it

The Pix (plugin-br-pix-jd #409) and TED (plugin-br-bank-transfer #481) engines' cross-core payee routes book a credit by debiting @intercore, a clearing account with unlimited overdraft. Today any application aimed at the engine, and any human editor, passes cross_core and can post a credit. With this guard only the JD Courier passes: an application issued with source jd-courier, whose sourceService the Access Manager stamps server-side. The guard is only as strong as the issuer: whoever may create applications for the tenant can issue one with that source. The README states this.

Crippling proof (each sabotage applied alone, then reverted)

Sabotage Test that fails
drop p.Type != application TestRequireSourceService/normal_user_is_403
drop IsDeclaredSourceService .../empty_service_admits_nobody, .../marker_service_admits_nobody
drop p.SourceService != service .../another_source_is_403, .../match_is_exact
read through PrincipalFromContext .../legacy_fabricated_subject_reaches_the_handler, plus TestRequireSourceService_BehindAuthorize at inversion=false (the Courier gets 401 instead of passing)

Verification

  • go test ./... -race -count=1 passes
  • golangci-lint run ./...: 0 issues; goimports -l: clean
  • TestRequireSourceService_BehindAuthorize runs the real Authorize chain against a fake authorization service with inversion off and with inversion on.

Line delta

prod +29/-8 (principal.go: the guard plus the docstring rewrite), test +91/-0, docs +27/-7 (README).

Pull Request Type

  • Feature

Checklist

  • I have tested these changes locally.
  • I have updated the documentation accordingly.
  • I have checked for any potential security issues.
  • I have ensured that all tests pass.

🤖 Generated with Claude Code

RequireSourceService(service) admits an application principal whose sourceService claim equals service and refuses every other caller with 403 (401 when no principal was published). It reads the stored Principal directly, so it decides under the legacy authorization model too, where PrincipalFromContext reports no application principal because the Subject is a fabricated role. The authorization round-trip vouched for the whole token in both models.

The Lerian engines need it to admit only the JD Courier on their cross-core payee routes, which book against an unlimited-overdraft clearing account.
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Essentials
  • Run ID: 49b9e650-0630-4040-aa09-2f1f395ca534

📥 Commits

Reviewing files that changed from the base of the PR and between dd8895e and aa31854.


📒 Files selected for processing (3)
  • README.md
  • auth/middleware/principal.go
  • auth/middleware/principal_test.go

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.



📝 Walkthrough

Walkthrough

The pull request adds RequireSourceService(service) to check an application principal’s declared SourceService claim. It documents the guard’s behavior and adds tests for direct use and for requests processed by Authorize under both M2M inversion settings.

Changes

Source service guard

Layer / File(s) Summary
Guard contract and implementation
auth/middleware/principal.go, README.md, auth/middleware/principal_test.go
RequireSourceService(service) returns 401 when no principal is stored and 403 unless an application principal has a declared claim that exactly matches service. The documentation describes claim availability across authorization models and the guard’s issuer trust boundary. Tests cover matching and rejected principals.
Authorization integration
auth/middleware/principal_test.go
Tests chain Authorize and RequireSourceService with M2M inversion enabled and disabled. They cover matching, undeclared, other-service, and normal-user claims.

Sequence Diagram(s)

sequenceDiagram
  participant Request
  participant Authorize
  participant RequireSourceService
  participant NextHandler
  Request->>Authorize: authorize request
  Authorize->>RequireSourceService: pass request with Principal
  RequireSourceService->>NextHandler: continue on exact application claim match
Loading

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to aa318

No actionable merge-blocking issue is identified; the change is ready for normal merge checks.

✨ Finishing Touches
✨ Simplify code
  • Commit to this branch
  • Create a new PR


  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@fredcamaral
fredcamaral merged commit 386086f into develop Oct 9, 2026
6 checks passed
@fredcamaral
fredcamaral deleted the feat/require-source-service branch October 9, 2026 20:57
@lerian-studio-midaz-push-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.8.0-beta.1 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@lerian-studio-midaz-push-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.8.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant