fix(web): make session cookie secure flag conditional on production
Setting Secure on http://localhost cookies works in Chrome (localhost exception) but Firefox/Safari silently drop them, so dev users hit 'redirect to /login on every click' after a 'successful' login. Switch to secure: NODE_ENV === 'production'. Public deploy still gets Secure-only. Also swap the login footer copy from a CLI hint to 'Forget Password? Contact IT' — operator-friendly, doesn't leak the bootstrap mechanism on the public sign-in screen. Test updated to assert secure=true under prod NODE_ENV and a new test locks in secure=false in dev. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ab51335a4
commit
ebbbdbdfb8
@ -79,21 +79,44 @@ function fd(fields: Record<string, string>): FormData {
|
||||
describe("loginAction", () => {
|
||||
it("issues a session cookie when credentials are correct", async () => {
|
||||
findUserMock.mockResolvedValue(ADMIN_ROW);
|
||||
const r = await loginAction(fd({ username: "admin", password: "correct-horse" })).catch(
|
||||
(e) => e,
|
||||
);
|
||||
// Successful login redirects, so the redirect mock throws.
|
||||
expect((r as Error).message).toBe("redirect");
|
||||
expect(cookiesSetMock).toHaveBeenCalledTimes(1);
|
||||
const [name, , attrs] = cookiesSetMock.mock.calls[0]!;
|
||||
expect(name).toBe("session");
|
||||
expect(attrs).toMatchObject({
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: 30 * 86400,
|
||||
});
|
||||
const prevEnv = process.env.NODE_ENV;
|
||||
// @ts-expect-error - test override
|
||||
process.env.NODE_ENV = "production";
|
||||
try {
|
||||
const r = await loginAction(fd({ username: "admin", password: "correct-horse" })).catch(
|
||||
(e) => e,
|
||||
);
|
||||
// Successful login redirects, so the redirect mock throws.
|
||||
expect((r as Error).message).toBe("redirect");
|
||||
expect(cookiesSetMock).toHaveBeenCalledTimes(1);
|
||||
const [name, , attrs] = cookiesSetMock.mock.calls[0]!;
|
||||
expect(name).toBe("session");
|
||||
expect(attrs).toMatchObject({
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: 30 * 86400,
|
||||
});
|
||||
} finally {
|
||||
// @ts-expect-error - test restore
|
||||
process.env.NODE_ENV = prevEnv;
|
||||
}
|
||||
});
|
||||
|
||||
it("sets secure=false on the cookie when NODE_ENV !== production", async () => {
|
||||
findUserMock.mockResolvedValue(ADMIN_ROW);
|
||||
const prevEnv = process.env.NODE_ENV;
|
||||
// @ts-expect-error - test override
|
||||
process.env.NODE_ENV = "development";
|
||||
try {
|
||||
await loginAction(fd({ username: "admin", password: "correct-horse" })).catch(() => {});
|
||||
const [, , attrs] = cookiesSetMock.mock.calls[0]!;
|
||||
expect(attrs).toMatchObject({ secure: false });
|
||||
} finally {
|
||||
// @ts-expect-error - test restore
|
||||
process.env.NODE_ENV = prevEnv;
|
||||
}
|
||||
});
|
||||
|
||||
it("returns ok:false on wrong password and does NOT set a cookie", async () => {
|
||||
|
||||
@ -98,9 +98,14 @@ export async function loginAction(formData: FormData): Promise<LoginResult> {
|
||||
secret,
|
||||
);
|
||||
const jar = await cookies();
|
||||
// Secure: only require https in production. In dev we hit
|
||||
// http://localhost:9000 directly, and Firefox/Safari silently drop
|
||||
// Set-Cookie when Secure is set on http origins (Chrome has a
|
||||
// localhost exception, others don't), which manifested as the
|
||||
// session cookie never being persisted across requests.
|
||||
jar.set(COOKIE_NAME, cookie, {
|
||||
httpOnly: true,
|
||||
secure: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: DEFAULT_TTL_SECONDS,
|
||||
|
||||
@ -59,8 +59,7 @@ export function LoginFormClient({ next }: { next: string }) {
|
||||
Sign in
|
||||
</Button>
|
||||
<p className="text-xs text-muted-foreground text-center">
|
||||
First time? Run <code>./scripts/set-password.sh <username></code>{" "}
|
||||
in your tools container.
|
||||
Forget Password? Contact IT
|
||||
</p>
|
||||
</form>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user