DEV Community

Roberto Luna
Roberto Luna

Posted on

Adding a Time‑Based “Login‑Free” Window to the /tv Route in CraveView

Adding a Time‑Based “Login‑Free” Window to the /tv Route in CraveView

TL;DR: I added a temporary rule that lets anyone access /tv without a session cookie between 9 AM‑6 PM America/Cancun. The change lives in src/lib/auth.ts (new helper) and src/middleware.ts (conditional auth bypass).


The Problem

Our Next.js app protects every route with a cookie‑based session check in src/middleware.ts. The /tv endpoint is meant for public streaming, but during a live‑event we wanted to open it to anyone only during a specific time window (9 AM‑6 PM America/Cancun). Outside that window the route must still require authentication, otherwise we expose premium content.

The symptom was simple: when we tried to hit /tv from a browser without a login cookie during the event hours, the middleware responded with a 302 redirect to /login. The log looked like:

[middleware] ❌ AUTH_COOKIE missing for /tv – redirecting to /login
Enter fullscreen mode Exit fullscreen mode

We needed a clean way to short‑circuit the auth check based on the current time, without sprinkling ad‑hoc if statements throughout the codebase.


What I Tried First

My first instinct was to add a hard‑coded if block directly inside src/middleware.ts:

if (req.nextUrl.pathname === "/tv") {
  const now = new Date();
  if (now.getHours() >= 9 && now.getHours() < 18) {
    return NextResponse.next(); // skip auth
  }
}
Enter fullscreen mode Exit fullscreen mode

Two problems emerged:

  1. Time zone mismatchDate defaults to the server’s UTC, while the event schedule is in America/Cancun (UTC‑5/‑4). I had to manually adjust offsets, which is error‑prone.
  2. Scattered logic – Adding route‑specific checks inside the generic auth middleware made the file harder to read and forced every future “temporary free window” to be added in the same place.

Both issues forced me to rethink the implementation.


The Implementation

1. Extract a reusable time‑window helper

I created a tiny utility in src/lib/auth.ts called isWithinPeriod. It receives a start/end hour and a TZ identifier, then returns a boolean. Keeping it in the auth library makes the function discoverable for any future auth‑related logic.

// src/lib/auth.ts
import { zonedTimeToUtc, utcToZonedTime } from "date-fns-tz";

export function isWithinPeriod(
  startHour: number,
  endHour: number,
  timeZone: string = "America/Cancun"
): boolean {
  // Current time in the target zone
  const now = utcToZonedTime(new Date(), timeZone);
  const hour = now.getHours();

  // Simple inclusive start / exclusive end check
  if (startHour <= endHour) {
    return hour >= startHour && hour < endHour;
  }

  // Edge case: window spans midnight (e.g., 22‑2)
  return hour >= startHour || hour < endHour;
}

/**
 * TEMPORARY – allow /tv without login between 9 AM‑6 PM America/Cancun.
 * This is a thin wrapper around `isWithinPeriod` so the intent stays obvious.
 */
export function isTvFreeWindow(): boolean {
  return isWithinPeriod(9, 18, "America/Cancun");
}
Enter fullscreen mode Exit fullscreen mode

Why date-fns-tz? It’s lightweight, already a dependency in the project, and guarantees correct DST handling for the Cancun zone.

2. Refactor the middleware to use the helper

In src/middleware.ts I replaced the previous import list and added the new function:

// src/middleware.ts
import { NextResponse, type NextRequest } from "next/server";
import {
  AUTH_COOKIE_NAME,
  getExpectedToken,
  isTvFreeWindow, // <-- new import
} from "@/lib/auth";

export async function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl;

  // 1️⃣ Short‑circuit for the temporary free window
  if (pathname === "/tv" && isTvFreeWindow()) {
    // No auth needed, just let the request continue
    console.log("[middleware] ✅ /tv free window – bypass auth");
    return NextResponse.next();
  }

  // 2️⃣ Regular auth flow for everything else
  const token = req.cookies.get(AUTH_COOKIE_NAME)?.value ?? null;
  const expected = await getExpectedToken();

  if (!token || token !== expected) {
    console.log(`[middleware] ❌ AUTH_COOKIE missing or invalid for ${pathname}`);
    return NextResponse.redirect(new URL("/login", req.url));
  }

  // Auth succeeded
  return NextResponse.next();
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • Early return – The free‑window check is performed before any cookie lookup, keeping the happy path fast.
  • Explicit logging – I added a console line to make the bypass visible in server logs during the event.
  • No mutation of existing logic – The rest of the middleware stays untouched, preserving the contract for all other routes.

3. Adjust the diff for completeness

The commit that landed on 2026‑08‑29 added 16 lines to auth.ts (the helper functions) and 8 lines to middleware.ts (import + early‑return block). The diff looked like this:

src/lib/auth.ts
@@ -24,3 +24,19 @@ export async function getExpectedToken(): Promise<string | null> {
   if (!creds) return null;
   return computeSessionToken(creds.user, creds.password);
 }

+// TEMPORARY – allow /tv without login between 9 AM‑6 PM America/Cancun
+export function isWithinPeriod(startHour: number, endHour: number, tz = "America/Cancun"): boolean {
+  const now = utcToZonedTime(new Date(), tz);
+  const hour = now.getHours();
+  if (startHour <= endHour) {
+    return hour >= startHour && hour < endHour;
+  }
+  // window spans midnight
+  return hour >= startHour || hour < endHour;
+}
+
+export function isTvFreeWindow(): boolean {
+  return isWithinPeriod(9, 18, "America/Cancun");
+}
Enter fullscreen mode Exit fullscreen mode
src/middleware.ts
@@ -1,5 +1,8 @@
 import { NextResponse, type NextRequest } from "next/server";
-import { AUTH_COOKIE_NAME, getExpectedToken } from "@/lib/auth";
+import {
+  AUTH_COOKIE_NAME,
+  getExpectedToken,
+  isTvFreeWindow,
+} from "@/lib/auth";

 export async function middleware(req: NextRequest) {
   const { pathname } = req.nextUrl;

   // TEMPORARY bypass for /tv during the free window
-  if (pathname === "/tv" && isWithinP) { /* truncated in diff */ }
+  if (pathname === "/tv" && isTvFreeWindow()) {
+    console.log("[middleware] ✅ /tv free window – bypass auth");
+    return NextResponse.next();
+  }

   // …rest of auth logic…
Enter fullscreen mode Exit fullscreen mode

4. Testing the change

I added a simple integration test in tests/middleware.test.ts:

import { middleware } from "@/middleware";
import { NextRequest } from "next/server";

test("bypasses auth for /tv during free window", async () => {
  // Mock the helper to return true regardless of real time
  jest.spyOn(require("@/lib/auth"), "isTvFreeWindow").mockReturnValue(true);

  const req = new NextRequest("http://localhost/tv");
  const res = await middleware(req);
  expect(res?.status).toBeUndefined(); // NextResponse.next() has no status
});
Enter fullscreen mode Exit fullscreen mode

Running npm test passed, confirming the early return works even when the real clock is outside the window (thanks to the mock).


Key Takeaway

Isolate time‑sensitive gate logic in a dedicated, testable helper. By moving the “is it within this window?” check out of the generic middleware, we keep the auth pipeline clean, make the


Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.

Repo: zaerohell/craveview · 2026-08-29

#playadev #buildinpublic

Top comments (0)