Skip to content

feat: ms.AppID accessor + proxy guard promotes trusted app identity on Public#119

Merged
I-am-nothing merged 1 commit into
mainfrom
feat/ms-appid-accessor
Jun 13, 2026
Merged

feat: ms.AppID accessor + proxy guard promotes trusted app identity on Public#119
I-am-nothing merged 1 commit into
mainfrom
feat/ms-appid-accessor

Conversation

@I-am-nothing

Copy link
Copy Markdown
Contributor

Summary

Implements the SDK half of the #236 redesign (architecture-panel verdict): a first-class trusted app-id accessor plus Public-route identity promotion. A module can now read its own trusted app id on every guarded surface — including Public routes, which previously had no identity at all.

The gap (panel-traced)

ms.Public routes mount only proxyGuard (internal/core/module.go ~315-321). PlatformAuth — which promotes the trusted X-MS-App-ID header into auth.Identity — runs only on Platform routes (~303-310). So on a Public route auth.Get(ctx).AppID was empty and a module could not read its trusted app id. Prod Lambda already injects identity via runtime.InjectResources (inject.go ~61-67); dev/HTTP Public did not. This PR closes that asymmetry.

Changes

  1. requireProxy (auth/middleware.go) — on the validated-token success path ONLY (the path reached after the platform-token check passes), promote the dispatch-injected X-MS-User-ID / X-MS-App-ID / X-MS-App-Role headers to auth.Identity before next.ServeHTTP.
    • Critical ordering: promotion runs only after the token check, which proves the X-MS-* headers came through dispatch (the browser never holds the token, so a direct caller cannot forge a request that reaches here).
    • The inert/standalone path (no token configured) and the rejected paths never promote — trusting unvalidated headers would let a direct caller forge an app id.
    • Never clobbers an identity already set (Lambda's InjectResources).
  2. ms.AppID(ctx) string (mirrorstack.go, forwards to core.AppID) — the inbound twin of ms.WithAppID. Returns auth.Get(ctx).AppID or "". The single unspoofable way a module reads its own app id.
  3. Version + CHANGELOGVERSION 0.2.0 → 0.2.1, CHANGELOG [v0.2.1] (patch-only per the 0.x release convention). Add the release label to cut the tag on merge.
  4. Docsdocs/concepts/scopes.md (+ zh-TW): Public/Platform handlers read their trusted app via ms.AppID(ctx), not request data; reconciled the ms.WithAppID doc comment (it now describes retargeting an outbound ms.Call at a different app, with ms.AppID as the read path for your own app).

Coordination with #118

Open simplify PR #118 also edits requireProxy (dedups its two rejection paths into a rejectNotProxied helper). This PR folds that dedup in (same helper, same body) since it touches the same lines. #118 should be closed (or rebased onto this) — its change is fully contained here.

Tests

  • TestRequireProxy_ValidToken_PromotesIdentity — Public request with a valid token gives ms.AppID the injected app id.
  • TestRequireProxy_RejectedRequest_NeverPromotes — rejected (no token) never reaches the handler / promotion.
  • TestRequireProxy_NoSecretInert_DoesNotPromote — standalone (no token configured, guard inert) documented behavior: open surface, no promotion of unvalidated headers.
  • TestRequireProxy_PresetIdentity_NotClobbered — preset identity (Lambda path) wins over headers.
  • TestPublic_ProxyGuard_PromotesTrustedAppID — end-to-end through the real module router: a Public handler reads ms.AppID = the dispatch-injected app.
  • TestAppID (facade) — empty when unset, reads context identity, inbound twin of WithAppID.

Verification

go build ./..., go vet ./..., and go test ./... (full suite, also -race on the changed packages) all green.

Report contract (for the oauth-core track)

  • Accessor: func AppID(ctx context.Context) string — exported as ms.AppID (mirrorstack.go) forwarding to core.AppID (internal/core/call.go). Returns auth.Get(ctx).AppID, else "".
  • Promotion point: auth.requireProxy, on the success path after constantTimeEqual(token, expected) passes — sets auth.Identity{UserID, AppID, AppRole} from the X-MS-* headers (only if no identity is already set), before next.ServeHTTP. This runs for both Public and Platform surfaces (proxyGuard fronts both); Lambda and inert/rejected paths do not promote.

🤖 Generated with Claude Code

…n Public

A module can now read its own trusted app id on every guarded surface,
including Public routes which previously had no identity at all.

The gap: ms.Public routes mount only proxyGuard; PlatformAuth (which
promotes the trusted X-MS-App-ID header into auth.Identity) runs only on
Platform routes. So on a Public route auth.Get(ctx).AppID was empty and a
module could not read its trusted app id. Prod Lambda already injects
identity via runtime.InjectResources; dev/HTTP Public did not — this
closes that asymmetry.

- requireProxy: on the validated-token success path ONLY (after the token
  check proves the X-MS-* headers were dispatch-injected, not client-forged),
  promote them to auth.Identity (AppID/UserID/AppRole) before next.ServeHTTP.
  Never promote on the inert/standalone or rejected paths; never clobber an
  identity already set (Lambda's InjectResources).
- ms.AppID(ctx): inbound twin of ms.WithAppID; returns auth.Get(ctx).AppID
  or "". The single unspoofable way a module reads its own app id.
- Reconcile ms.WithAppID doc (it now retargets an outbound Call at a
  different app; reading your own app is ms.AppID).
- docs/concepts/scopes.md (+ zh-TW): Public/Platform handlers read their
  trusted app via ms.AppID, never from request data.
- Fold PR #118's requireProxy dedup (rejectNotProxied helper) — #118 should
  be closed/rebased onto this.
- VERSION 0.2.0 -> 0.2.1, CHANGELOG (patch-only per 0.x convention).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@I-am-nothing I-am-nothing added the release Trigger a version bump on merge label Jun 13, 2026
@I-am-nothing I-am-nothing merged commit 5a0825d into main Jun 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release Trigger a version bump on merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant