@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 +23 -1
- package/components/console/graphAwareConfig.d.ts +2 -2
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/ui-components/api-keys/index.d.ts +0 -2
- package/ui-components/api-keys/index.js +0 -2
- package/ui-components/api-keys/ApiKeyTable.d.ts +0 -9
- package/ui-components/api-keys/ApiKeyTable.js +0 -17
- package/ui-components/api-keys/ApiKeysCard.d.ts +0 -11
- package/ui-components/api-keys/ApiKeysCard.js +0 -112
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
|
|
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
|
|
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,
|
|
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,
|
|
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,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
|
-
};
|