fix(auth): build password-reset links from an operator-set public address, never the Host header (M462 #4981)
test-go / test (push) Successful in 1m29s
test-web / test (push) Successful in 1m37s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
test-go / integration (push) Canceled after 2m45s
release / Build signed APK (releases and dev) (push) Canceled after 3m40s
test-go / test (push) Successful in 1m29s
test-web / test (push) Successful in 1m37s
release / Build + push container image (push) Canceled after 0s
release / Verify release artifacts (tag releases only) (push) Canceled after 0s
test-go / integration (push) Canceled after 2m45s
release / Build signed APK (releases and dev) (push) Canceled after 3m40s
buildResetURL used r.Host and r.TLS, so a forgot-password request with a forged Host emailed the victim a real reset token on a link to the attacker's server. Links now come only from network_settings.public_url (migration 0062), and no reset email is sent while it is empty; the response stays the same opaque 200 and the log says why. The address is set on a new "Public address" card under Admin → Integrations, which offers the page's own origin and warns while unset. PUT /api/admin/network-settings takes either field alone, so the proxy card and this one can't overwrite each other. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
package netsettings
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNormalizePublicURL(t *testing.T) {
|
||||
ok := map[string]string{
|
||||
"": "",
|
||||
" ": "",
|
||||
"https://music.example.com": "https://music.example.com",
|
||||
"https://music.example.com/": "https://music.example.com",
|
||||
"http://192.168.1.10:4533": "http://192.168.1.10:4533",
|
||||
" https://Music.Example.com/ ": "https://Music.Example.com",
|
||||
}
|
||||
for in, want := range ok {
|
||||
got, err := NormalizePublicURL(in)
|
||||
if err != nil || got != want {
|
||||
t.Errorf("NormalizePublicURL(%q) = %q, %v; want %q", in, got, err, want)
|
||||
}
|
||||
}
|
||||
for _, in := range []string{
|
||||
"music.example.com", // no scheme
|
||||
"ftp://music.example.com", // wrong scheme
|
||||
"https://", // no host
|
||||
"https://music.example.com/app", // path
|
||||
"https://music.example.com/?x=1", // query
|
||||
"https://music.example.com/#frag", // fragment
|
||||
"https://user:pw@music.example.com",
|
||||
"javascript:alert(1)",
|
||||
} {
|
||||
if _, err := NormalizePublicURL(in); err == nil {
|
||||
t.Errorf("NormalizePublicURL(%q) accepted, want ErrInvalidPublicURL", in)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user