auth

package
v0.4.20 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

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

View Source
const (
	RoleAdmin  = "admin"
	RoleMember = "member"
	RoleViewer = "viewer"
)
View Source
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.

View Source
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

func InternalAuth() func(http.Handler) http.Handler

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

func PayloadTrusted(ctx context.Context) bool

PayloadTrusted reports whether WithPayloadTrust marked this context.

func PlatformAuth

func PlatformAuth() func(http.Handler) http.Handler

PlatformAuth returns middleware that requires an authenticated user. Use RequirePermission per-route for authorization (which roles can access).

Identity sources (first match wins):

  1. An Identity already set in ctx (the Lambda runtime's authorizer path puts one here before this middleware runs).
  2. 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.
  3. 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

func PublicAuth() func(http.Handler) http.Handler

PublicAuth returns middleware that allows any request.

func RequireProxy

func RequireProxy() func(http.Handler) http.Handler

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

func RequireRoles(roles ...string) func(http.Handler) http.Handler

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 Roles

func Roles(r ...string) []string

Roles is a convenience constructor for role lists.

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 Set

func Set(ctx context.Context, id Identity) context.Context

Set stores the identity in the context.

func WithPayloadTrust

func WithPayloadTrust(ctx context.Context) context.Context

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.

Types

type Identity

type Identity struct {
	UserID  string // Who
	AppID   string // Which app
	AppRole string // What access (admin, member, viewer)
}

Identity holds the authenticated user's identity for the current request.

func Get

func Get(ctx context.Context) *Identity

Get retrieves the identity from the context. Returns nil if not set.

a := auth.Get(r.Context())
a.UserID   // "u-123"
a.AppID    // "a-456"
a.AppRole  // "admin"

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL