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:
@@ -430,6 +430,7 @@ type MissingReacquisition struct {
|
||||
type NetworkSetting struct {
|
||||
ID bool
|
||||
TrustedProxyHops int32
|
||||
PublicUrl string
|
||||
}
|
||||
|
||||
type PasswordReset struct {
|
||||
|
||||
@@ -10,23 +10,34 @@ import (
|
||||
)
|
||||
|
||||
const getNetworkSettings = `-- name: GetNetworkSettings :one
|
||||
SELECT id, trusted_proxy_hops FROM network_settings WHERE id = true
|
||||
SELECT id, trusted_proxy_hops, public_url FROM network_settings WHERE id = true
|
||||
`
|
||||
|
||||
func (q *Queries) GetNetworkSettings(ctx context.Context) (NetworkSetting, error) {
|
||||
row := q.db.QueryRow(ctx, getNetworkSettings)
|
||||
var i NetworkSetting
|
||||
err := row.Scan(&i.ID, &i.TrustedProxyHops)
|
||||
err := row.Scan(&i.ID, &i.TrustedProxyHops, &i.PublicUrl)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updatePublicURL = `-- name: UpdatePublicURL :one
|
||||
UPDATE network_settings SET public_url = $1 WHERE id = true RETURNING id, trusted_proxy_hops, public_url
|
||||
`
|
||||
|
||||
func (q *Queries) UpdatePublicURL(ctx context.Context, publicUrl string) (NetworkSetting, error) {
|
||||
row := q.db.QueryRow(ctx, updatePublicURL, publicUrl)
|
||||
var i NetworkSetting
|
||||
err := row.Scan(&i.ID, &i.TrustedProxyHops, &i.PublicUrl)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateTrustedProxyHops = `-- name: UpdateTrustedProxyHops :one
|
||||
UPDATE network_settings SET trusted_proxy_hops = $1 WHERE id = true RETURNING id, trusted_proxy_hops
|
||||
UPDATE network_settings SET trusted_proxy_hops = $1 WHERE id = true RETURNING id, trusted_proxy_hops, public_url
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateTrustedProxyHops(ctx context.Context, trustedProxyHops int32) (NetworkSetting, error) {
|
||||
row := q.db.QueryRow(ctx, updateTrustedProxyHops, trustedProxyHops)
|
||||
var i NetworkSetting
|
||||
err := row.Scan(&i.ID, &i.TrustedProxyHops)
|
||||
err := row.Scan(&i.ID, &i.TrustedProxyHops, &i.PublicUrl)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE network_settings DROP COLUMN IF EXISTS public_url;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- The address users reach Minstrel at, e.g. https://music.example.com.
|
||||
--
|
||||
-- Password-reset links used to be built from the request's Host header,
|
||||
-- which the requester controls: a forgot-password call with a forged Host
|
||||
-- would email the victim a real reset token on a link to the attacker's
|
||||
-- server. Links are now built only from this operator-set value, and none
|
||||
-- are sent while it is empty (M462 #4981).
|
||||
ALTER TABLE network_settings ADD COLUMN public_url text NOT NULL DEFAULT '';
|
||||
@@ -3,3 +3,6 @@ SELECT * FROM network_settings WHERE id = true;
|
||||
|
||||
-- name: UpdateTrustedProxyHops :one
|
||||
UPDATE network_settings SET trusted_proxy_hops = $1 WHERE id = true RETURNING *;
|
||||
|
||||
-- name: UpdatePublicURL :one
|
||||
UPDATE network_settings SET public_url = $1 WHERE id = true RETURNING *;
|
||||
|
||||
Reference in New Issue
Block a user