Delete hard-coded Repixelizer callback trust (deletion_line 2)

acceptsBackendCallback is now a pure per-app env allow-list lookup;
Repixelizer's callback must be configured via
GC_ACCESS_APP_REPIXELIZER_BACKEND_CALLBACK_URLS like every other app (R8:
operator sets this on the running legacy body before the new build starts).
Test config adds the allow-list entry so existing Repixelizer flows keep
passing, and a new negative test pins that the slug name alone buys nothing.
This commit is contained in:
Meta Vens 2026-09-08 13:15:25 +02:00
commit c6d0ecb68b
2 changed files with 30 additions and 9 deletions

View file

@ -508,15 +508,7 @@ function acceptsBackendCallback(config: HeimdallConfig, appSlug: AppSlug, handof
return false;
}
if ((config.appBackendCallbacks[appSlug] ?? []).includes(handoff.callbackUrl)) {
return true;
}
if (appSlug === "repixelizer") {
return handoff.callbackUrl === "https://repixelizer.gamecult.org/api/auth/heimdall/callback";
}
return false;
return (config.appBackendCallbacks[appSlug] ?? []).includes(handoff.callbackUrl);
}
async function maybeDeliverBackendHandoff(handoff: OAuthHandoff, payload: BackendHandoffPayload): Promise<void> {

View file

@ -32,6 +32,7 @@ function createTestConfig(): HeimdallConfig {
},
appBackendCallbacks: {
bifrost: ["https://bifrost.gamecult.org/auth/heimdall/callback"],
repixelizer: ["https://repixelizer.gamecult.org/api/auth/heimdall/callback"],
},
storage: {
backend: "memory",
@ -898,6 +899,34 @@ describe("Heimdall service", () => {
);
});
it("no longer trusts Repixelizer's callback URL by slug name alone", async () => {
const baseConfig = createTestConfig();
const config = {
...baseConfig,
appBackendCallbacks: {},
};
const app = await buildApp({ config });
apps.push(app);
const response = await app.inject({
method: "POST",
url: "/v1/oauth/discord/start",
payload: {
appSlug: "repixelizer",
mode: "sign_in",
returnTo: "https://repixelizer.gamecult.org/app/",
handoff: {
kind: "backend_callback",
attemptId: "attempt-123",
callbackUrl: "https://repixelizer.gamecult.org/api/auth/heimdall/callback",
},
},
});
expect(response.statusCode).toBe(400);
expect(response.json()).toEqual(expect.objectContaining({ error: "untrusted_backend_callback" }));
});
it("resolves app-managed provider credentials without exposing refresh custody", async () => {
const store = new InMemoryStore();
const app = await buildApp({