@theia/core 1.74.0-next.5 → 1.74.0-next.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +4 -4
  2. package/lib/browser/catalog.json +300 -36
  3. package/lib/browser/common-frontend-contribution.d.ts.map +1 -1
  4. package/lib/browser/common-frontend-contribution.js +1 -2
  5. package/lib/browser/common-frontend-contribution.js.map +1 -1
  6. package/lib/browser/keybinding.d.ts.map +1 -1
  7. package/lib/browser/keybinding.js +12 -7
  8. package/lib/browser/keybinding.js.map +1 -1
  9. package/lib/browser/preferences/preference-contribution.d.ts.map +1 -1
  10. package/lib/browser/preferences/preference-contribution.js +9 -1
  11. package/lib/browser/preferences/preference-contribution.js.map +1 -1
  12. package/lib/browser/preferences/preference-service.spec.js +63 -0
  13. package/lib/browser/preferences/preference-service.spec.js.map +1 -1
  14. package/lib/browser/shell/application-shell.d.ts.map +1 -1
  15. package/lib/browser/shell/application-shell.js +19 -26
  16. package/lib/browser/shell/application-shell.js.map +1 -1
  17. package/lib/browser/tree/tree-widget.d.ts +7 -33
  18. package/lib/browser/tree/tree-widget.d.ts.map +1 -1
  19. package/lib/browser/tree/tree-widget.js +7 -54
  20. package/lib/browser/tree/tree-widget.js.map +1 -1
  21. package/lib/browser/widgets/select-component.d.ts +2 -2
  22. package/lib/browser/widgets/select-component.d.ts.map +1 -1
  23. package/lib/browser/widgets/select-component.js +42 -12
  24. package/lib/browser/widgets/select-component.js.map +1 -1
  25. package/lib/common/preferences/index.d.ts +1 -0
  26. package/lib/common/preferences/index.d.ts.map +1 -1
  27. package/lib/common/preferences/index.js +1 -0
  28. package/lib/common/preferences/index.js.map +1 -1
  29. package/lib/common/preferences/preference-scope.d.ts +6 -1
  30. package/lib/common/preferences/preference-scope.d.ts.map +1 -1
  31. package/lib/common/preferences/preference-scope.js +5 -0
  32. package/lib/common/preferences/preference-scope.js.map +1 -1
  33. package/lib/common/preferences/preference-scope.spec.js +5 -3
  34. package/lib/common/preferences/preference-scope.spec.js.map +1 -1
  35. package/lib/common/preferences/preference-service.d.ts +19 -0
  36. package/lib/common/preferences/preference-service.d.ts.map +1 -1
  37. package/lib/common/preferences/preference-service.js +34 -5
  38. package/lib/common/preferences/preference-service.js.map +1 -1
  39. package/lib/common/preferences/session-preference-provider.d.ts +17 -0
  40. package/lib/common/preferences/session-preference-provider.d.ts.map +1 -0
  41. package/lib/common/preferences/session-preference-provider.js +77 -0
  42. package/lib/common/preferences/session-preference-provider.js.map +1 -0
  43. package/lib/common/preferences/session-preference-provider.spec.d.ts +2 -0
  44. package/lib/common/preferences/session-preference-provider.spec.d.ts.map +1 -0
  45. package/lib/common/preferences/session-preference-provider.spec.js +71 -0
  46. package/lib/common/preferences/session-preference-provider.spec.js.map +1 -0
  47. package/lib/electron-node/hosting/electron-backend-hosting-module.d.ts.map +1 -1
  48. package/lib/electron-node/hosting/electron-backend-hosting-module.js +7 -0
  49. package/lib/electron-node/hosting/electron-backend-hosting-module.js.map +1 -1
  50. package/lib/electron-node/hosting/electron-backend-hosting-module.spec.d.ts +2 -0
  51. package/lib/electron-node/hosting/electron-backend-hosting-module.spec.d.ts.map +1 -0
  52. package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js +39 -0
  53. package/lib/electron-node/hosting/electron-backend-hosting-module.spec.js.map +1 -0
  54. package/lib/node/hosting/backend-hosting-module.d.ts.map +1 -1
  55. package/lib/node/hosting/backend-hosting-module.js +4 -1
  56. package/lib/node/hosting/backend-hosting-module.js.map +1 -1
  57. package/lib/node/hosting/browser-connection-token.d.ts +29 -1
  58. package/lib/node/hosting/browser-connection-token.d.ts.map +1 -1
  59. package/lib/node/hosting/browser-connection-token.js +25 -1
  60. package/lib/node/hosting/browser-connection-token.js.map +1 -1
  61. package/lib/node/hosting/browser-connection-token.spec.js +70 -0
  62. package/lib/node/hosting/browser-connection-token.spec.js.map +1 -1
  63. package/lib/node/index.d.ts +1 -0
  64. package/lib/node/index.d.ts.map +1 -1
  65. package/lib/node/index.js +3 -1
  66. package/lib/node/index.js.map +1 -1
  67. package/package.json +9 -9
  68. package/src/browser/common-frontend-contribution.ts +1 -2
  69. package/src/browser/keybinding.ts +12 -7
  70. package/src/browser/preferences/preference-contribution.ts +9 -1
  71. package/src/browser/preferences/preference-service.spec.ts +72 -0
  72. package/src/browser/shell/application-shell.ts +19 -25
  73. package/src/browser/style/select-component.css +16 -0
  74. package/src/browser/style/sidepanel.css +5 -0
  75. package/src/browser/style/status-bar.css +1 -1
  76. package/src/browser/tree/tree-widget.tsx +12 -78
  77. package/src/browser/widgets/select-component.tsx +41 -12
  78. package/src/common/preferences/index.ts +1 -0
  79. package/src/common/preferences/preference-scope.spec.ts +5 -3
  80. package/src/common/preferences/preference-scope.ts +6 -1
  81. package/src/common/preferences/preference-service.ts +39 -5
  82. package/src/common/preferences/session-preference-provider.spec.ts +78 -0
  83. package/src/common/preferences/session-preference-provider.ts +69 -0
  84. package/src/electron-node/hosting/electron-backend-hosting-module.spec.ts +42 -0
  85. package/src/electron-node/hosting/electron-backend-hosting-module.ts +8 -0
  86. package/src/node/hosting/backend-hosting-module.ts +5 -2
  87. package/src/node/hosting/browser-connection-token.spec.ts +92 -0
  88. package/src/node/hosting/browser-connection-token.ts +45 -1
  89. package/src/node/index.ts +1 -0
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { expect } from 'chai';
18
18
  import * as http from 'http';
19
+ import { environment } from '../../common';
19
20
  import {
20
21
  BrowserConnectionToken,
21
22
  BrowserConnectionTokenBackendContribution,
@@ -164,4 +165,95 @@ describe('BrowserConnectionTokenBackendContribution', () => {
164
165
  expect(result.cookieCalls[0].value).to.equal(token.value);
165
166
  });
166
167
  });
168
+
169
+ describe('validateRequest (HTTP token enforcement)', () => {
170
+
171
+ interface ValidationResult {
172
+ nextCalled: boolean;
173
+ sentStatus?: number;
174
+ }
175
+
176
+ function callValidate(cookieValue?: string): ValidationResult {
177
+ const contribution = createContribution();
178
+ const result: ValidationResult = { nextCalled: false };
179
+
180
+ const req = { headers: {} as Record<string, string | undefined> };
181
+ if (cookieValue !== undefined) {
182
+ req.headers.cookie = `${BROWSER_TOKEN_COOKIE_NAME}=${cookieValue}`;
183
+ }
184
+
185
+ const res = {
186
+ sendStatus: (status: number) => { result.sentStatus = status; }
187
+ };
188
+
189
+ const next = (): void => { result.nextCalled = true; };
190
+
191
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
+ (contribution as any).validateRequest(req, res, next);
193
+ return result;
194
+ }
195
+
196
+ it('should allow request with valid token cookie', () => {
197
+ const result = callValidate(token.value);
198
+ expect(result.nextCalled).to.be.true;
199
+ expect(result.sentStatus).to.be.undefined;
200
+ });
201
+
202
+ it('should reject request with no cookie', () => {
203
+ const result = callValidate();
204
+ expect(result.nextCalled).to.be.false;
205
+ expect(result.sentStatus).to.equal(403);
206
+ });
207
+
208
+ it('should reject request with invalid token', () => {
209
+ const result = callValidate('wrong-token');
210
+ expect(result.nextCalled).to.be.false;
211
+ expect(result.sentStatus).to.equal(403);
212
+ });
213
+
214
+ it('should reject request with stale token from previous server', () => {
215
+ const staleToken = createBrowserConnectionToken().value;
216
+ const result = callValidate(staleToken);
217
+ expect(result.nextCalled).to.be.false;
218
+ expect(result.sentStatus).to.equal(403);
219
+ });
220
+
221
+ it('should reject request with empty token', () => {
222
+ const result = callValidate('');
223
+ expect(result.nextCalled).to.be.false;
224
+ expect(result.sentStatus).to.equal(403);
225
+ });
226
+ });
227
+
228
+ describe('Electron deployment (no-op)', () => {
229
+
230
+ let originalIsElectron: () => boolean;
231
+
232
+ beforeEach(() => {
233
+ originalIsElectron = environment.electron.is;
234
+ environment.electron.is = () => true;
235
+ });
236
+
237
+ afterEach(() => {
238
+ environment.electron.is = originalIsElectron;
239
+ });
240
+
241
+ it('validateRequest should call next() without a cookie', () => {
242
+ const contribution = createContribution();
243
+ let nextCalled = false;
244
+ let sentStatus: number | undefined;
245
+ const req = { headers: {} as Record<string, string | undefined> };
246
+ const res = { sendStatus: (status: number) => { sentStatus = status; } };
247
+ const next = (): void => { nextCalled = true; };
248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
249
+ (contribution as any).validateRequest(req, res, next);
250
+ expect(nextCalled).to.be.true;
251
+ expect(sentStatus).to.be.undefined;
252
+ });
253
+
254
+ it('allowWsUpgrade should return true without a cookie', () => {
255
+ const contribution = createContribution();
256
+ expect(contribution.allowWsUpgrade(createRequest())).to.be.true;
257
+ });
258
+ });
167
259
  });
