forked from molecule-ai/molecule-core
AdminAuth and WorkspaceAuth both carried the same 5-line
`ADMIN_TOKEN == "" && MOLECULE_ENV in {development, dev}` check. If a
third middleware ever needs the hatch — or if "dev mode" semantics
change (new env name, allowlist, runtime flag) — the previous shape
made N places to keep in sync and N places a security reviewer has to
audit.
This commit factors the predicate into a single `isDevModeFailOpen()`
helper in `internal/middleware/devmode.go`. Each call site becomes
if isDevModeFailOpen() { c.Next(); return }
`devmode.go` carries the full rationale (why the hatch exists, why
it's safe for SaaS) so call sites don't need to restate it.
### Also
- Moved the dev-mode env-value set to a package-level `devModeEnvValues`
map so adding aliases is one line. Matches the existing convention
(`handlers/admin_test_token.go`) of treating `MOLECULE_ENV != "production"`
as dev — but stays explicit about which values opt IN rather than
blanket-accepting everything non-prod.
- Added case-insensitive compare + trim on the env value so operators
don't have to remember exact casing.
- New `devmode_test.go` unit-tests the predicate directly: 6 cases
covering happy path, both opt-out signals (ADMIN_TOKEN, production
mode), short alias, case-insensitive + whitespace tolerance, and an
explicit negative-space sweep of arbitrary non-dev values
("staging", "preview", "test", "devel", "") to lock in that typos
don't silently enable the hatch.
Existing AdminAuth/WorkspaceAuth integration tests still exercise the
helper indirectly via HTTP — they pass unchanged, confirming the
behaviour is preserved.
### No behavioural change
Before and after this commit, `go test -race ./internal/middleware/`
reports identical results. Zero production surface change — this is a
pure refactor, but it collapses the dev-mode seam from two inline
blocks into one named predicate, which is the shape future
contributors (and security reviewers) can follow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
80 lines
2.6 KiB
Go
80 lines
2.6 KiB
Go
package middleware
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
// Unit tests for the isDevModeFailOpen predicate. The AdminAuth and
|
|
// WorkspaceAuth middleware tests exercise the same helper indirectly via
|
|
// HTTP, but a direct predicate test locks the pure-logic behaviour:
|
|
// future callers can add themselves to `devmode.go` with confidence.
|
|
|
|
func TestIsDevModeFailOpen_DevModeNoAdminToken_True(t *testing.T) {
|
|
t.Setenv("MOLECULE_ENV", "development")
|
|
t.Setenv("ADMIN_TOKEN", "")
|
|
if !isDevModeFailOpen() {
|
|
t.Error("expected dev mode + no admin token to return true")
|
|
}
|
|
}
|
|
|
|
func TestIsDevModeFailOpen_DevModeShortAlias_True(t *testing.T) {
|
|
// "dev" is a valid alias for "development" — matches the convention
|
|
// in handlers/admin_test_token.go.
|
|
t.Setenv("MOLECULE_ENV", "dev")
|
|
t.Setenv("ADMIN_TOKEN", "")
|
|
if !isDevModeFailOpen() {
|
|
t.Error("expected MOLECULE_ENV=dev to be treated as dev mode")
|
|
}
|
|
}
|
|
|
|
func TestIsDevModeFailOpen_AdminTokenSet_False(t *testing.T) {
|
|
// Setting ADMIN_TOKEN is the operator's explicit opt-in to the #684
|
|
// closure. Dev mode must NOT silently override that signal.
|
|
t.Setenv("MOLECULE_ENV", "development")
|
|
t.Setenv("ADMIN_TOKEN", "operator-explicitly-set-this")
|
|
if isDevModeFailOpen() {
|
|
t.Error("explicit ADMIN_TOKEN must suppress the dev-mode hatch")
|
|
}
|
|
}
|
|
|
|
func TestIsDevModeFailOpen_Production_False(t *testing.T) {
|
|
// The SaaS-safety guarantee: production tenants always have
|
|
// MOLECULE_ENV=production, so the hatch is unreachable even if a
|
|
// misconfigured deployment also leaves ADMIN_TOKEN unset.
|
|
t.Setenv("MOLECULE_ENV", "production")
|
|
t.Setenv("ADMIN_TOKEN", "")
|
|
if isDevModeFailOpen() {
|
|
t.Error("production must never hit the dev-mode fail-open branch")
|
|
}
|
|
}
|
|
|
|
func TestIsDevModeFailOpen_CaseInsensitive(t *testing.T) {
|
|
// Operators shouldn't have to remember exact casing for a dev-only
|
|
// convenience. "Development", "DEV", " dev " all count.
|
|
cases := []string{"Development", "DEVELOPMENT", "Dev", "DEV", " dev "}
|
|
for _, env := range cases {
|
|
t.Run(env, func(t *testing.T) {
|
|
t.Setenv("MOLECULE_ENV", env)
|
|
t.Setenv("ADMIN_TOKEN", "")
|
|
if !isDevModeFailOpen() {
|
|
t.Errorf("MOLECULE_ENV=%q should count as dev mode", env)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsDevModeFailOpen_UnknownEnv_False(t *testing.T) {
|
|
// Arbitrary / unset MOLECULE_ENV values are NOT treated as dev mode.
|
|
// Keeps the fail-open branch narrow — no silent opt-in from a typo.
|
|
cases := []string{"", "staging", "local", "preview", "test", "devel"}
|
|
for _, env := range cases {
|
|
t.Run(env, func(t *testing.T) {
|
|
t.Setenv("MOLECULE_ENV", env)
|
|
t.Setenv("ADMIN_TOKEN", "")
|
|
if isDevModeFailOpen() {
|
|
t.Errorf("MOLECULE_ENV=%q must not enable fail-open", env)
|
|
}
|
|
})
|
|
}
|
|
}
|