@vertesia/ui 1.6.0-dev.20261014.015055Z → 1.6.0-dev.20261022.120426Z

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 (62) hide show
  1. package/lib/core/components/shadcn/selectBox.js +1 -1
  2. package/lib/core/components/shadcn/selectBox.js.map +1 -1
  3. package/lib/features/agent/PayloadBuilder.d.ts +1 -1
  4. package/lib/features/oauth/useMcpConnections.d.ts.map +1 -1
  5. package/lib/features/oauth/useMcpConnections.js +21 -8
  6. package/lib/features/oauth/useMcpConnections.js.map +1 -1
  7. package/lib/features/store/objects/DocumentTable.d.ts.map +1 -1
  8. package/lib/features/store/objects/DocumentTable.js +0 -9
  9. package/lib/features/store/objects/DocumentTable.js.map +1 -1
  10. package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  11. package/lib/features/store/objects/components/ContentOverview.js +2 -30
  12. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  13. package/lib/features/store/objects/components/VectorSearchWidget.d.ts.map +1 -1
  14. package/lib/features/store/objects/components/VectorSearchWidget.js +1 -1
  15. package/lib/features/store/objects/components/VectorSearchWidget.js.map +1 -1
  16. package/lib/features/store/objects/components/textPanelSource.d.ts +14 -0
  17. package/lib/features/store/objects/components/textPanelSource.d.ts.map +1 -0
  18. package/lib/features/store/objects/components/textPanelSource.js +52 -0
  19. package/lib/features/store/objects/components/textPanelSource.js.map +1 -0
  20. package/lib/features/store/objects/upload/DocumentUploadModal.d.ts.map +1 -1
  21. package/lib/features/store/objects/upload/DocumentUploadModal.js +13 -9
  22. package/lib/features/store/objects/upload/DocumentUploadModal.js.map +1 -1
  23. package/lib/session/auth/oauth.d.ts.map +1 -1
  24. package/lib/session/auth/oauth.js +18 -10
  25. package/lib/session/auth/oauth.js.map +1 -1
  26. package/lib/vertesia-ui-boot.js.map +1 -1
  27. package/lib/vertesia-ui-core.js +76 -76
  28. package/lib/vertesia-ui-core.js.map +1 -1
  29. package/lib/vertesia-ui-env.js.map +1 -1
  30. package/lib/vertesia-ui-features.js +339 -339
  31. package/lib/vertesia-ui-features.js.map +1 -1
  32. package/lib/vertesia-ui-i18n.js +1 -1
  33. package/lib/vertesia-ui-i18n.js.map +1 -1
  34. package/lib/vertesia-ui-layout.js +16 -16
  35. package/lib/vertesia-ui-layout.js.map +1 -1
  36. package/lib/vertesia-ui-rich-text.js +118 -118
  37. package/lib/vertesia-ui-rich-text.js.map +1 -1
  38. package/lib/vertesia-ui-router.js +1 -1
  39. package/lib/vertesia-ui-router.js.map +1 -1
  40. package/lib/vertesia-ui-session.js +1 -1
  41. package/lib/vertesia-ui-session.js.map +1 -1
  42. package/lib/vertesia-ui-shell.js +26 -26
  43. package/lib/vertesia-ui-shell.js.map +1 -1
  44. package/lib/vertesia-ui-widgets.js +148 -148
  45. package/lib/vertesia-ui-widgets.js.map +1 -1
  46. package/lib/widgets/markdown/MermaidDiagram.d.ts.map +1 -1
  47. package/lib/widgets/markdown/MermaidDiagram.js +5 -2
  48. package/lib/widgets/markdown/MermaidDiagram.js.map +1 -1
  49. package/lib/widgets/upload/DropZone.d.ts.map +1 -1
  50. package/lib/widgets/upload/DropZone.js +1 -0
  51. package/lib/widgets/upload/DropZone.js.map +1 -1
  52. package/package.json +46 -41
  53. package/src/core/components/shadcn/selectBox.tsx +1 -1
  54. package/src/features/oauth/useMcpConnections.ts +26 -8
  55. package/src/features/store/objects/DocumentTable.tsx +0 -9
  56. package/src/features/store/objects/components/ContentOverview.tsx +2 -34
  57. package/src/features/store/objects/components/VectorSearchWidget.tsx +4 -2
  58. package/src/features/store/objects/components/textPanelSource.ts +58 -0
  59. package/src/features/store/objects/upload/DocumentUploadModal.tsx +14 -10
  60. package/src/session/auth/oauth.ts +18 -9
  61. package/src/widgets/markdown/MermaidDiagram.tsx +5 -2
  62. package/src/widgets/upload/DropZone.tsx +1 -0
