← back to Designer Wallcoverings
Verify local session cookie with HMAC (close TK-11776 auth-bypass twin)
387873882b796c6f5db1d7be6f4ecb6a61fbb872 · 2026-09-22 11:13:20 -0700 · Steve Abrams
The local `session` cookie was accepted on existence alone in middleware and
minted unsigned as base64(user:timestamp) at login — the same forgeable class
TK-11776 #4 closed for dw_central_session, so `session=<anything>` still reached
the product-minting routes. Login now signs via signDWCentralSession; middleware
and /api/auth/check now HMAC-verify instead of trusting presence. Fails closed on
an unset DW_SESSION_SECRET. No other code reads/decodes the session cookie.
TK-11786 review finding #1 (block-on-merge).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwQJGSbGmK5rDzRsCqvVEc
Files touched
M DW-Programming/ImportNewSkufromURL/app/api/auth/check/route.tsM DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.tsM DW-Programming/ImportNewSkufromURL/middleware.ts
Diff
commit 387873882b796c6f5db1d7be6f4ecb6a61fbb872
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 22 11:13:20 2026 -0700
Verify local session cookie with HMAC (close TK-11776 auth-bypass twin)
The local `session` cookie was accepted on existence alone in middleware and
minted unsigned as base64(user:timestamp) at login — the same forgeable class
TK-11776 #4 closed for dw_central_session, so `session=<anything>` still reached
the product-minting routes. Login now signs via signDWCentralSession; middleware
and /api/auth/check now HMAC-verify instead of trusting presence. Fails closed on
an unset DW_SESSION_SECRET. No other code reads/decodes the session cookie.
TK-11786 review finding #1 (block-on-merge).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DwQJGSbGmK5rDzRsCqvVEc
---
.../app/api/auth/check/route.ts | 9 +++--
.../app/api/auth/login/route.ts | 8 +++--
DW-Programming/ImportNewSkufromURL/middleware.ts | 39 +++++++++-------------
3 files changed, 27 insertions(+), 29 deletions(-)
diff --git a/DW-Programming/ImportNewSkufromURL/app/api/auth/check/route.ts b/DW-Programming/ImportNewSkufromURL/app/api/auth/check/route.ts
index 6430651f..79ba46fb 100644
--- a/DW-Programming/ImportNewSkufromURL/app/api/auth/check/route.ts
+++ b/DW-Programming/ImportNewSkufromURL/app/api/auth/check/route.ts
@@ -1,11 +1,14 @@
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
+import { verifyDWCentralSession } from '@/lib/dw-central-session';
export async function GET() {
const cookieStore = await cookies();
const session = cookieStore.get('session');
- return NextResponse.json({
- authenticated: !!session?.value
- });
+ // Verify the HMAC signature, not mere presence (TK-11776 follow-up): otherwise the
+ // frontend would treat a forged base64 cookie as authenticated.
+ const authenticated = await verifyDWCentralSession(session?.value, process.env.DW_SESSION_SECRET);
+
+ return NextResponse.json({ authenticated });
}
diff --git a/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts b/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts
index b248a6fb..42a4155b 100644
--- a/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts
+++ b/DW-Programming/ImportNewSkufromURL/app/api/auth/login/route.ts
@@ -1,4 +1,5 @@
import { NextRequest, NextResponse } from 'next/server';
+import { signDWCentralSession } from '@/lib/dw-central-session';
// Credentials must be supplied via DW_BASIC_AUTH in "user:password" form.
// Fail-closed at module load — never default to a hardcoded literal.
@@ -19,8 +20,11 @@ export async function POST(request: NextRequest) {
const { username, password } = body;
if (username === USERNAME && password === PASSWORD) {
- // Create a session token (simple implementation)
- const sessionToken = Buffer.from(`${username}:${Date.now()}`).toString('base64');
+ // HMAC-sign the local session token with DW_SESSION_SECRET (TK-11776 follow-up):
+ // the middleware now VERIFIES this cookie, so an unsigned base64("user:ts") value is
+ // no longer accepted anywhere. signDWCentralSession throws if the secret is unset →
+ // the catch below returns 500 (fail-closed: never mint an unsigned/forgeable session).
+ const sessionToken = await signDWCentralSession(username, process.env.DW_SESSION_SECRET);
// Set cookie using response headers
const response = NextResponse.json({ success: true });
diff --git a/DW-Programming/ImportNewSkufromURL/middleware.ts b/DW-Programming/ImportNewSkufromURL/middleware.ts
index f9489ad8..0ec4d30a 100644
--- a/DW-Programming/ImportNewSkufromURL/middleware.ts
+++ b/DW-Programming/ImportNewSkufromURL/middleware.ts
@@ -1,25 +1,14 @@
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
+import { verifyDWCentralSession } from '@/lib/dw-central-session';
-// DW Central SSO: Check if user has a valid DW Central session
-function verifyDWCentralSession(token: string): boolean {
- try {
- // DW Central uses base64 encoded "username:timestamp" format
- const decoded = Buffer.from(token, 'base64').toString('utf-8');
- const [username, timestamp] = decoded.split(':');
- if (username && timestamp) {
- const sessionTime = parseInt(timestamp, 10);
- const now = Date.now();
- const maxAge = 86400000; // 24 hours
- return (now - sessionTime) < maxAge;
- }
- return false;
- } catch {
- return false;
- }
-}
+// DW Central SSO cookies are now HMAC-signed (TK-11776 finding #4). The verifier lives
+// in @/lib/dw-central-session and REQUIRES DW_SESSION_SECRET — an unsigned/forged cookie
+// (incl. every legacy base64('user:'+Date.now()) token) is rejected. The issuer at
+// DW Central must sign with the SAME secret using that module's signDWCentralSession, or
+// logins break. Fails closed when the secret is unset.
-export function middleware(request: NextRequest) {
+export async function middleware(request: NextRequest) {
const isLoginPage = request.nextUrl.pathname === '/login';
const isAuthApi = request.nextUrl.pathname.startsWith('/api/auth');
const isDashboard = request.nextUrl.pathname === '/apps-dashboard' || request.nextUrl.pathname.startsWith('/api/pm2');
@@ -32,17 +21,19 @@ export function middleware(request: NextRequest) {
// Check for DW Central SSO session first (from dw.greendomainbrokers.com)
const dwCentralToken = request.cookies.get('dw_central_session')?.value;
- if (dwCentralToken && verifyDWCentralSession(dwCentralToken)) {
+ if (dwCentralToken && (await verifyDWCentralSession(dwCentralToken, process.env.DW_SESSION_SECRET))) {
return NextResponse.next();
}
- // Check for local session
- const session = request.cookies.get('session');
- if (!session) {
- return NextResponse.redirect(new URL('/login', request.url));
+ // Check for local session — now HMAC-VERIFIED (TK-11776 follow-up), not existence-only.
+ // Previously ANY non-empty `session` cookie passed; the login route mints an HMAC-signed
+ // token via signDWCentralSession, so verification here rejects every unsigned/forged value.
+ const session = request.cookies.get('session')?.value;
+ if (session && (await verifyDWCentralSession(session, process.env.DW_SESSION_SECRET))) {
+ return NextResponse.next();
}
- return NextResponse.next();
+ return NextResponse.redirect(new URL('/login', request.url));
}
export const config = {
← b8ec6402 auto-data-snapshot: 2026-09-22T11:11:00 (3 data files) — sho
·
back to Designer Wallcoverings
·
Apply TK-11786 review fixes #2/#3/#6/#7 (gate 4th writer, fa e3301879 →