import { WALL_HELPER_NAME, WALL_HELPER_PATH, WALL_PAGE_HELPER_NAME } from './constants' /** * `pages/api/site-password.js`: the whole password page, served and signed * in through one route. * * GET serves the page: with a 401 when the middleware put it in place of what * was asked for (`?page=1`), with a 200 on a visit to the page's own address * (`?page=1&direct=1`). A route is used because it can answer with any * status, which a Next.js page cannot promise, and the wall needs its 401 so * nothing behind it is ever indexed. * * POST asks the platform whether the password is right (the site never holds * it) and, when it is, sets the wall cookie for the version the platform * names. Node runtime, so the HMAC uses Node's crypto; the middleware computes * the same value with the Web Crypto API (see utils/site-wall.js). */ export const buildSignInRouteSource = (): string => `// GENERATED by TeleportHQ — see utils/site-wall.js. import { createHmac } from 'crypto'; import { wallConfig, wallConfigured, WALL_COOKIE } from '../../${[ ...WALL_HELPER_PATH, WALL_HELPER_NAME, ].join('/')}'; import { renderPasswordPage } from '../../${[...WALL_HELPER_PATH, WALL_PAGE_HELPER_NAME].join( '/' )}'; var COOKIE_MAX_AGE_SECONDS = 30 * 24 * 60 * 60; function readPassword(body) { var source = body; if (typeof source === 'string') { try { source = JSON.parse(source); } catch (e) { return ''; } } var password = source && typeof source === 'object' ? source.password : ''; return typeof password === 'string' ? password.trim() : ''; } function servePage(req, res) { var direct = req.query && req.query.direct === '1'; res.setHeader('content-type', 'text/html; charset=utf-8'); res.setHeader('x-robots-tag', 'noindex'); return res.status(direct ? 200 : 401).send(renderPasswordPage()); } export default async function handler(req, res) { res.setHeader('cache-control', 'no-store'); if (req.method === 'GET' || req.method === 'HEAD') { if (req.query && req.query.page === '1') { return servePage(req, res); } res.setHeader('allow', 'POST'); return res.status(405).json({ ok: false }); } if (req.method !== 'POST') { res.setHeader('allow', 'POST'); return res.status(405).json({ ok: false }); } if (!wallConfigured()) { return res.status(404).json({ ok: false }); } var password = readPassword(req.body); if (!password || password.length > 128) { return res.status(400).json({ ok: false }); } var upstream; try { upstream = await fetch( wallConfig.apiUrl + '/site-wall/' + encodeURIComponent(wallConfig.projectId) + '/sign-in', { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify({ password: password }), } ); } catch (e) { return res.status(503).json({ ok: false }); } if (upstream.status === 401 || upstream.status === 403) { return res.status(401).json({ ok: false }); } if (upstream.status === 429) { return res.status(429).json({ ok: false }); } if (!upstream.ok) { return res.status(503).json({ ok: false }); } var answer = await upstream.json(); var version = answer && typeof answer.version === 'string' ? answer.version : ''; var value = createHmac('sha256', wallConfig.secret).update('wall:' + version).digest('hex'); var proto = String(req.headers['x-forwarded-proto'] || ''); var secure = proto.indexOf('https') === 0 || process.env.NODE_ENV === 'production'; res.setHeader( 'set-cookie', WALL_COOKIE + '=' + value + '; Path=/; Max-Age=' + COOKIE_MAX_AGE_SECONDS + '; HttpOnly; SameSite=Lax' + (secure ? '; Secure' : '') ); return res.status(200).json({ ok: true }); } `