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:
@@ -708,6 +708,9 @@ export type NetworkSettings = {
|
||||
detected_client_ip: string;
|
||||
forwarded_chain: string;
|
||||
remote_addr: string;
|
||||
// Where users reach Minstrel. Password-reset emails link here and are not
|
||||
// sent while it is empty.
|
||||
public_url: string;
|
||||
};
|
||||
|
||||
export async function getNetworkSettings(): Promise<NetworkSettings> {
|
||||
@@ -722,6 +725,13 @@ export async function updateNetworkSettings(hops: number): Promise<NetworkSettin
|
||||
});
|
||||
}
|
||||
|
||||
// Saves only the public address; the proxy depth is left as it is.
|
||||
export async function updatePublicUrl(publicUrl: string): Promise<NetworkSettings> {
|
||||
return api.put<NetworkSettings>('/api/admin/network-settings', {
|
||||
public_url: publicUrl
|
||||
});
|
||||
}
|
||||
|
||||
// Duplicates report (#3912) -------------------------------------------------
|
||||
|
||||
export async function listDuplicates(
|
||||
|
||||
@@ -19,7 +19,9 @@ const DETAIL_CODES: ReadonlySet<string> = new Set([
|
||||
'library_not_writable',
|
||||
'file_delete_failed',
|
||||
// The server names the field and its range (#3913).
|
||||
'invalid_setting'
|
||||
'invalid_setting',
|
||||
// The server says what shape of address it wants (#4981).
|
||||
'invalid_public_url'
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user