import { PASSWORD_PAGE_ROUTE, SIGN_IN_ROUTE, WALL_COOKIE } from './constants' /** * `utils/site-wall.js`: what the middleware (Edge runtime) and the sign-in * route (Node runtime) share. Web APIs only — no Node module may be imported * here, the middleware bundle would refuse it. */ export const buildWallHelperSource = (): string => `// GENERATED by TeleportHQ — the password wall shared by middleware.js and // pages/api/site-password.js. Nothing here runs while the site was published // without a wall (the TELEPORT_WALL_* values below are empty). export var WALL_COOKIE = '${WALL_COOKIE}'; export var PASSWORD_PAGE = '${PASSWORD_PAGE_ROUTE}'; export var SIGN_IN_ROUTE = '${SIGN_IN_ROUTE}'; // Literal process.env reads: Next.js hands the middleware only the variables // it sees named in the source. export var wallConfig = { defaultOn: process.env.TELEPORT_WALL === 'on', defaultVersion: process.env.TELEPORT_WALL_VERSION || '', secret: process.env.TELEPORT_WALL_SECRET || '', projectId: process.env.TELEPORT_PROJECT_ID || '', apiUrl: (process.env.TELEPORT_API_URL || '').replace(/\\/+$/, ''), }; export function wallConfigured() { return !!(wallConfig.secret && wallConfig.projectId && wallConfig.apiUrl); } // Paths that stay open: the site's hashed scripts and styles (no content in // them) and its API routes (payment webhooks, cron runs, the site's own server // calls, and the route that serves the password page and signs in). export function isOpenPath(pathname) { return ( pathname.indexOf('/_next/static/') === 0 || pathname === '/api' || pathname.indexOf('/api/') === 0 ); } var encoder = new TextEncoder(); function toHex(buffer) { var bytes = new Uint8Array(buffer); var out = ''; for (var i = 0; i < bytes.length; i++) { out += (bytes[i] < 16 ? '0' : '') + bytes[i].toString(16); } return out; } // What a signed-in visitor's cookie holds: HMAC-SHA256 of the wall's version // under the site's secret. Refreshing the password changes the version, so // every cookie expires at once; the password itself never reaches the site. export async function cookieValueFor(version) { var key = await crypto.subtle.importKey( 'raw', encoder.encode(wallConfig.secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); var signature = await crypto.subtle.sign('HMAC', key, encoder.encode('wall:' + version)); return toHex(signature); } // Constant-time string comparison, so a cookie cannot be guessed byte by byte. export function sameValue(a, b) { if (typeof a !== 'string' || typeof b !== 'string' || a.length !== b.length) { return false; } var diff = 0; for (var i = 0; i < a.length; i++) { diff |= a.charCodeAt(i) ^ b.charCodeAt(i); } return diff === 0; } // Whether the site is walled right now, and the wall's version, from the // platform. Throws when the platform cannot be reached or answers nonsense; // the caller keeps the state it had. export async function fetchWallState() { var response = await fetch( wallConfig.apiUrl + '/site-wall/' + encodeURIComponent(wallConfig.projectId), { headers: { accept: 'application/json' }, cache: 'no-store' } ); if (!response.ok) { throw new Error('wall state answered ' + response.status); } var json = await response.json(); if (!json || typeof json.walled !== 'boolean') { throw new Error('wall state malformed'); } return { on: json.walled, version: typeof json.version === 'string' ? json.version : '' }; } `