@robosystems/core 0.8.5 → 0.8.7

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.
package/auth-core/sso.js CHANGED
@@ -9,6 +9,28 @@ const debugLog = (message, error) => {
9
9
  console.debug(`[SSO] ${message}`, error);
10
10
  }
11
11
  };
12
+ /**
13
+ * Reduce a `returnUrl` to a same-origin path, or reject it.
14
+ *
15
+ * The value reaches us from a query parameter (or the sessionStorage mirror of
16
+ * one), so it is attacker-controlled on any crafted SSO link: an absolute URL
17
+ * would bounce the freshly authenticated user to another site, and a
18
+ * `javascript:` payload would execute as this origin. Cross-app returns don't
19
+ * need the escape hatch — `getSSORedirectUrl` hands the path to the *target*
20
+ * app, which resolves it against its own origin — so a bare absolute path is
21
+ * the only shape we ever legitimately produce.
22
+ */
23
+ const sanitizeReturnUrl = (returnUrl) => {
24
+ if (!returnUrl)
25
+ return null;
26
+ // `//host` and `/\host` are both protocol-relative once a browser
27
+ // normalises them, so a leading slash alone isn't enough.
28
+ if (!returnUrl.startsWith('/'))
29
+ return null;
30
+ if (returnUrl.startsWith('//') || returnUrl.startsWith('/\\'))
31
+ return null;
32
+ return returnUrl;
33
+ };
12
34
  export class SSOManager {
13
35
  constructor(apiUrl) {
14
36
  this.authClient = new RoboSystemsAuthClient(apiUrl);
@@ -84,7 +106,7 @@ export class SSOManager {
84
106
  debugLog('sessionStorage read failed during SSO login', error);
85
107
  }
86
108
  const finalSessionId = sessionId; // Only use URL parameter for session ID
87
- const finalReturnUrl = returnUrl || sessionStorageReturn;
109
+ const finalReturnUrl = sanitizeReturnUrl(returnUrl || sessionStorageReturn);
88
110
  if (!finalSessionId) {
89
111
  return null;
90
112
  }
@@ -3,7 +3,7 @@ import type { ConsoleConfig, SampleQuery } from './types';
3
3
  /**
4
4
  * Graph-aware console configuration.
5
5
  *
6
- * The console's natural-language agent (the "cypher operator") and its example
6
+ * The console's natural-language agent (the Analyst Operator) and its example
7
7
  * queries only make sense against the schema of the *selected* graph. A shared
8
8
  * SEC repository, a RoboLedger accounting graph, a RoboInvestor portfolio
9
9
  * graph, and a generic custom graph each expose completely different nodes —
@@ -19,7 +19,7 @@ export interface GraphExampleSet {
19
19
  * builder swaps for the graph's display name when available. */
20
20
  description: string;
21
21
  /** Natural-language prompts shown in the USAGE section. Must be answerable by
22
- * the cypher operator against THIS graph's schema. */
22
+ * the Analyst Operator against THIS graph's schema. */
23
23
  naturalLanguageExamples: string[];
24
24
  /** `/query <cypher>` one-liners shown in the USAGE section. */
25
25
  directQueryExamples: string[];
package/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { buildGraphAwareConsoleConfig, ConsoleContent, EXAMPLE_SETS, getGraphExa
14
14
  export type { ConsoleBranding, ConsoleCommandContext, ConsoleConfig, ConsoleExtraCommand, ConsoleHeaderConfig, ConsoleMcpConfig, ConsoleWelcomeConfig, GraphExampleKind, GraphExampleSet, SampleQuery, } from './components/console';
15
15
  export { ActiveSubscriptions, BrowseRepositories, EntitySelector, EntitySelectorCore, GraphSelectorCore, PageLayout, RepositoryGuard, SearchBar, SearchContent, SearchHitCard, SearchPagination, SearchResultsMeta, useIsRepository, type ActiveSubscriptionsProps, type BrowseRepositoriesProps, type EntityGroup, type EntityLike, type EntityRecord, type EntitySelectorProps, type GraphSelectorProps, type GraphWithEntities, type SearchBarProps, type SearchConfig, type SearchFilterConfig, type SearchHitCardProps, type SearchPaginationProps, type SearchResultsMetaProps, type SelectableEntity, } from './components';
16
16
  export { composeFilters, excludeRepositories, excludeSubgraphs, GraphFilters, hasSchemaExtension, onlyRepositories, onlyUserGraphs, } from './components/graph-filters';
17
- export { ApiKeyDisplay, ApiKeysCard, ApiKeyTable, BrandSpinner, categorizeError, CategoryInput, ChatHeader, ChatInputArea, ChatMessage, ConfirmModal, CoreNavbar, CoreSidebar, CreateApiKeyModal, DeepResearchToggle, EmptyState, ErrorType, GeneralInformationCard, generateMessageId, getErrorMessage, LandingFooter, LoadingState, MarkdownProse, PageContainer, PageHeader, PasswordInformationCard, PasswordRequirements, SecureApiKeyField, SettingsCard, SettingsContainer, SettingsFormField, SettingsPageHeader, Spinner, StatCard, StatusAlert, SupportModal, TagInput, ThemeToggle, } from './ui-components';
17
+ export { ApiKeyDisplay, BrandSpinner, categorizeError, CategoryInput, ChatHeader, ChatInputArea, ChatMessage, ConfirmModal, CoreNavbar, CoreSidebar, CreateApiKeyModal, DeepResearchToggle, EmptyState, ErrorType, GeneralInformationCard, generateMessageId, getErrorMessage, LandingFooter, LoadingState, MarkdownProse, PageContainer, PageHeader, PasswordInformationCard, PasswordRequirements, SecureApiKeyField, SettingsCard, SettingsContainer, SettingsFormField, SettingsPageHeader, Spinner, StatCard, StatusAlert, SupportModal, TagInput, ThemeToggle, } from './ui-components';
18
18
  export type { CategoryInputProps, MarkdownProseProps, TagInputProps, } from './ui-components';
19
19
  export type { AgentType, FooterLink, LandingFooterProps, Message, PageHeaderProps, SidebarItemData, SupportMetadata, } from './ui-components';
20
20
  export * from './library';
package/index.js CHANGED
@@ -35,7 +35,7 @@ export { ActiveSubscriptions, BrowseRepositories, EntitySelector, EntitySelector
35
35
  // Export graph filters
36
36
  export { composeFilters, excludeRepositories, excludeSubgraphs, GraphFilters, hasSchemaExtension, onlyRepositories, onlyUserGraphs, } from './components/graph-filters';
37
37
  // Export app-components
38
- export { ApiKeyDisplay, ApiKeysCard, ApiKeyTable, BrandSpinner, categorizeError, CategoryInput, ChatHeader, ChatInputArea,
38
+ export { ApiKeyDisplay, BrandSpinner, categorizeError, CategoryInput, ChatHeader, ChatInputArea,
39
39
  // Chat components
40
40
  ChatMessage, ConfirmModal, CoreNavbar, CoreSidebar, CreateApiKeyModal, DeepResearchToggle, EmptyState, ErrorType, GeneralInformationCard, generateMessageId, getErrorMessage, LandingFooter, LoadingState, MarkdownProse, PageContainer, PageHeader, PasswordInformationCard, PasswordRequirements, SecureApiKeyField, SettingsCard, SettingsContainer, SettingsFormField, SettingsPageHeader, Spinner, StatCard, StatusAlert, SupportModal, TagInput, ThemeToggle, } from './ui-components';
41
41
  // Export library components, types, and helpers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robosystems/core",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Shared RoboSystems frontend core library (auth, contexts, task monitoring, UI components) for the RoboSystems Next.js apps",
5
5
  "license": "MIT",
6
6
  "private": false,
@@ -1,5 +1,3 @@
1
1
  export { ApiKeyDisplay } from './ApiKeyDisplay';
2
- export { ApiKeysCard } from './ApiKeysCard';
3
- export { ApiKeyTable } from './ApiKeyTable';
4
2
  export { CreateApiKeyModal } from './CreateApiKeyModal';
5
3
  export { SecureApiKeyField } from './SecureApiKeyField';
@@ -1,5 +1,3 @@
1
1
  export { ApiKeyDisplay } from './ApiKeyDisplay';
2
- export { ApiKeysCard } from './ApiKeysCard';
3
- export { ApiKeyTable } from './ApiKeyTable';
4
2
  export { CreateApiKeyModal } from './CreateApiKeyModal';
5
3
  export { SecureApiKeyField } from './SecureApiKeyField';
@@ -1,9 +0,0 @@
1
- import React from 'react';
2
- import type { ApiKey } from '../types';
3
- export interface ApiKeyTableProps {
4
- apiKeys: ApiKey[];
5
- onRevokeKey: (keyId: string) => void;
6
- theme?: any;
7
- formatDate?: (date: Date | string | null | undefined) => string;
8
- }
9
- export declare const ApiKeyTable: React.FC<ApiKeyTableProps>;
@@ -1,17 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Button, Dropdown, DropdownItem, Table, TableBody, TableCell, TableHead, TableHeadCell, TableRow, } from 'flowbite-react';
3
- import { HiCheckCircle, HiDotsVertical, HiExclamationCircle, HiKey, HiTrash, } from 'react-icons/hi';
4
- const defaultFormatDate = (date) => {
5
- if (!date)
6
- return 'Never';
7
- return new Date(date).toLocaleDateString('en-US', {
8
- year: 'numeric',
9
- month: 'short',
10
- day: 'numeric',
11
- });
12
- };
13
- export const ApiKeyTable = ({ apiKeys = [], onRevokeKey, theme, formatDate = defaultFormatDate, }) => {
14
- // Defensive check to ensure apiKeys is always an array
15
- const safeApiKeys = Array.isArray(apiKeys) ? apiKeys : [];
16
- return (_jsx("div", { className: "overflow-x-auto", children: _jsxs(Table, { className: "table-fixed", children: [_jsxs(TableHead, { children: [_jsx(TableHeadCell, { className: "w-[40%]", children: "Name" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Created" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Last Used" }), _jsx(TableHeadCell, { className: "w-[15%] whitespace-nowrap", children: "Status" }), _jsx(TableHeadCell, { className: "w-[15%] text-right", children: "Actions" })] }), _jsx(TableBody, { className: "divide-y", children: safeApiKeys.map((key) => (_jsxs(TableRow, { className: "dark:border-zinc-700", children: [_jsx(TableCell, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: _jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx(HiKey, { className: "h-5 w-5 shrink-0 text-zinc-600 dark:text-zinc-400" }), _jsx("span", { className: "truncate", title: key.name, children: key.name }), key.isSystem && (_jsx("span", { className: "bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-300 shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium", children: "System" })), key.graphId && (_jsx("span", { className: "max-w-40 shrink-0 truncate rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-300", title: `Scoped to graph ${key.graphId} — valid only for that graph and its subgraphs`, children: key.graphId }))] }) }), _jsx(TableCell, { className: "whitespace-nowrap text-zinc-600 dark:text-zinc-400", children: formatDate(key.createdAt) }), _jsx(TableCell, { className: "whitespace-nowrap text-zinc-600 dark:text-zinc-400", children: formatDate(key.lastUsedAt) }), _jsx(TableCell, { className: "whitespace-nowrap", children: key.isActive ? (_jsxs("div", { className: "inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800 dark:bg-green-800 dark:text-green-100", children: [_jsx(HiCheckCircle, { className: "mr-1 h-3 w-3" }), "Active"] })) : (_jsxs("div", { className: "inline-flex items-center rounded-full bg-red-100 px-2 py-0.5 text-xs font-medium text-red-800 dark:bg-red-800 dark:text-red-100", children: [_jsx(HiExclamationCircle, { className: "mr-1 h-3 w-3" }), "Revoked"] })) }), _jsx(TableCell, { className: "text-right", children: _jsx("div", { className: "inline-flex justify-end", children: _jsx(Dropdown, { theme: theme === null || theme === void 0 ? void 0 : theme.dropdown, label: "", dismissOnClick: true, renderTrigger: () => (_jsx(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, size: "sm", pill: true, className: "bg-zinc-100 text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-700 dark:text-zinc-100 dark:hover:bg-zinc-600", children: _jsx(HiDotsVertical, { className: "h-4 w-4" }) })), children: _jsx(DropdownItem, { onClick: () => onRevokeKey(key.id), disabled: !key.isActive || key.isSystem, icon: HiTrash, className: "hover:bg-zinc-100 dark:hover:bg-zinc-700", children: "Revoke" }) }) }) })] }, key.id))) })] }) }));
17
- };
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- export interface ApiKeysCardProps {
3
- theme?: any;
4
- className?: string;
5
- /**
6
- * When set, the card links MCP users to the app's Connect page (the
7
- * ready-made connector-URL flow) — e.g. "/connect" in robosystems-app.
8
- */
9
- connectHref?: string;
10
- }
11
- export declare const ApiKeysCard: React.FC<ApiKeysCardProps>;
@@ -1,112 +0,0 @@
1
- 'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import * as SDK from '@robosystems/client';
4
- import { Button } from 'flowbite-react';
5
- import { useContext, useEffect, useState } from 'react';
6
- import { HiKey, HiPlusCircle } from 'react-icons/hi';
7
- import { GraphContext } from '../../contexts';
8
- import { Spinner } from '../Spinner';
9
- import { SettingsCard } from '../forms/SettingsCard';
10
- import { StatusAlert } from '../forms/StatusAlert';
11
- import { ApiKeyTable } from './ApiKeyTable';
12
- import { CreateApiKeyModal } from './CreateApiKeyModal';
13
- export const ApiKeysCard = ({ theme, className = '', connectHref, }) => {
14
- var _a;
15
- // Read the graph context leniently: outside a GraphProvider the scope
16
- // selector simply doesn't render, preserving the card's old standalone
17
- // contract instead of throwing.
18
- const graphContext = useContext(GraphContext);
19
- const availableGraphs = (_a = graphContext === null || graphContext === void 0 ? void 0 : graphContext.state.graphs) !== null && _a !== void 0 ? _a : [];
20
- const [apiKeys, setApiKeys] = useState([]);
21
- const [isLoading, setIsLoading] = useState(false);
22
- const [isOpenCreateModal, setIsOpenCreateModal] = useState(false);
23
- const [error, setError] = useState(null);
24
- useEffect(() => {
25
- fetchApiKeys();
26
- }, []);
27
- const fetchApiKeys = async () => {
28
- var _a;
29
- setIsLoading(true);
30
- setError(null);
31
- try {
32
- // Always use SDK
33
- const response = await SDK.listUserApiKeys();
34
- const sdkKeys = ((_a = response.data) === null || _a === void 0 ? void 0 : _a.api_keys) || [];
35
- // Map SDK response to component format
36
- const keys = sdkKeys.map((key) => {
37
- var _a, _b;
38
- return ({
39
- id: key.id,
40
- name: key.name,
41
- graphId: key.graph_id,
42
- createdAt: key.created_at,
43
- lastUsedAt: key.last_used_at,
44
- expiresAt: key.expires_at,
45
- isActive: (_a = key.is_active) !== null && _a !== void 0 ? _a : true,
46
- isSystem: (_b = key.is_system) !== null && _b !== void 0 ? _b : false,
47
- });
48
- });
49
- setApiKeys(keys);
50
- }
51
- catch (_b) {
52
- setError('Failed to load API keys. Please try again.');
53
- }
54
- finally {
55
- setIsLoading(false);
56
- }
57
- };
58
- const handleCreateKey = async (data) => {
59
- var _a, _b;
60
- // Always use SDK to create key
61
- const response = await SDK.createUserApiKey({
62
- body: {
63
- name: data.name,
64
- description: `API key for ${data.name}`,
65
- graph_id: data.graphId,
66
- },
67
- });
68
- await fetchApiKeys(); // Refresh the list
69
- // Map SDK response to component format
70
- const responseData = response.data;
71
- const apiKeyInfo = responseData === null || responseData === void 0 ? void 0 : responseData.api_key;
72
- return {
73
- id: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.id) || '',
74
- name: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.name) || data.name,
75
- graphId: (_a = apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.graph_id) !== null && _a !== void 0 ? _a : data.graphId,
76
- key: (responseData === null || responseData === void 0 ? void 0 : responseData.key) || '', // The actual API key value
77
- createdAt: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.created_at) || new Date().toISOString(),
78
- lastUsedAt: (apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.last_used_at) || null,
79
- expiresAt: null,
80
- isActive: (_b = apiKeyInfo === null || apiKeyInfo === void 0 ? void 0 : apiKeyInfo.is_active) !== null && _b !== void 0 ? _b : true,
81
- isSystem: false,
82
- };
83
- };
84
- const handleRevokeKey = async (keyId) => {
85
- try {
86
- // Always use SDK to revoke key
87
- await SDK.revokeUserApiKey({
88
- path: { api_key_id: keyId },
89
- });
90
- await fetchApiKeys(); // Refresh the list
91
- }
92
- catch (_a) {
93
- setError('Failed to revoke API key. Please try again.');
94
- }
95
- };
96
- const formatDate = (date) => {
97
- if (!date)
98
- return 'Never';
99
- return new Date(date).toLocaleDateString('en-US', {
100
- year: 'numeric',
101
- month: 'short',
102
- day: 'numeric',
103
- });
104
- };
105
- return (_jsxs(SettingsCard, { title: "API Keys", description: "Manage your API keys for programmatic access", icon: HiKey, theme: theme === null || theme === void 0 ? void 0 : theme.card, className: className, children: [_jsx("div", { className: "mb-4 flex items-center justify-end", children: _jsxs(Button, { theme: theme === null || theme === void 0 ? void 0 : theme.button, color: "blue", onClick: () => setIsOpenCreateModal(true), children: [_jsx(HiPlusCircle, { className: "mr-2 h-5 w-5" }), "Create API Key"] }) }), error && (_jsx(StatusAlert, { type: "error", message: error, theme: theme === null || theme === void 0 ? void 0 : theme.alert })), isLoading ? (_jsx("div", { className: "flex justify-center py-4", children: _jsx(Spinner, { size: "lg" }) })) : !Array.isArray(apiKeys) || apiKeys.length === 0 ? (_jsxs("div", { className: "rounded-lg border border-zinc-200 bg-zinc-50 p-8 text-center dark:border-zinc-700 dark:bg-zinc-800", children: [_jsx(HiKey, { className: "mx-auto mb-3 h-12 w-12 text-zinc-400" }), _jsx("h3", { className: "mb-2 text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: "No API keys yet" }), _jsx("p", { className: "text-sm text-zinc-500 dark:text-zinc-400", children: "Create an API key to access your graphs programmatically" })] })) : (_jsx(ApiKeyTable, { apiKeys: apiKeys, onRevokeKey: handleRevokeKey, theme: theme, formatDate: formatDate })), connectHref && (_jsxs("p", { className: "mt-4 text-sm text-zinc-500 dark:text-zinc-400", children: ["Connecting Claude or another MCP client? Generate a ready-made connector URL from the", ' ', _jsx("a", { href: connectHref, className: "text-primary-600 dark:text-primary-400 font-medium hover:underline", children: "Connect page" }), "."] })), _jsx(CreateApiKeyModal, { isOpen: isOpenCreateModal, onClose: () => setIsOpenCreateModal(false), onCreateKey: handleCreateKey, theme: theme, graphs: availableGraphs.map((g) => {
106
- var _a;
107
- return ({
108
- graphId: g.graphId,
109
- graphName: (_a = g.graphName) !== null && _a !== void 0 ? _a : g.graphId,
110
- });
111
- }) })] }));
112
- };