fix(water-log): set wl_admin_session cookie outside pg transaction
Deploy to route.crispygoat.com / deploy (push) Successful in 4m49s
Deploy to route.crispygoat.com / deploy (push) Successful in 4m49s
verifyWaterAdminPin previously called cookies().set(...) from inside the withBrand(...) pg transaction. The pg transaction's AsyncLocalStorage context masks Next.js's request-scoped cookie store, so for users without a platform login the cookie set threw and the route handler returned 500. Move the cookie write to the outer function frame, after withBrand resolves. The DB session row is still inserted inside the transaction (so it's atomic with the PIN verification); only the cookie write is hoisted out. The prior fix removed getAdminUser() but left the cookie set inside the transaction, so valid PINs still 500'd. This is the missing half. Adds a static regression test that asserts const cookieStore is declared AFTER the matching close-paren of withBrand(...).
This commit is contained in:
@@ -205,7 +205,12 @@ export async function verifyWaterAdminPin(
|
||||
const formatError = validatePin(pin);
|
||||
if (formatError) return { success: false, error: formatError };
|
||||
|
||||
return withBrand(brandId, async (db) => {
|
||||
// Run the DB work inside withBrand, but resolve the cookie write
|
||||
// OUTSIDE the transaction. Calling `cookies().set(...)` from inside
|
||||
// a pg transaction corrupts Next.js's request-scoped AsyncLocalStorage
|
||||
// and the cookie set throws for users without a platform login
|
||||
// (manifesting as a 500 from /api/water-admin-auth).
|
||||
const result = await withBrand(brandId, async (db) => {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(waterAdminSettings)
|
||||
@@ -213,17 +218,21 @@ const formatError = validatePin(pin);
|
||||
.limit(1);
|
||||
const settings = rows[0];
|
||||
if (!settings) {
|
||||
return { success: false, error: "Admin portal not configured" };
|
||||
return { success: false, error: "Admin portal not configured" } as const;
|
||||
}
|
||||
if (!settings.enabled) {
|
||||
return { success: false, error: "Admin portal is disabled" };
|
||||
return { success: false, error: "Admin portal is disabled" } as const;
|
||||
}
|
||||
if (!settings.pinHash) {
|
||||
return { success: false, error: "No PIN configured — generate one in /admin/water-log/settings" };
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
"No PIN configured — generate one in /admin/water-log/settings",
|
||||
} as const;
|
||||
}
|
||||
if (!verifyPin(pin, settings.pinHash)) {
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
return { success: false, error: "Invalid PIN" };
|
||||
return { success: false, error: "Invalid PIN" } as const;
|
||||
}
|
||||
|
||||
// The `/water/admin/login` page is Tuxedo-brand-specific and PIN-only.
|
||||
@@ -246,17 +255,29 @@ const formatError = validatePin(pin);
|
||||
expiresAt,
|
||||
})
|
||||
.returning({ id: waterAdminSessions.id });
|
||||
if (!session) return { success: false, error: "Failed to create session" };
|
||||
if (!session) {
|
||||
return { success: false, error: "Failed to create session" } as const;
|
||||
}
|
||||
|
||||
return {
|
||||
success: true as const,
|
||||
sessionId: session.id,
|
||||
sessionDurationHours: settings.sessionDurationHours,
|
||||
};
|
||||
});
|
||||
|
||||
// Cookie write happens AFTER withBrand resolves, in the outer request
|
||||
// frame where Next.js's cookie store is intact. See the comment above.
|
||||
if (result.success) {
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set("wl_admin_session", session.id, {
|
||||
cookieStore.set("wl_admin_session", result.sessionId, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
maxAge: settings.sessionDurationHours * 3600,
|
||||
maxAge: result.sessionDurationHours * 3600,
|
||||
path: "/",
|
||||
});
|
||||
|
||||
return { success: true, session_id: session.id };
|
||||
});
|
||||
return { success: true, session_id: result.sessionId };
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user