internal/server/api/authreject_test.go
Ref: Size: 2.7 KiB History
package api
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
// TestSanitizeOriginPassesOnlyPlainHTTPOrigins pins every clause of the guard
// set, one row per independently-removable clause.
//
// What the guards protect is not HTML injection — the value is emitted through
// http.Error (text/plain, nosniff) and slog. It is the phishing-copy surface
// and log hygiene: the wrong_origin page prints "it started at <this string>"
// to whoever followed a crafted callback link, and the log line carries it as
// got_host=. The length cap bounds what one request can push into both. So the
// clauses are not paranoia and must not be simplified away.
func TestSanitizeOriginPassesOnlyPlainHTTPOrigins(t *testing.T) {
// 7 for "http://" + 193 of host = exactly the 200-char cap.
atCap := "http://" + strings.Repeat("a", 189) + ".com"
overCap := "http://" + strings.Repeat("a", 190) + ".com"
for _, tc := range []struct {
guard string // the clause this row fails if removed
in string
want string
}{
{"length cap", overCap, ""},
{"length cap boundary", atCap, atCap},
{"scheme allowlist", "javascript:alert(1)", ""},
{"scheme allowlist", "data:text/html,x", ""},
{"scheme allowlist", "ftp://h", ""},
{"scheme allowlist", "evil.com", ""}, // scheme-less: url.Parse reads it as a path
{"scheme allowlist", "http://h", "http://h"},
{"scheme allowlist", "https://h", "https://h"},
{"non-empty host", "http://", ""},
{"no path", "http://h/path", ""},
{"no query", "http://h?q=1", ""},
{"no fragment", "http://h#f", ""},
{"no userinfo", "http://u:p@h", ""},
{"host charset", "http://h<x>", ""},
{"host charset", "http://h%41", ""}, // percent-encoding hides anything
{"host charset", "http://h x", ""},
{"host charset", "http://hé", ""},
{"host charset: IPv6 and port stay legal", "http://[::1]:8080", "http://[::1]:8080"},
{"host charset: hyphen and dot stay legal", "http://a-b.c:9090", "http://a-b.c:9090"},
// The echoed string is re-emitted from the parsed parts, so the scheme
// comes back lowercased and the host does not. Pinned so a rewrite
// cannot quietly change what the error page shows.
{"re-emission is scheme-normalized, host-verbatim", "HTTP://H", "http://H"},
} {
t.Run(tc.guard+" "+tc.in, func(t *testing.T) {
got := sanitizeOrigin(tc.in)
if tc.want == "" {
assert.Empty(t, got,
"the %s guard must reject %q: it reached an error page a victim reads and a log line, and nothing downstream re-checks it", tc.guard, tc.in)
return
}
assert.Equal(t, tc.want, got,
"the %s guard must pass %q unchanged: rejecting it costs a real sign-in its wrong-origin diagnosis", tc.guard, tc.in)
})
}
}