Staf Booking

fixed staff ability to book a customer in
This commit is contained in:
Stefan Lazic committed 2026-08-06 14:49:46 +01:00
1 parent 973f18f64c
commit 90ec663e6e
8 files changed
+378 -174

No files matched your search

+4 -3
View File
@@ -1,5 +1,6 @@
import "server-only";
import { cookies } from "next/headers";
import { isSecureDeployment } from "@/lib/security/cookie-security";
import { bookingDraftSchema, emptyDraft, emptyStaffDraft, staffBookingDraftSchema } from "./schema";
import type { BookingDraft, StaffBookingDraft } from "./schema";
import { sign, verify } from "./sign";
@@ -35,7 +36,7 @@ export async function writeDraft(draft: BookingDraft): Promise<void> {
const token = sign(JSON.stringify(draft));
store.set(COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
secure: isSecureDeployment(),
sameSite: "lax",
path: "/",
maxAge: MAX_AGE_SECONDS,
@@ -66,7 +67,7 @@ export async function writeLastBookingReference(reference: LastBookingReference)
const token = sign(JSON.stringify(reference));
store.set(CONFIRMATION_COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
secure: isSecureDeployment(),
sameSite: "lax",
path: "/",
maxAge: CONFIRMATION_MAX_AGE_SECONDS,
@@ -120,7 +121,7 @@ export async function writeStaffDraft(draft: StaffBookingDraft): Promise<void> {
const token = sign(JSON.stringify(draft));
store.set(STAFF_COOKIE_NAME, token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
secure: isSecureDeployment(),
sameSite: "lax",
path: "/",
maxAge: MAX_AGE_SECONDS,
@@ -0,0 +1,31 @@
import { afterEach, describe, expect, it } from "vitest";
import { isSecureDeployment } from "../cookie-security";
describe("isSecureDeployment", () => {
const originalAppUrl = process.env.NEXT_PUBLIC_APP_URL;
afterEach(() => {
if (originalAppUrl === undefined) delete process.env.NEXT_PUBLIC_APP_URL;
else process.env.NEXT_PUBLIC_APP_URL = originalAppUrl;
});
it("is true when the deployment's own URL is https", () => {
process.env.NEXT_PUBLIC_APP_URL = "https://barbershop.example.com";
expect(isSecureDeployment()).toBe(true);
});
it("is false for a plain-http deployment — e.g. the documented default self-hosted setup", () => {
process.env.NEXT_PUBLIC_APP_URL = "http://localhost:3000";
expect(isSecureDeployment()).toBe(false);
});
it("is false for a plain-http LAN address, regardless of NODE_ENV", () => {
process.env.NEXT_PUBLIC_APP_URL = "http://192.168.1.50:3000";
expect(isSecureDeployment()).toBe(false);
});
it("is false when unset", () => {
delete process.env.NEXT_PUBLIC_APP_URL;
expect(isSecureDeployment()).toBe(false);
});
});
+25
View File
@@ -0,0 +1,25 @@
/**
* Whether this deployment is actually served over HTTPS — the correct
* signal for a cookie's `Secure` attribute, which every browser enforces
* by silently refusing to store if the response wasn't delivered over
* HTTPS (see MDN/RFC 6265bis). `NODE_ENV === "production"` is NOT that
* signal: this app's own Dockerfile hard-sets `NODE_ENV=production`
* regardless of whether the operator has put TLS in front of it, and
* docker-compose.local.yaml ships with no TLS termination at all — a
* self-hosted instance reached at "http://<lan-ip>:3000" (the default,
* documented setup) is exactly `NODE_ENV=production` *and* plain HTTP.
* A cookie marked `Secure` there is silently dropped by the browser on
* write, with no error surfaced anywhere — this was the root cause of a
* real bug where the staff "book for customer" flow's draft cookie
* vanished between steps, bouncing the user back to customer search with
* no explanation. NextAuth's own session cookie doesn't hit this because
* it adapts via `AUTH_TRUST_HOST`/its own request-aware cookie config;
* this app's hand-rolled cookies (booking draft, staff booking draft,
* last-booking reference, Google Calendar OAuth state) need the same
* request-aware treatment, derived here from the deployer-declared
* `NEXT_PUBLIC_APP_URL` (see .env.local.example — defaults to
* "http://localhost:3000") rather than blindly trusting NODE_ENV.
*/
export function isSecureDeployment(): boolean {
return (process.env.NEXT_PUBLIC_APP_URL ?? "").startsWith("https://");
}