Skip to content

feat(auth): PlatformAuth trusts identity from trusted forwarder + local-dev bypass#103

Merged
I-am-nothing merged 1 commit into
mainfrom
feat/platform-auth-identity
May 25, 2026
Merged

feat(auth): PlatformAuth trusts identity from trusted forwarder + local-dev bypass#103
I-am-nothing merged 1 commit into
mainfrom
feat/platform-auth-identity

Conversation

@I-am-nothing

Copy link
Copy Markdown
Contributor

Summary

Second slice of the tunnel-auth workstream (see `memory/feedback_tunnel_auth_no_bypass.md`). Closes the SDK side of the gap that today makes `/platform/*` routes unreachable from `mirrorstack dev` UIs:

`auth.PlatformAuth` only honors `Identity` already preset in ctx. The Lambda runtime sets it in prod. In local dev nothing does, so every Platform-scope request 401s.

This PR teaches PlatformAuth two new identity sources. No second secret is introduced — the same `MS_INTERNAL_SECRET` that PR mirrorstack-cli#33 injects now also gates trusted-forwarder identity injection.

New behavior matrix

```
inLambda + secret unset → 503 (operator misconfig — matches InternalAuth)
inLambda + secret set → enforce: validate secret + identity headers
(Lambda authorizer path still wins if Identity
is preset in ctx)
local + secret unset → BYPASS: inject synthetic admin Identity so
'mirrorstack dev' (no tunnel) can render
platform-scope routes without ceremony
local + secret set → enforce ('mirrorstack dev --tunnel' — CLI sets
the secret via mirrorstack-cli#33)
```

Identity sources, first match wins:

  1. Preset in ctx (Lambda authorizer)
  2. Trusted-forwarder: `X-MS-Internal-Secret` valid + `X-MS-User-ID` / `X-MS-App-ID` / `X-MS-App-Role` headers present → Identity injected
  3. Local bypass: synthetic `local-dev-user` / `local-dev-app` / `admin`

Cascade unblocked

Slice Repo Status
1. CLI generates secret, injects `MS_INTERNAL_SECRET` mirrorstack-cli #33 ✅ open
2. SDK trusts identity headers from secret-bearing caller app-module-sdk this PR
3. Dispatch parses `internal_secret` + signs forwarded requests api-platform follow-up
4. Platform-side proxy at `/apps/[id]/[slug]/api/[...]` web-applications follow-up
5. Bundle reads `apiBase` from mount context bundles trivial follow-up

With (1)+(2)+(4) landed, plain `mirrorstack dev` (no tunnel) renders modules' `/platform/*` routes end-to-end against a synthetic admin identity. (3) is the production-shape path that handles real users via dispatch.

Test plan

  • `go test ./auth/...` — 7 new PlatformAuth tests all pass (ctx-preset, local bypass, lambda 503, missing secret, wrong secret, missing identity headers, valid headers → identity injected)
  • `go test ./internal/core/...` — new `TestPlatform_LocalDevBypass_InjectsSyntheticAdmin` and `TestPlatform_SecretSet_RejectsNoHeader` replace `TestPlatform_RejectsNoAuth`, pinning the new contract through the SDK's scope-routes plumbing
  • Whole-repo `go test ./...` clean except for a pre-existing flake in `internal/refcache.TestSlowPath_SingleflightCoalesces` (passes on retry; concurrency-timing test, unrelated)

Notes

  • No changes to InternalAuth — its existing matrix is unchanged.
  • The trusted-forwarder headers (`HeaderUserID`, `HeaderAppID`, `HeaderAppRole`) are exported so dispatch + the platform proxy can reference the same constants when forwarding.
  • `Set` already existed; we just call it from PlatformAuth now.

🤖 Generated with Claude Code

…ypass

Closes the loop on the tunnel-auth workstream's SDK slice (memory:
feedback_tunnel_auth_no_bypass). PlatformAuth now has three identity
sources, in priority order:

  1. Identity preset in ctx (Lambda authorizer path — unchanged).
  2. Trusted-forwarder injection: a request carrying a valid
     X-MS-Internal-Secret may also assert user identity via
     X-MS-User-ID / X-MS-App-ID / X-MS-App-Role headers, which
     PlatformAuth promotes to ctx.Identity. Same trust signal
     InternalAuth uses; no second secret needed.
  3. Local-dev bypass: when MS_INTERNAL_SECRET is unset AND we're
     not in Lambda, a synthetic admin identity is injected so
     `mirrorstack dev` (no tunnel) can render platform-scope
     routes without the developer wiring auth manually. Matches
     InternalAuth's local bypass shape.

Behavior matrix mirrors InternalAuth:

  inLambda + secret unset → 503 (operator misconfig)
  inLambda + secret set   → enforce (Lambda authorizer wins if preset)
  local    + secret unset → bypass with synthetic admin
  local    + secret set   → enforce (tunnel mode — CLI sets the
                            secret via mirrorstack-cli #33)

The trusted-forwarder design unblocks the cascade:
  - api-platform proxy/dispatch attaches signed headers when
    forwarding browser requests on behalf of an authenticated
    platform user → modules see the right Identity automatically.
  - In local dev with no tunnel, the bypass lets module UIs render
    against platform-scope routes without coordinating env between
    web-applications and the running module.

Test plan:
  - All 7 auth.PlatformAuth* tests pass (preset ctx, local bypass,
    Lambda 503, missing secret, wrong secret, missing identity
    headers, valid headers → identity injected).
  - internal/core.TestPlatform_LocalDevBypass_InjectsSyntheticAdmin
    and TestPlatform_SecretSet_RejectsNoHeader replace the old
    TestPlatform_RejectsNoAuth, pinning the new contract end-to-end
    through the SDK's scope-routes plumbing.
  - Whole-repo `go test ./...` clean except for a pre-existing
    flake in internal/refcache.TestSlowPath_SingleflightCoalesces
    (concurrency timing, unrelated to auth).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@I-am-nothing I-am-nothing merged commit a9ed221 into main May 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant