@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
168 lines (140 loc) • 6.71 kB
text/typescript
// *****************************************************************************
// Copyright (C) 2026 STMicroelectronics and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************
import { expect } from 'chai';
import * as http from 'http';
import {
BrowserConnectionToken,
BrowserConnectionTokenBackendContribution,
BROWSER_TOKEN_COOKIE_NAME,
createBrowserConnectionToken
} from './browser-connection-token';
describe('BrowserConnectionToken', () => {
it('should generate unique tokens', () => {
const token1 = createBrowserConnectionToken();
const token2 = createBrowserConnectionToken();
expect(token1.value).to.not.equal(token2.value);
});
it('should generate tokens of sufficient length', () => {
const token = createBrowserConnectionToken();
expect(token.value.length).to.be.greaterThan(16);
});
});
describe('BrowserConnectionTokenBackendContribution', () => {
const token: BrowserConnectionToken = createBrowserConnectionToken();
function createContribution(): BrowserConnectionTokenBackendContribution {
const contribution = new BrowserConnectionTokenBackendContribution();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(contribution as any)['browserConnectionToken'] = token;
return contribution;
}
function createRequest(cookieValue?: string): http.IncomingMessage {
const request = {
headers: {} as http.IncomingHttpHeaders
} as http.IncomingMessage;
if (cookieValue !== undefined) {
request.headers.cookie = `${BROWSER_TOKEN_COOKIE_NAME}=${cookieValue}`;
}
return request;
}
describe('allowWsUpgrade (WebSocket validation)', () => {
it('should allow WebSocket with valid token cookie', () => {
const contribution = createContribution();
expect(contribution.allowWsUpgrade(createRequest(token.value))).to.be.true;
});
it('should reject WebSocket with no cookie', () => {
const contribution = createContribution();
expect(contribution.allowWsUpgrade(createRequest())).to.be.false;
});
it('should reject WebSocket with invalid token', () => {
const contribution = createContribution();
expect(contribution.allowWsUpgrade(createRequest('wrong-token'))).to.be.false;
});
it('should reject WebSocket with stale token from previous server', () => {
const contribution = createContribution();
const staleToken = createBrowserConnectionToken().value;
expect(contribution.allowWsUpgrade(createRequest(staleToken))).to.be.false;
});
it('should reject WebSocket with empty token', () => {
const contribution = createContribution();
expect(contribution.allowWsUpgrade(createRequest(''))).to.be.false;
});
it('should handle multiple cookies correctly', () => {
const contribution = createContribution();
const request = {
headers: {
cookie: `other-cookie=foo; ${BROWSER_TOKEN_COOKIE_NAME}=${token.value}; another=bar`
} as http.IncomingHttpHeaders
} as http.IncomingMessage;
expect(contribution.allowWsUpgrade(request)).to.be.true;
});
});
describe('expressMiddleware (HTTP cookie flow)', () => {
interface CookieCall {
name: string;
value: string;
options: Record<string, unknown>;
}
interface MockResponse {
cookieCalls: CookieCall[];
nextCalled: boolean;
}
function callMiddleware(cookieValue?: string): MockResponse {
const contribution = createContribution();
const result: MockResponse = { cookieCalls: [], nextCalled: false };
const req = {
headers: {} as Record<string, string | undefined>,
socket: { remoteAddress: '127.0.0.1' }
};
if (cookieValue !== undefined) {
req.headers.cookie = `${BROWSER_TOKEN_COOKIE_NAME}=${cookieValue}`;
}
const res = {
cookie: (name: string, value: string, options: Record<string, unknown>) => {
result.cookieCalls.push({ name, value, options });
}
};
const next = (): void => { result.nextCalled = true; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(contribution as any).expressMiddleware(req, res, next);
return result;
}
it('should set cookie and allow request when no cookie present', () => {
const result = callMiddleware();
expect(result.nextCalled).to.be.true;
expect(result.cookieCalls).to.have.length(1);
expect(result.cookieCalls[0].name).to.equal(BROWSER_TOKEN_COOKIE_NAME);
expect(result.cookieCalls[0].value).to.equal(token.value);
});
it('should set cookie with HttpOnly, SameSite=Strict, and path=/', () => {
const result = callMiddleware();
const opts = result.cookieCalls[0].options;
expect(opts.httpOnly).to.be.true;
expect(opts.sameSite).to.equal('strict');
expect(opts.path).to.equal('/');
});
it('should allow request with valid cookie without re-setting it', () => {
const result = callMiddleware(token.value);
expect(result.nextCalled).to.be.true;
expect(result.cookieCalls).to.have.length(0);
});
it('should refresh stale cookie and allow request', () => {
const result = callMiddleware('stale-token-from-old-server');
expect(result.nextCalled).to.be.true;
expect(result.cookieCalls).to.have.length(1);
expect(result.cookieCalls[0].value).to.equal(token.value);
});
});
});