feat: add query-param token auth and optional HTTP auth to gateway

Support ?token= query parameter as a fallback for WebSocket clients that
cannot set Authorization headers (e.g. browsers). Add authHttp option to
GatewayServer so token auth can be applied to HTTP requests too, returning
401 with WWW-Authenticate header on failure.
This commit is contained in:
William Valentin
2026-02-06 16:51:41 -08:00
parent 0eb1f7a073
commit 20930a4816
4 changed files with 132 additions and 12 deletions
+45 -1
View File
@@ -6,6 +6,10 @@ function mockRequest(headers: Record<string, string> = {}): IncomingMessage {
return { headers } as unknown as IncomingMessage;
}
function mockRequestWithUrl(url: string, headers: Record<string, string> = {}): IncomingMessage {
return { url, headers } as unknown as IncomingMessage;
}
describe('authenticateRequest', () => {
describe('no auth configured', () => {
it('allows all connections', () => {
@@ -30,7 +34,7 @@ describe('authenticateRequest', () => {
it('rejects missing Authorization header', () => {
const result = authenticateRequest(mockRequest(), config);
expect(result.authenticated).toBe(false);
expect(result.error).toContain('Authorization header required');
expect(result.error).toContain('Authorization required');
});
it('rejects invalid token', () => {
@@ -82,4 +86,44 @@ describe('authenticateRequest', () => {
expect(result.identity).toBe('anonymous');
});
});
describe('query parameter token', () => {
const config = { token: 'secret-token-123' };
it('accepts valid token in query parameter', () => {
const result = authenticateRequest(
mockRequestWithUrl('/?token=secret-token-123'),
config,
);
expect(result.authenticated).toBe(true);
expect(result.identity).toBe('token-user');
});
it('rejects invalid token in query parameter', () => {
const result = authenticateRequest(
mockRequestWithUrl('/?token=wrong'),
config,
);
expect(result.authenticated).toBe(false);
expect(result.error).toContain('Invalid token');
});
it('prefers header over query parameter', () => {
const result = authenticateRequest(
mockRequestWithUrl('/?token=wrong', { authorization: 'Bearer secret-token-123' }),
config,
);
expect(result.authenticated).toBe(true);
expect(result.identity).toBe('token-user');
});
it('rejects when neither header nor query parameter provided', () => {
const result = authenticateRequest(
mockRequestWithUrl('/'),
config,
);
expect(result.authenticated).toBe(false);
expect(result.error).toContain('Authorization required');
});
});
});