Staf Booking
fixed staff ability to book a customer in
This commit is contained in:
8 files changed
+378
-174
No files matched your search
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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://");
|
||||
}
|
||||
Reference in new issue
Block a user