import http from 'node:http' import https from 'node:https' import { createHmac, timingSafeEqual } from 'node:crypto' const publicPort = Number(process.env.PORT || '7860') const upstreamUrl = new URL( process.env.WORKSPACE_UPSTREAM_URL || `http://127.0.0.1:${process.env.WORKSPACE_INTERNAL_PORT || '7861'}`, ) const accessPassword = process.env.SPACE_ACCESS_PASSWORD || '' const sessionSecret = process.env.SPACE_SESSION_SECRET || accessPassword const cookieName = 'hermes-space-access' const loginPath = '/__space_auth/login' const logoutPath = '/__space_auth/logout' const healthPath = '/__space_auth/health' const authorizedToken = signToken('authorized') if (!accessPassword) { console.error( '[space-access] Missing SPACE_ACCESS_PASSWORD. Configure it as a Hugging Face Space secret.', ) process.exit(1) } function signToken(scope) { return createHmac('sha256', sessionSecret) .update(`${scope}:${accessPassword}`) .digest('base64url') } function safeEqual(left, right) { const leftBuffer = Buffer.from(left) const rightBuffer = Buffer.from(right) if (leftBuffer.length !== rightBuffer.length) { return false } return timingSafeEqual(leftBuffer, rightBuffer) } function parseCookies(cookieHeader = '') { const cookies = {} for (const chunk of cookieHeader.split(';')) { const trimmed = chunk.trim() if (!trimmed) continue const separatorIndex = trimmed.indexOf('=') if (separatorIndex === -1) continue const key = trimmed.slice(0, separatorIndex) const value = trimmed.slice(separatorIndex + 1) cookies[key] = value } return cookies } function isAuthorized(request) { const cookies = parseCookies(request.headers.cookie) const token = cookies[cookieName] if (!token) return false return safeEqual(token, authorizedToken) } function wantsHtml(request) { const accept = request.headers.accept || '' return accept.includes('text/html') } function readJsonBody(request) { return new Promise((resolve, reject) => { const chunks = [] let size = 0 request.on('data', (chunk) => { size += chunk.length if (size > 16 * 1024) { reject(new Error('Body too large')) request.destroy() return } chunks.push(chunk) }) request.on('end', () => { try { const raw = Buffer.concat(chunks).toString('utf8') resolve(raw ? JSON.parse(raw) : {}) } catch (error) { reject(error) } }) request.on('error', reject) }) } function sendJson(response, statusCode, payload, extraHeaders = {}) { response.writeHead(statusCode, { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store', ...extraHeaders, }) response.end(JSON.stringify(payload)) } function sendPromptPage(response) { const html = ` Protected Workspace

Workspace Locked

This Space is protected. Enter the access password in the browser prompt to continue.

Nothing inside the workspace will load until the correct password is entered.

` response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store', }) response.end(html) } function proxyRequest(clientRequest, clientResponse) { const proxyModule = upstreamUrl.protocol === 'https:' ? https : http const headers = { ...clientRequest.headers } headers.host = upstreamUrl.host headers['x-forwarded-host'] = clientRequest.headers.host || '' headers['x-forwarded-proto'] = 'https' headers['x-forwarded-port'] = String(publicPort) const upstreamRequest = proxyModule.request( { protocol: upstreamUrl.protocol, hostname: upstreamUrl.hostname, port: upstreamUrl.port, method: clientRequest.method, path: clientRequest.url, headers, }, (upstreamResponse) => { const responseHeaders = { ...upstreamResponse.headers } clientResponse.writeHead(upstreamResponse.statusCode || 502, responseHeaders) upstreamResponse.pipe(clientResponse) }, ) upstreamRequest.on('error', (error) => { console.error('[space-access] Upstream request failed:', error) sendJson(clientResponse, 502, { ok: false, error: 'Workspace upstream is unavailable.', }) }) clientRequest.pipe(upstreamRequest) } const server = http.createServer(async (request, response) => { const url = new URL(request.url || '/', `http://${request.headers.host || '127.0.0.1'}`) if (url.pathname === healthPath) { sendJson(response, 200, { ok: true, upstream: upstreamUrl.toString(), }) return } if (url.pathname === logoutPath && request.method === 'POST') { sendJson( response, 200, { ok: true }, { 'Set-Cookie': `${cookieName}=; HttpOnly; SameSite=Lax; Path=/; Max-Age=0`, }, ) return } if (url.pathname === loginPath && request.method === 'POST') { try { const body = await readJsonBody(request) const submittedPassword = body && typeof body.password === 'string' ? body.password : '' if (safeEqual(submittedPassword, accessPassword)) { sendJson( response, 200, { ok: true }, { 'Set-Cookie': `${cookieName}=${authorizedToken}; HttpOnly; SameSite=Lax; Path=/; Max-Age=${30 * 24 * 60 * 60}`, }, ) return } sendJson(response, 401, { ok: false, error: 'Invalid password' }) return } catch (error) { sendJson(response, 400, { ok: false, error: error instanceof Error ? error.message : 'Invalid request', }) return } } if (!isAuthorized(request)) { if (wantsHtml(request)) { sendPromptPage(response) return } sendJson(response, 401, { ok: false, error: 'Authentication required', }) return } proxyRequest(request, response) }) server.listen(publicPort, '0.0.0.0', () => { console.log( `[space-access] Password gate listening on http://0.0.0.0:${publicPort} -> ${upstreamUrl.toString()}`, ) })