Files
chat/api/server/middleware/requireGuestOrJwtAuth.js
zooqueen 4543cd9d14 Let a guest read back their own reply
The streaming hooks sent `Authorization: Bearer ${token}` whether or not there
was a token, so a guest's stream arrived carrying `Bearer undefined`. The server
reads any bearer as a claim to be somebody and asks IAM, IAM refuses an empty
one, and the guest was refused the one surface built for them — their message
sent and the reply never readable.

Both halves now agree that a credential is a token, not a header: the hooks send
the header only when they have something to put in it, and the guest check asks
whether a token is present rather than whether the header is. Either alone would
have fixed the symptom; both together make the disagreement impossible.

The 401 path in those hooks also still called the refresh endpoint, which no
longer exists — it now renews through IAM like every other caller.
2026-08-14 14:55:09 -07:00

36 lines
1.6 KiB
JavaScript

const requireJwtAuth = require('./requireJwtAuth');
const { getGuestConfig, buildGuestPrincipal } = require('~/server/services/guestConfig');
/**
* Authentication for the routes an anonymous visitor may reach.
*
* A guest has no account, so there is no identity to prove and nothing to
* verify: a request that carries no bearer becomes a guest, and a request that
* carries one is a member and goes to IAM like every other route. Nothing is
* issued to make this work — the visitor holds no credential at any point.
*
* Which visitor they are comes from `guestClientIp`, the same address the free
* message quota has always keyed on, so the anonymous visitor has ONE identity
* rather than a quota keyed on the network and a principal keyed on a token.
* `enforceGuestScope` and `guestMessageLimiter` do the rest; this only answers
* who is asking.
*
* @type {import('express').RequestHandler}
*/
const requireGuestOrJwtAuth = (req, res, next) => {
/* An EMPTY bearer is not a claim to be anybody. A caller with no token can
still send the header — `Bearer ` with nothing after it — and reading that
as "presented a credential" sends a guest to IAM, which refuses them, which
is a guest locked out of the one surface built for them. Ask whether there
is a token, not whether there is a header. */
const header = req.headers?.authorization;
const hasBearer = header?.startsWith('Bearer ') && header.slice('Bearer '.length).trim() !== '';
if (!hasBearer && getGuestConfig().enabled) {
req.user = buildGuestPrincipal(req);
return next();
}
return requireJwtAuth(req, res, next);
};
module.exports = requireGuestOrJwtAuth;