Bypass auth/session pages in frontend serviceworker cache

This commit is contained in:
svemagie
2026-03-08 05:06:29 +01:00
parent ef484c069d
commit 5b3e8aff3b
2 changed files with 97 additions and 17 deletions
+1 -1
View File
@@ -77,6 +77,6 @@
- The frontend sharp runtime patch makes icon generation non-fatal on FreeBSD when `sharp` cannot load, preventing startup crashes in asset controller imports.
- The files upload route patch fixes browser multi-upload by posting to `/files/upload` (session-authenticated) instead of direct `/media` calls without bearer token.
- The files upload locale patch adds missing `files.upload.dropText`/`files.upload.browse`/`files.upload.submitMultiple` labels in endpoint locale files so UI text does not render raw translation keys.
- The frontend serviceworker patch ensures `@indiekit/frontend/lib/serviceworker.js` exists at runtime to avoid ENOENT in the offline/service worker route.
- The frontend serviceworker patch ensures `@indiekit/frontend/lib/serviceworker.js` exists at runtime, and forces network-only handling for `/auth` and `/session` pages to avoid stale cached login/consent screens.
- The conversations guard patch prevents `Cannot read properties of undefined (reading 'find')` when the `conversation_items` collection is temporarily unavailable.
- The indieauth dev-mode guard patch prevents accidental production auth bypass by requiring explicit `INDIEKIT_ALLOW_DEV_AUTH=1` to enable dev auto-login.
+96 -16
View File
@@ -23,6 +23,51 @@ self.addEventListener("activate", (event) => {
self.addEventListener("fetch", () => {});
`;
const authBypassMarker = "Never cache auth/session pages";
const oldFetchCacheLine = " const retrieveFromCache = caches.match(request);";
const newFetchCacheBlock = ` const requestUrl = new URL(request.url);
// Never cache auth/session pages; always go to network.
if (
requestUrl.origin === self.location.origin &&
/^\\/(auth|session)(?:\\/|$)/.test(requestUrl.pathname)
) {
event.respondWith(fetch(request));
return;
}
const retrieveFromCache = caches.match(request);`;
const clearAuthSessionEntriesFn = `
async function clearAuthSessionEntries() {
try {
const pagesCache = await caches.open(pagesCacheName);
const keys = await pagesCache.keys();
await Promise.all(
keys
.filter((request) => {
const requestUrl = new URL(request.url);
return (
requestUrl.origin === self.location.origin &&
/^\\/(auth|session)(?:\\/|$)/.test(requestUrl.pathname)
);
})
.map((request) => pagesCache.delete(request)),
);
} catch (error) {
console.error("Error clearing auth/session cache entries", error);
}
}
`;
const activateOld = ` await clearOldCaches();
await clients.claim();`;
const activateNew = ` await clearOldCaches();
await clearAuthSessionEntries();
await clients.claim();`;
async function exists(filePath) {
try {
await access(filePath);
@@ -32,26 +77,61 @@ async function exists(filePath) {
}
}
if (await exists(expected)) {
console.log("[postinstall] frontend serviceworker already present");
process.exit(0);
function patchServiceworker(content) {
let updated = content;
if (!updated.includes(authBypassMarker) && updated.includes(oldFetchCacheLine)) {
updated = updated.replace(oldFetchCacheLine, newFetchCacheBlock);
}
if (
!updated.includes("async function clearAuthSessionEntries()") &&
updated.includes("async function trimCache(cacheName, maxItems)")
) {
updated = updated.replace(
"async function trimCache(cacheName, maxItems)",
`${clearAuthSessionEntriesFn}\nasync function trimCache(cacheName, maxItems)`,
);
}
if (updated.includes(activateOld)) {
updated = updated.replace(activateOld, activateNew);
}
return updated;
}
let sourcePath = null;
for (const candidate of candidates) {
if (await exists(candidate)) {
sourcePath = candidate;
break;
let restored = false;
if (!(await exists(expected))) {
let sourcePath = null;
for (const candidate of candidates) {
if (await exists(candidate)) {
sourcePath = candidate;
break;
}
}
await mkdir(path.dirname(expected), { recursive: true });
if (sourcePath) {
const content = await readFile(sourcePath, "utf8");
await writeFile(expected, content, "utf8");
restored = true;
console.log(`[postinstall] Restored frontend serviceworker from ${sourcePath}`);
} else {
await writeFile(expected, fallback, "utf8");
restored = true;
console.log("[postinstall] Created fallback frontend serviceworker");
}
}
await mkdir(path.dirname(expected), { recursive: true });
const source = await readFile(expected, "utf8");
const updated = patchServiceworker(source);
if (sourcePath) {
const content = await readFile(sourcePath, "utf8");
await writeFile(expected, content, "utf8");
console.log(`[postinstall] Restored frontend serviceworker from ${sourcePath}`);
} else {
await writeFile(expected, fallback, "utf8");
console.log("[postinstall] Created fallback frontend serviceworker");
if (updated !== source) {
await writeFile(expected, updated, "utf8");
console.log("[postinstall] Patched frontend serviceworker auth/session cache bypass");
} else if (!restored) {
console.log("[postinstall] frontend serviceworker already present");
}