@@ -6,6 +6,7 @@ import { markCentralAuthRoundTripStarted } from './authRoundTrip';
6
6
  import { verifyAuthState } from './authState';
7
7
  import { usesGatewaySession } from './gateway';
8
8
  import { isStsTokenIssuer } from './tokenIssuer';
9
+ import { isLoopbackHostname } from './vertesiaHosts';
9
10
 
10
11
  const TRANSACTION_KEY = 'vertesia.oauth.transaction';
11
12
  const TOKEN_KEY = 'vertesia.oauth.access';
@@ -55,9 +56,13 @@ function base64url(bytes: Uint8Array): string {
55
56
  function randomValue(): string {
56
57
  return base64url(crypto.getRandomValues(new Uint8Array(32)));
57
58
  }
58
- function httpsUrl(value: string): URL {
59
+ /** Validate a public protocol endpoint URL. The returned URL is routing metadata, not a credential. */
60
+ function validatedEndpointUrl(value: string): URL {
59
61
  const url = new URL(value);
60
- if (url.protocol !== 'https:' || url.username || url.password) throw new Error('OAuth endpoint must use HTTPS');
62
+ const localHttp = Env.isLocalDev && url.protocol === 'http:' && isLoopbackHostname(url.hostname);
63
+ if ((url.protocol !== 'https:' && !localHttp) || url.username || url.password) {
64
+ throw new Error('OAuth endpoint must use HTTPS or local-development loopback HTTP');
65
+ }
61
66
  return url;
62
67
  }
63
68
 
@@ -97,11 +102,13 @@ async function acquireToken(forceRefresh = false): Promise<string> {
97
102
  }
98
103
  const redirectUri = redirect.toString();
99
104
  if (new URL(redirectUri).origin !== window.location.origin) throw new Error('OAuth callback must be same-origin');
100
- const issuer = (config.issuer ? httpsUrl(config.issuer).toString() : Env.endpoints.sts).replace(/\/+$/, '');
105
+ const issuer = validatedEndpointUrl(config.issuer ?? Env.endpoints.sts)
106
+ .toString()
107
+ .replace(/\/+$/, '');
101
108
  // A token is issued for one audience. The default audience follows the issuer, which is already part of every
102
109
  // stored credential's identity; a configured one is recorded alongside it, so changing it never reuses a token,
103
110
  // refresh session or login transaction issued for the previous audience.
104
- const configuredResource = config.resource ? httpsUrl(config.resource).toString() : undefined;
111
+ const configuredResource = config.resource ? validatedEndpointUrl(config.resource).toString() : undefined;
105
112
  const sameClient = (stored: { clientId: string; issuer: string; resource?: string }) =>
106
113
  stored.clientId === clientId && stored.issuer === issuer && stored.resource === configuredResource;
107
114
  let current = new URL(window.location.href);
@@ -184,10 +191,12 @@ async function acquireToken(forceRefresh = false): Promise<string> {
184
191
  revocation_endpoint?: string;
185
192
  } = await discovery.json();
186
193
  if (metadata.issuer.replace(/\/+$/, '') !== issuer) throw new Error('OAuth issuer mismatch');
187
- const authorize = httpsUrl(metadata.authorization_endpoint);
188
- const tokenEndpoint = httpsUrl(metadata.token_endpoint);
194
+ const authorize = validatedEndpointUrl(metadata.authorization_endpoint);
195
+ const tokenEndpoint = validatedEndpointUrl(metadata.token_endpoint);
189
196
  const revocationEndpoint =
190
- metadata.revocation_endpoint === undefined ? undefined : httpsUrl(metadata.revocation_endpoint).toString();
197
+ metadata.revocation_endpoint === undefined
198
+ ? undefined
199
+ : validatedEndpointUrl(metadata.revocation_endpoint).toString();
191
200
  const resource = configuredResource ?? new URL(metadata.issuer).toString();
192
201
  if (refreshCredential) {
193
202
  try {
@@ -299,7 +308,7 @@ async function acquireToken(forceRefresh = false): Promise<string> {
299
308
  }
300
309
  let scopes = config.scopes ?? ['openid', 'profile'];
301
310
  if (clientId.startsWith('https://')) {
302
- const documentResponse = await fetch(httpsUrl(clientId), { cache: 'no-store' });
311
+ const documentResponse = await fetch(validatedEndpointUrl(clientId), { cache: 'no-store' });
303
312
  if (!documentResponse.ok) throw new Error(`Application CIMD unavailable (${documentResponse.status})`);
304
313
  const document: { scope?: string; redirect_uris?: string[] } = await documentResponse.json();
305
314
  if (!document.redirect_uris?.includes(redirectUri))
@@ -368,7 +377,7 @@ export async function revokeAppOAuthSession(): Promise<void> {
368
377
  const cached = refreshSession;
369
378
  clearAppOAuth();
370
379
  if (!cached) return;
371
- const revoke = httpsUrl(cached.revocationEndpoint ?? cached.tokenEndpoint);
380
+ const revoke = validatedEndpointUrl(cached.revocationEndpoint ?? cached.tokenEndpoint);
372
381
  if (cached.revocationEndpoint === undefined) revoke.pathname = revoke.pathname.replace(/\/token$/, '/revoke');
373
382
  await fetch(revoke, {
374
383
  method: 'POST',
@@ -92,7 +92,10 @@ function loadMermaid(): Promise<Mermaid> {
92
92
  mermaid.initialize({
93
93
  startOnLoad: false,
94
94
  theme: 'default',
95
- securityLevel: 'loose',
95
+ // Diagram source comes from LLM output and documents, so it is untrusted. 'strict' makes Mermaid
96
+ // sanitize the whole SVG with DOMPurify, drop `javascript:` link targets and ignore click callbacks;
97
+ // 'loose' let `click A href "javascript:…"` through to the page.
98
+ securityLevel: 'strict',
96
99
  fontFamily: MERMAID_FONT_FAMILY,
97
100
  suppressErrorRendering: true,
98
101
  flowchart: {
@@ -202,7 +205,7 @@ export function MermaidDiagram({ code, className }: MermaidDiagramProps) {
202
205
  <div
203
206
  ref={containerRef}
204
207
  className={`my-4 w-full overflow-x-auto [&_svg]:mx-auto [&_svg]:w-full [&_svg]:h-auto [&_svg]:max-w-full ${className || ''}`}
205
- // biome-ignore lint/security/noDangerouslySetInnerHtml: SVG is produced by Mermaid renderer from a known mermaid-source string
208
+ // biome-ignore lint/security/noDangerouslySetInnerHtml: Mermaid sanitizes its SVG output under securityLevel 'strict'
206
209
  dangerouslySetInnerHTML={{ __html: svg }}
207
210
  />
208
211
  );
@@ -94,6 +94,7 @@ export function DropZone({ onDrop, message, buttonLabel, allowFolders = true, cl
94
94
 
95
95
  const handleDrop = async (e: React.DragEvent) => {
96
96
  e.preventDefault();
97
+ e.stopPropagation();
97
98
  setIsDragging(false);
98
99
 
99
100
  if (e.dataTransfer.items && e.dataTransfer.items.length > 0) {