@@ -33,6 +33,26 @@ export interface BrowserConnectionToken {
33
33
  value: string;
34
34
  }
35
35
 
36
+ export const HttpConnectionValidator = Symbol('HttpConnectionValidator');
37
+
38
+ /**
39
+ * Express middleware provider that rejects HTTP requests lacking a valid connection token.
40
+ *
41
+ * The connection-token cookie is only *bootstrapped* globally (see
42
+ * {@link BrowserConnectionTokenBackendContribution}); enforcement is opt-in per route.
43
+ * Security-sensitive HTTP endpoints (e.g. the filesystem upload/download routes) should
44
+ * inject this and apply {@link validateRequest} as route middleware. Non-sensitive routes
45
+ * (the initial HTML page, static assets) must not use it, as they legitimately have no
46
+ * cookie yet on the very first page load.
47
+ */
48
+ export interface HttpConnectionValidator {
49
+ /**
50
+ * Express middleware that calls `next()` when the request carries a valid connection-token
51
+ * cookie (or when running in Electron) and responds with `403` otherwise.
52
+ */
53
+ validateRequest(req: express.Request, res: express.Response, next: express.NextFunction): void;
54
+ }
55
+
36
56
  /**
37
57
  * Validates WebSocket and HTTP requests using a cookie-based connection token.
38
58
  *
@@ -40,13 +60,18 @@ export interface BrowserConnectionToken {
40
60
  * as a `SameSite=Strict; HttpOnly` cookie on the first page load. Cross-origin pages
41
61
  * cannot obtain or send this cookie, so their requests are rejected.
42
62
  *
63
+ * The cookie is *bootstrapped* for every HTTP request (via {@link expressMiddleware}) so that
64
+ * browsers always receive it, but HTTP requests are only *rejected* on routes that opt in to
65
+ * enforcement via {@link validateRequest} (see {@link HttpConnectionValidator}). WebSocket
66
+ * upgrades are always validated (see {@link allowWsUpgrade}).
67
+ *
43
68
  * This complements the origin validator: non-browser callers that omit the Origin
44
69
  * header (e.g. Node.js scripts) still cannot reach the backend without the cookie.
45
70
  *
46
71
  * Skipped in Electron deployments (which use their own `ElectronSecurityToken`).
47
72
  */
48
73
  @injectable()
49
- export class BrowserConnectionTokenBackendContribution implements BackendApplicationContribution, WsRequestValidatorContribution {
74
+ export class BrowserConnectionTokenBackendContribution implements BackendApplicationContribution, WsRequestValidatorContribution, HttpConnectionValidator {
50
75
 
51
76
  @inject(BrowserConnectionToken)
52
77
  protected readonly browserConnectionToken: BrowserConnectionToken;
@@ -84,6 +109,25 @@ export class BrowserConnectionTokenBackendContribution implements BackendApplica
84
109
  return false;
85
110
  }
86
111
 
112
+ /**
113
+ * Reject the request with `403` unless it carries a valid connection-token cookie.
114
+ * Always allows the request in Electron deployments, consistent with {@link allowWsUpgrade}.
115
+ */
116
+ validateRequest(req: express.Request, res: express.Response, next: express.NextFunction): void {
117
+ if (environment.electron.is()) {
118
+ next();
119
+ return;
120
+ }
121
+ const token = this.getTokenFromCookie(req);
122
+ if (token && this.isTokenValid(token)) {
123
+ next();
124
+ return;
125
+ }
126
+ // No cookie or stale/invalid token: reject. Legitimate browsers always have the cookie
127
+ // because it is set on the initial page load, and same-origin requests send it automatically.
128
+ res.sendStatus(403);
129
+ }
130
+
87
131
  protected expressMiddleware(req: express.Request, res: express.Response, next: express.NextFunction): void {
88
132
  const existing = this.getTokenFromCookie(req);
89
133
  if (!existing || !this.isTokenValid(existing)) {
package/src/node/index.ts CHANGED
@@ -23,3 +23,4 @@ export * from './cli';
23
23
  export * from './setting-service';
24
24
  export { FileSystemLocking } from './filesystem-locking';
25
25
  export { BackendRequestAllowedContribution } from './request/backend-request-facade';
26
+ export { HttpConnectionValidator } from './hosting/browser-connection-token';