fix(version): move the finer geo state to the admin status

geoLookup and geoProviderChosen on the public /api/version added one
more way to tell instances apart. They now live on /api/admin/status
(requireAdmin, cookie only) as geo { lookup, providerChosen,
providerHost }, which both admin status views read. offlineGeoEnabled
and geoProviderHost stay public as before.
This commit is contained in:
Marc-André Bombeck committed 2026-10-09 10:44:18 +02:00
1 parent 0ea8d929d2
commit 470ddeedc4
11 files changed
+135 -128

No files matched your search

@@ -44,6 +44,17 @@ vi.mock("@/lib/jobs/worker-status", () => ({
})),
}));
const geoMock = vi.hoisted(() => ({
lookup: "online" as "offline" | "online" | "off",
chosen: false,
host: "ipwho.is",
}));
vi.mock("@/lib/geo", () => ({
geoLookupMode: () => geoMock.lookup,
geoProviderChosen: () => geoMock.chosen,
resolveGeoProviderHost: () => geoMock.host,
}));
vi.mock("@/lib/jobs/job-failures", () => ({
JOB_FAILURE_WINDOW_HOURS: 72,
readFailingQueues: vi.fn(),
@@ -208,4 +219,17 @@ describe("GET /api/admin/status (integration health summary)", () => {
],
});
});
it("carries the login-IP geo state the public version route no longer shows", async () => {
geoMock.lookup = "online";
geoMock.chosen = true;
geoMock.host = "ip-api.com";
const res = await GET();
const body = (await res.json()) as { data: { geo: unknown } };
expect(body.data.geo).toEqual({
lookup: "online",
providerChosen: true,
providerHost: "ip-api.com",
});
});
});
+12
View File
@@ -9,6 +9,11 @@ import {
readFailingQueues,
} from "@/lib/jobs/job-failures";
import { readFileSync } from "node:fs";
import {
geoLookupMode,
geoProviderChosen,
resolveGeoProviderHost,
} from "@/lib/geo";
// Build timestamp (set at build time via next.config)
const BUILD_TIME = process.env.BUILD_TIMESTAMP || new Date().toISOString();
@@ -109,5 +114,12 @@ export const GET = apiHandler(async () => {
: null,
webPush: webPushConfigured ? { configured: true } : null,
},
// How login IPs are placed on this host. Admin-only: the public
// `/api/version` keeps just `offlineGeoEnabled` and `geoProviderHost`.
geo: {
lookup: geoLookupMode(),
providerChosen: geoProviderChosen(),
providerHost: resolveGeoProviderHost(),
},
});
});
+19 -2
View File
@@ -9,8 +9,6 @@ const resolveGeoProviderHostMock = vi.fn(() => "ipwho.is");
vi.mock("@/lib/geo", () => ({
offlineGeoReady: () => offlineGeoReadyMock(),
resolveGeoProviderHost: () => resolveGeoProviderHostMock(),
geoLookupMode: () => (offlineGeoReadyMock() ? "offline" : "online"),
geoProviderChosen: () => false,
}));
import { GET } from "../route";
@@ -108,4 +106,23 @@ describe("GET /api/version", () => {
const body = (await response.json()) as VersionEnvelope;
expect(body.data.geoProviderHost).toBe("ip-api.com");
});
it("keeps the finer geo state off the public answer", async () => {
// Lookup mode and whether the operator chose the provider are admin-only
// (`/api/admin/status`); the public route would only add a way to tell
// instances apart.
const response = await (GET as unknown as () => Promise<Response>)();
const body = (await response.json()) as { data: Record<string, unknown> };
expect(Object.keys(body.data).sort()).toEqual([
"buildSha",
"builtAt",
"changelog",
"docs",
"geoProviderHost",
"license",
"offlineGeoEnabled",
"repository",
"version",
]);
});
});
+5 -9
View File
@@ -1,11 +1,6 @@
import { apiHandler } from "@/lib/api-handler";
import { apiSuccess } from "@/lib/api-response";
import {
geoLookupMode,
geoProviderChosen,
offlineGeoReady,
resolveGeoProviderHost,
} from "@/lib/geo";
import { offlineGeoReady, resolveGeoProviderHost } from "@/lib/geo";
import packageJson from "../../../../package.json";
// v1.4.27 R5 — `offlineGeoEnabled` reads from the same source the geo
@@ -41,7 +36,10 @@ export const dynamic = "force-dynamic";
* `geoProviderHost` (the host of `IP_GEO_LOOKUP_URL`, default
* `ipwho.is`) and the resolver emits a one-shot admin notification on
* first use. `geoProviderHost` lets the admin status surface name the
* real provider instead of assuming the default.
* real provider instead of assuming the default. Both were public before
* v1.42 and stay so for existing readers; the finer geo state (lookup mode,
* whether the operator chose the provider) is admin-only and lives on
* `/api/admin/status`, so it adds nothing to tell instances apart by.
*/
export const GET = apiHandler(async () => {
// v1.4.43 B11 — prefer the build-arg-injected env var so the runtime
@@ -69,7 +67,5 @@ export const GET = apiHandler(async () => {
docs: "https://docs.healthlog.dev",
offlineGeoEnabled,
geoProviderHost,
geoLookup: geoLookupMode(),
geoProviderChosen: geoProviderChosen(),
});
});