Documentation
¶
Overview ¶
Package auth provides authentication context and middleware for MirrorStack modules.
Module code reads the request identity from the CONTEXT — ms.UserID / ms.AppID / ms.AppRole (or auth.Get) — never from the X-MS-* Header* constants, which are the internal platform-to-SDK wire; see the Header* docs for why header reads silently break deployed.
Index ¶
- Constants
- func InternalAuth() func(http.Handler) http.Handler
- func PayloadTrusted(ctx context.Context) bool
- func PlatformAuth() func(http.Handler) http.Handler
- func PublicAuth() func(http.Handler) http.Handler
- func RequireProxy() func(http.Handler) http.Handler
- func RequireRoles(roles ...string) func(http.Handler) http.Handler
- func Roles(r ...string) []string
- func SecretConfigured() bool
- func Set(ctx context.Context, id Identity) context.Context
- func WithPayloadTrust(ctx context.Context) context.Context
- type Identity
Constants ¶
const ( RoleAdmin = "admin" RoleMember = "member" RoleViewer = "viewer" )
const ( HeaderInternalSecret = "X-MS-Internal-Secret" HeaderPlatformToken = "X-MS-Platform-Token" HeaderUserID = "X-MS-User-ID" HeaderAppID = "X-MS-App-ID" HeaderAppRole = "X-MS-App-Role" )
Trusted-forwarder identity-injection headers — the INTERNAL wire between the platform (dispatch/tunnel) and this SDK's middleware, NOT a module identity API. A caller proves it's a trusted forwarder (the platform or its dispatch) by sending a valid X-MS-Internal-Secret; if proven, PlatformAuth trusts the user identity those callers assert via these headers.
Module code must NEVER read the identity-claim headers (HeaderUserID / HeaderAppID / HeaderAppRole) directly: the deployed Lambda shim strips every client-settable X-MS-* identity header before the router runs (trusted identity rides the typed invoke payload instead), so a header read works under the dev tunnel and silently breaks deployed — the exact bug shipped in ms-app-modules#30. Read identity via ms.UserID / ms.AppID / ms.AppRole (or auth.Get), which resolve on every ingestion path.
const CodeNotProxied = "not_proxied"
CodeNotProxied is the stable error code returned when a request reaches a module's public/platform surface without the server-injected platform token — i.e. it did not come through the platform proxy (dispatch). The dispatch track matches on this code; do not rename it without updating that contract.
Variables ¶
This section is empty.
Functions ¶
func InternalAuth ¶
InternalAuth returns middleware that validates the internal secret. Reads MS_INTERNAL_SECRET env var at construction time. Behavior matrix (see internalAuth doc).
CONTRACT: the returned middleware captures the expected secret once at construction time. Callers (e.g., Module.internalAuth) cache and reuse the returned closure for every route registration. Do not add request- time behavior (re-reading the secret, fetching from a config service) without updating all cache sites in mirrorstack.go.
func PayloadTrusted ¶
PayloadTrusted reports whether WithPayloadTrust marked this context.
func PlatformAuth ¶
PlatformAuth returns middleware that requires an authenticated user. Use RequirePermission per-route for authorization (which roles can access).
Identity sources (first match wins):
- An Identity already set in ctx (the Lambda runtime's authorizer path puts one here before this middleware runs).
- Trusted-forwarder injection: a request that presents a valid X-MS-Internal-Secret may also assert user identity via X-MS-User-ID / X-MS-App-ID / X-MS-App-Role. PlatformAuth promotes those to ctx.Identity. Same trust signal InternalAuth uses.
- Local-dev bypass: when MS_INTERNAL_SECRET is unset AND we are 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.
In Lambda with no secret configured, this returns 503 (operator misconfiguration) to match InternalAuth's behavior.
func PublicAuth ¶
PublicAuth returns middleware that allows any request.
func RequireProxy ¶
RequireProxy returns middleware that rejects requests which did not arrive through the platform proxy. It guards the module's PUBLIC and PLATFORM request surfaces: a direct caller can spoof X-MS-App-ID / X-MS-User-ID / X-MS-App-Role, but NOT the X-MS-Platform-Token (a per-session secret the platform injects at the tunnel boundary; the browser never sees or sends it). Validating the token here, at the SDK trust boundary, makes every 3rd-party module inherit the protection for free.
It mirrors InternalAuth's env gating exactly via platformSecretReader, so a module's standalone `go test` (no token configured) is unaffected, while the `mirrorstack dev` / tunnel / production HTTP paths (dispatch injects the token) enforce.
func RequireRoles ¶
RequireRoles returns chi middleware that checks AppRole against the allowed roles. Pure middleware factory: no registry, no global state, safe to call from any goroutine.
This function does NOT track the permission name in any module manifest — it only enforces the role check at request time. To both enforce AND surface the permission in the manifest payload, use Module.RequirePermission (or mirrorstack.RequirePermission, which dispatches to the default Module).
SECURITY: calling RequireRoles with no arguments produces a middleware that denies every authenticated caller (the empty roleSet rejects all lookups). This is the safe-by-default behavior — a typo'd or generated empty roles list locks down the route rather than opening it. Do not rely on RequireRoles() as a "no-op" middleware; use auth.PublicAuth instead.
r.With(auth.RequireRoles("admin", "member", "viewer")).Get("/items", listItems)
func SecretConfigured ¶
func SecretConfigured() bool
SecretConfigured reports whether any platform-secret source (MS_PLATFORM_TOKEN_FILE / MS_PLATFORM_TOKEN / MS_INTERNAL_SECRET) is set. It is the single source of truth for "are we in local-dev (no secret) mode", used by the SDK to gate local-dev-only behavior (e.g. permissive CORS) so that gate stays in lockstep with the auth/proxy guards' bypass branches rather than checking one specific env var name.
func WithPayloadTrust ¶
WithPayloadTrust marks ctx as carrying identity injected from the typed Lambda payload — set ONLY by runtime.NewLambdaHandler (the real Lambda invoke path, or the dev lambda-invoke shim after its envelope-secret gate). RequireProxy gives a marked request the same pass-through it gives Lambda mode: the payload IS the trust boundary, and the envelope never carries the per-session X-MS-Platform-Token. The mark lives in context, so inbound request data can never set it.