mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-04 21:04:37 +08:00
fix(account-settings): decode URL-encoded city in active sessions (#1503)
## Summary The **Active Sessions** table in account settings showed locations like `San%20Francisco` instead of `San Francisco`. Vercel percent-encodes its geolocation headers (e.g. `x-vercel-ip-city`), so a multi-word city arrives URL-encoded. The city name was being stored verbatim, so the raw `%20` leaked into the UI. The fix decodes the city name where the Vercel geo header is read, so recorded sessions store the human-readable name. This also benefits any other consumer of the location data. It falls back to the raw value if it isn't valid percent-encoding, so a stray `%` can't break things. ## Test plan - [ ] Unit tests (in-source, `apps/backend/src/lib/end-users.tsx`): simulating Vercel headers with `x-vercel-ip-city: San%20Francisco` now yields `cityName: "San Francisco"`; an invalid-encoding value (`100% Real City`) passes through unchanged instead of throwing. All 8 tests in the file pass. - [ ] In a Vercel-deployed environment, sign in and open Account Settings → Active Sessions; confirm the Location column shows a plain city name (e.g. `San Francisco`) with no `%20`. > Note: this can't be reproduced on localhost because there's no Vercel proxy supplying geo headers (the location shows `Unknown`). The behavior is covered by the unit tests, which feed the exact headers Vercel sends. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Corrected handling of city name data from hosting-provided location headers so multi-word city names display correctly and invalid percent-encoding no longer causes errors. * **Tests** * Added tests to verify URL-decoded city names from location headers and to ensure malformed encodings are safely preserved. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1503?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
80d1530b48
commit
5b9ae9c443
@ -92,6 +92,15 @@ function parseCoordinate(raw: string | null | undefined): number | undefined {
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function decodeVercelGeoHeader(raw: string | null | undefined): string | undefined {
|
||||
if (raw == null || raw === "") return undefined;
|
||||
try {
|
||||
return decodeURIComponent(raw);
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
function getBrowserEndUserInfo(allHeaders: Headers, trustedProxy: TrustedProxy):
|
||||
| { maybeSpoofed: true, spoofedInfo: EndUserInfoInner }
|
||||
| { maybeSpoofed: false, exactInfo: EndUserInfoInner }
|
||||
@ -133,7 +142,7 @@ function getBrowserEndUserInfo(allHeaders: Headers, trustedProxy: TrustedProxy):
|
||||
const geoLocation: EndUserLocation = {
|
||||
countryCode: rawCountryCode ? normalizeCountryCode(rawCountryCode) : undefined,
|
||||
regionCode: (isVercelTrusted ? allHeaders.get("x-vercel-ip-country-region") : undefined) || undefined,
|
||||
cityName: (isVercelTrusted ? allHeaders.get("x-vercel-ip-city") : undefined) || undefined,
|
||||
cityName: decodeVercelGeoHeader(isVercelTrusted ? allHeaders.get("x-vercel-ip-city") : undefined),
|
||||
latitude: parseCoordinate(isVercelTrusted ? allHeaders.get("x-vercel-ip-latitude") : null),
|
||||
longitude: parseCoordinate(isVercelTrusted ? allHeaders.get("x-vercel-ip-longitude") : null),
|
||||
tzIdentifier: (isVercelTrusted ? allHeaders.get("x-vercel-ip-timezone") : undefined) || undefined,
|
||||
@ -144,7 +153,7 @@ function getBrowserEndUserInfo(allHeaders: Headers, trustedProxy: TrustedProxy):
|
||||
const spoofedGeoLocation: EndUserLocation = trustedProxy === "" ? {
|
||||
countryCode: rawSpoofedCountryCode ? normalizeCountryCode(rawSpoofedCountryCode) : undefined,
|
||||
regionCode: allHeaders.get("x-vercel-ip-country-region") || undefined,
|
||||
cityName: allHeaders.get("x-vercel-ip-city") || undefined,
|
||||
cityName: decodeVercelGeoHeader(allHeaders.get("x-vercel-ip-city")),
|
||||
latitude: parseCoordinate(allHeaders.get("x-vercel-ip-latitude")),
|
||||
longitude: parseCoordinate(allHeaders.get("x-vercel-ip-longitude")),
|
||||
tzIdentifier: allHeaders.get("x-vercel-ip-timezone") || undefined,
|
||||
@ -315,4 +324,48 @@ import.meta.vitest?.describe("getBrowserEndUserInfo(...)", () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("decodes URL-encoded city names from Vercel geo headers", () => {
|
||||
// Vercel percent-encodes city names, so a multi-word city arrives as "San%20Francisco".
|
||||
const result = getBrowserEndUserInfo(new Headers({
|
||||
"user-agent": "Mozilla/5.0",
|
||||
"x-vercel-forwarded-for": "203.0.113.10",
|
||||
"x-vercel-ip-country": "US",
|
||||
"x-vercel-ip-country-region": "CA",
|
||||
"x-vercel-ip-city": "San%20Francisco",
|
||||
"x-vercel-ip-latitude": "37.77",
|
||||
"x-vercel-ip-longitude": "-122.41",
|
||||
"x-vercel-ip-timezone": "America/Los_Angeles",
|
||||
}), "vercel");
|
||||
|
||||
expect(result).toEqual({
|
||||
maybeSpoofed: false,
|
||||
exactInfo: {
|
||||
ip: "203.0.113.10",
|
||||
countryCode: "US",
|
||||
regionCode: "CA",
|
||||
cityName: "San Francisco",
|
||||
latitude: 37.77,
|
||||
longitude: -122.41,
|
||||
tzIdentifier: "America/Los_Angeles",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("falls back to the raw city name when it is not valid percent-encoding", () => {
|
||||
// A lone "%" is invalid percent-encoding; decoding must not throw, just pass it through.
|
||||
const result = getBrowserEndUserInfo(new Headers({
|
||||
"user-agent": "Mozilla/5.0",
|
||||
"x-vercel-forwarded-for": "203.0.113.10",
|
||||
"x-vercel-ip-city": "100% Real City",
|
||||
}), "vercel");
|
||||
|
||||
expect(result).toEqual({
|
||||
maybeSpoofed: false,
|
||||
exactInfo: {
|
||||
ip: "203.0.113.10",
|
||||
cityName: "100% Real City",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user