Files
chat/api/server/middleware/enforceGuestScope.js
zooqueen 47ad491722 fix(guest): serve logged-out chat on a zero-cost free model
The guest model was a paid house SKU (zen5-flash) that draws on the deployment
balance, so with the balance overdrawn a guest's first message returned 402 and
signed-out chat did not work at all. Pin the guest to a free route
(nvidia/nemotron-nano-9b-v2:free) — the same free id @hanzo/ai names for a
signed-in fallback — so a guest is served at zero cost regardless of balance.

Guest scope now overrides the endpoint/model to the free one rather than
rejecting a mismatch, so a stale client model can never leave a guest unable to
send, while a guest still can never reach a paid route. The free guest model and
endpoint are published in startup config so the client can open and name the
conversation on it. Bumps @hanzo/ai to 0.3.4.
2026-08-14 20:57:43 -07:00

56 lines
1.8 KiB
JavaScript

const { logger } = require('@hanzochat/data-schemas');
const { EModelEndpoint } = require('@hanzochat/data-provider');
const { getGuestConfig } = require('~/server/services/guestConfig');
/**
* Server-side capability scope for guest principals.
*
* Runs after guest authentication and before `buildEndpointOption`. For guest
* requests it pins the endpoint and model to the configured free model and strips
* every capability a guest must not reach (paid models, agents, tools, files,
* model specs, presets). The client is never trusted: whatever endpoint or model
* it names is overridden to the free one, so a guest is served free no matter
* what they send and can never reach a paid route.
*
* Non-guest requests pass through untouched.
*
* @param {ServerRequest} req
* @param {ServerResponse} res
* @param {import('express').NextFunction} next
*/
const enforceGuestScope = (req, res, next) => {
if (req.user?.guest !== true) {
return next();
}
const config = getGuestConfig();
if (!config.enabled) {
return res.status(404).json({ message: 'Guest chat is not enabled' });
}
const body = req.body || {};
const { endpoint, model } = body;
if ((endpoint != null && endpoint !== config.endpoint) || (model != null && model !== config.model)) {
logger.warn(`[enforceGuestScope] Guest ${req.user.id} pinned to the free model from ${endpoint}/${model}`);
}
body.endpoint = config.endpoint;
body.endpointType = EModelEndpoint.custom;
body.model = config.model;
delete body.agent_id;
delete body.spec;
delete body.preset;
delete body.files;
delete body.tools;
delete body.tool_resources;
delete body.resendFiles;
delete body.promptPrefix;
delete body.web_search;
req.body = body;
return next();
};
module.exports = enforceGuestScope;