@robosystems/core 0.3.0
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/LICENSE +21 -0
- package/README.md +252 -0
- package/actions/entity-actions.d.ts +8 -0
- package/actions/entity-actions.js +16 -0
- package/actions/graph-actions.d.ts +3 -0
- package/actions/graph-actions.js +13 -0
- package/auth-components/AppSwitcher.d.ts +6 -0
- package/auth-components/AppSwitcher.js +28 -0
- package/auth-components/AuthGuard.d.ts +7 -0
- package/auth-components/AuthGuard.js +31 -0
- package/auth-components/AuthProvider.d.ts +9 -0
- package/auth-components/AuthProvider.js +565 -0
- package/auth-components/SessionWarningDialog.d.ts +1 -0
- package/auth-components/SessionWarningDialog.js +51 -0
- package/auth-components/SignInForm.d.ts +20 -0
- package/auth-components/SignInForm.js +139 -0
- package/auth-components/SignUpForm.d.ts +15 -0
- package/auth-components/SignUpForm.js +167 -0
- package/auth-components/TurnstileWidget.d.ts +99 -0
- package/auth-components/TurnstileWidget.js +224 -0
- package/auth-components/index.d.ts +6 -0
- package/auth-components/index.js +6 -0
- package/auth-core/cleanup.d.ts +44 -0
- package/auth-core/cleanup.js +157 -0
- package/auth-core/client.d.ts +121 -0
- package/auth-core/client.js +767 -0
- package/auth-core/config.d.ts +19 -0
- package/auth-core/config.js +56 -0
- package/auth-core/hooks.d.ts +19 -0
- package/auth-core/hooks.js +63 -0
- package/auth-core/index.d.ts +6 -0
- package/auth-core/index.js +6 -0
- package/auth-core/sso.d.ts +42 -0
- package/auth-core/sso.js +281 -0
- package/auth-core/token-storage.d.ts +58 -0
- package/auth-core/token-storage.js +177 -0
- package/auth-core/types.d.ts +145 -0
- package/auth-core/types.js +1 -0
- package/components/EntitySelector.d.ts +44 -0
- package/components/EntitySelector.js +64 -0
- package/components/EntitySelectorCore.d.ts +45 -0
- package/components/EntitySelectorCore.js +67 -0
- package/components/GraphSelectorCore.d.ts +21 -0
- package/components/GraphSelectorCore.js +39 -0
- package/components/PageLayout.d.ts +8 -0
- package/components/PageLayout.js +5 -0
- package/components/RepositoryGuard.d.ts +68 -0
- package/components/RepositoryGuard.js +91 -0
- package/components/console/ConsoleContent.d.ts +4 -0
- package/components/console/ConsoleContent.js +612 -0
- package/components/console/ConsoleMarkdown.d.ts +11 -0
- package/components/console/ConsoleMarkdown.js +49 -0
- package/components/console/ProgressiveText.d.ts +6 -0
- package/components/console/ProgressiveText.js +21 -0
- package/components/console/graphAwareConfig.d.ts +70 -0
- package/components/console/graphAwareConfig.js +411 -0
- package/components/console/index.d.ts +6 -0
- package/components/console/index.js +4 -0
- package/components/console/types.d.ts +84 -0
- package/components/console/types.js +1 -0
- package/components/graph-filters.d.ts +75 -0
- package/components/graph-filters.js +111 -0
- package/components/index.d.ts +10 -0
- package/components/index.js +9 -0
- package/components/repositories/ActiveSubscriptions.d.ts +26 -0
- package/components/repositories/ActiveSubscriptions.js +103 -0
- package/components/repositories/BrowseRepositories.d.ts +11 -0
- package/components/repositories/BrowseRepositories.js +184 -0
- package/components/repositories/index.d.ts +2 -0
- package/components/repositories/index.js +2 -0
- package/components/search/SearchContent.d.ts +4 -0
- package/components/search/SearchContent.js +168 -0
- package/components/search/index.d.ts +2 -0
- package/components/search/index.js +1 -0
- package/components/search/types.d.ts +15 -0
- package/components/search/types.js +1 -0
- package/contexts/entity-context.d.ts +28 -0
- package/contexts/entity-context.js +89 -0
- package/contexts/graph-context.d.ts +27 -0
- package/contexts/graph-context.js +128 -0
- package/contexts/index.d.ts +5 -0
- package/contexts/index.js +5 -0
- package/contexts/org-context.d.ts +25 -0
- package/contexts/org-context.js +64 -0
- package/contexts/service-offerings-context.d.ts +45 -0
- package/contexts/service-offerings-context.js +98 -0
- package/contexts/sidebar-context.d.ts +18 -0
- package/contexts/sidebar-context.js +44 -0
- package/hooks/index.d.ts +6 -0
- package/hooks/index.js +6 -0
- package/hooks/use-api-error.d.ts +3 -0
- package/hooks/use-api-error.js +24 -0
- package/hooks/use-media-query.d.ts +1 -0
- package/hooks/use-media-query.js +21 -0
- package/hooks/use-streaming-query.d.ts +22 -0
- package/hooks/use-streaming-query.js +142 -0
- package/hooks/use-toast.d.ts +37 -0
- package/hooks/use-toast.js +117 -0
- package/hooks/use-user-limits.d.ts +12 -0
- package/hooks/use-user-limits.js +73 -0
- package/hooks/use-user.d.ts +11 -0
- package/hooks/use-user.js +39 -0
- package/index.d.ts +29 -0
- package/index.js +77 -0
- package/lib/entity-cookie.d.ts +10 -0
- package/lib/entity-cookie.js +38 -0
- package/lib/graph-cookie.d.ts +11 -0
- package/lib/graph-cookie.js +62 -0
- package/lib/graph-tiers.d.ts +41 -0
- package/lib/graph-tiers.js +107 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.js +3 -0
- package/lib/sidebar-cookie.d.ts +11 -0
- package/lib/sidebar-cookie.js +42 -0
- package/library/colors.d.ts +4 -0
- package/library/colors.js +41 -0
- package/library/components/ClassificationPicker.d.ts +8 -0
- package/library/components/ClassificationPicker.js +55 -0
- package/library/components/ElementBrowser.d.ts +11 -0
- package/library/components/ElementBrowser.js +106 -0
- package/library/components/ElementDetail.d.ts +7 -0
- package/library/components/ElementDetail.js +97 -0
- package/library/components/LibraryHierarchy.d.ts +13 -0
- package/library/components/LibraryHierarchy.js +380 -0
- package/library/components/TaxonomySidebar.d.ts +6 -0
- package/library/components/TaxonomySidebar.js +45 -0
- package/library/index.d.ts +8 -0
- package/library/index.js +7 -0
- package/package.json +37 -0
- package/research/CoverageCard.d.ts +12 -0
- package/research/CoverageCard.js +15 -0
- package/research/CoverageGrid.d.ts +6 -0
- package/research/CoverageGrid.js +9 -0
- package/research/CoverageHistory.d.ts +5 -0
- package/research/CoverageHistory.js +7 -0
- package/research/ResearchArticle.d.ts +15 -0
- package/research/ResearchArticle.js +26 -0
- package/research/catalog.d.ts +14 -0
- package/research/catalog.js +42 -0
- package/research/index.d.ts +6 -0
- package/research/index.js +9 -0
- package/research/types.d.ts +38 -0
- package/research/types.js +4 -0
- package/task-monitoring/hooks.d.ts +26 -0
- package/task-monitoring/hooks.js +126 -0
- package/task-monitoring/index.d.ts +5 -0
- package/task-monitoring/index.js +7 -0
- package/task-monitoring/operationErrors.d.ts +82 -0
- package/task-monitoring/operationErrors.js +251 -0
- package/task-monitoring/operationHooks.d.ts +117 -0
- package/task-monitoring/operationHooks.js +536 -0
- package/task-monitoring/operationMonitor.d.ts +78 -0
- package/task-monitoring/operationMonitor.js +200 -0
- package/task-monitoring/operationTypes.d.ts +236 -0
- package/task-monitoring/operationTypes.js +60 -0
- package/task-monitoring/taskMonitor.d.ts +14 -0
- package/task-monitoring/taskMonitor.js +132 -0
- package/task-monitoring/types.d.ts +31 -0
- package/task-monitoring/types.js +1 -0
- package/theme/flowbite-theme.d.ts +758 -0
- package/theme/flowbite-theme.js +765 -0
- package/theme/index.d.ts +1 -0
- package/theme/index.js +1 -0
- package/types/entity.d.ts +19 -0
- package/types/index.d.ts +2 -0
- package/types/index.js +1 -0
- package/types/user.d.ts +6 -0
- package/ui-components/ConfirmModal.d.ts +41 -0
- package/ui-components/ConfirmModal.js +19 -0
- package/ui-components/EmptyState.d.ts +22 -0
- package/ui-components/EmptyState.js +10 -0
- package/ui-components/LoadingState.d.ts +27 -0
- package/ui-components/LoadingState.js +12 -0
- package/ui-components/Logo.d.ts +28 -0
- package/ui-components/Logo.js +95 -0
- package/ui-components/Spinner.d.ts +37 -0
- package/ui-components/Spinner.js +48 -0
- package/ui-components/StatCard.d.ts +21 -0
- package/ui-components/StatCard.js +10 -0
- package/ui-components/api-keys/ApiKeyDisplay.d.ts +13 -0
- package/ui-components/api-keys/ApiKeyDisplay.js +7 -0
- package/ui-components/api-keys/ApiKeyTable.d.ts +9 -0
- package/ui-components/api-keys/ApiKeyTable.js +17 -0
- package/ui-components/api-keys/ApiKeysCard.d.ts +6 -0
- package/ui-components/api-keys/ApiKeysCard.js +97 -0
- package/ui-components/api-keys/CreateApiKeyModal.d.ts +11 -0
- package/ui-components/api-keys/CreateApiKeyModal.js +47 -0
- package/ui-components/api-keys/SecureApiKeyField.d.ts +13 -0
- package/ui-components/api-keys/SecureApiKeyField.js +64 -0
- package/ui-components/api-keys/index.d.ts +5 -0
- package/ui-components/api-keys/index.js +5 -0
- package/ui-components/chat/ChatHeader.d.ts +12 -0
- package/ui-components/chat/ChatHeader.js +5 -0
- package/ui-components/chat/ChatInputArea.d.ts +16 -0
- package/ui-components/chat/ChatInputArea.js +9 -0
- package/ui-components/chat/ChatMessage.d.ts +7 -0
- package/ui-components/chat/ChatMessage.js +24 -0
- package/ui-components/chat/DeepResearchToggle.d.ts +8 -0
- package/ui-components/chat/DeepResearchToggle.js +13 -0
- package/ui-components/chat/index.d.ts +9 -0
- package/ui-components/chat/index.js +8 -0
- package/ui-components/chat/types.d.ts +23 -0
- package/ui-components/chat/types.js +51 -0
- package/ui-components/forms/PasswordRequirements.d.ts +10 -0
- package/ui-components/forms/PasswordRequirements.js +15 -0
- package/ui-components/forms/SettingsCard.d.ts +11 -0
- package/ui-components/forms/SettingsCard.js +5 -0
- package/ui-components/forms/SettingsFormField.d.ts +18 -0
- package/ui-components/forms/SettingsFormField.js +17 -0
- package/ui-components/forms/StatusAlert.d.ts +8 -0
- package/ui-components/forms/StatusAlert.js +7 -0
- package/ui-components/forms/index.d.ts +4 -0
- package/ui-components/forms/index.js +4 -0
- package/ui-components/index.d.ts +13 -0
- package/ui-components/index.js +18 -0
- package/ui-components/layout/CoreNavbar.d.ts +14 -0
- package/ui-components/layout/CoreNavbar.js +53 -0
- package/ui-components/layout/CoreSidebar.d.ts +28 -0
- package/ui-components/layout/CoreSidebar.js +62 -0
- package/ui-components/layout/LandingFooter.d.ts +28 -0
- package/ui-components/layout/LandingFooter.js +34 -0
- package/ui-components/layout/PageContainer.d.ts +8 -0
- package/ui-components/layout/PageContainer.js +17 -0
- package/ui-components/layout/PageHeader.d.ts +23 -0
- package/ui-components/layout/PageHeader.js +12 -0
- package/ui-components/layout/SettingsContainer.d.ts +10 -0
- package/ui-components/layout/SettingsContainer.js +9 -0
- package/ui-components/layout/ThemeToggle.d.ts +4 -0
- package/ui-components/layout/ThemeToggle.js +6 -0
- package/ui-components/layout/index.d.ts +7 -0
- package/ui-components/layout/index.js +7 -0
- package/ui-components/settings/GeneralInformationCard.d.ts +12 -0
- package/ui-components/settings/GeneralInformationCard.js +72 -0
- package/ui-components/settings/PasswordInformationCard.d.ts +10 -0
- package/ui-components/settings/PasswordInformationCard.js +167 -0
- package/ui-components/settings/SettingsPageHeader.d.ts +7 -0
- package/ui-components/settings/SettingsPageHeader.js +6 -0
- package/ui-components/settings/index.d.ts +3 -0
- package/ui-components/settings/index.js +3 -0
- package/ui-components/support/SupportModal.d.ts +15 -0
- package/ui-components/support/SupportModal.js +74 -0
- package/ui-components/support/index.d.ts +2 -0
- package/ui-components/support/index.js +1 -0
- package/ui-components/types/index.d.ts +39 -0
- package/ui-components/types/index.js +1 -0
- package/utils/index.d.ts +31 -0
- package/utils/index.js +38 -0
- package/utils/turnstile-config.d.ts +11 -0
- package/utils/turnstile-config.js +19 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AppConfig, AppName } from './types';
|
|
2
|
+
export declare const CURRENT_APP: AppName;
|
|
3
|
+
/**
|
|
4
|
+
* Each app's fixed brand hex. Cross-app safe: unlike the Tailwind `primary`
|
|
5
|
+
* tokens (which only ever resolve to the *currently-running* app's palette),
|
|
6
|
+
* these literals correctly represent any app from inside any other app — used
|
|
7
|
+
* by the colored AnimatedLogo and the AppSwitcher so each product's mark shows
|
|
8
|
+
* in its own brand color.
|
|
9
|
+
*/
|
|
10
|
+
export declare const BRAND_COLORS: Record<AppName, string>;
|
|
11
|
+
/**
|
|
12
|
+
* Per-app brand gradient (CSS) for the LogoBadge chip — a white mark on a
|
|
13
|
+
* brand-gradient rounded square. Cross-app safe literals (NOT tokens), so each
|
|
14
|
+
* product's chip renders its own gradient from inside any app (e.g. the
|
|
15
|
+
* AppSwitcher). Mirrors each app's landing hero gradient.
|
|
16
|
+
*/
|
|
17
|
+
export declare const BRAND_GRADIENTS: Record<AppName, string>;
|
|
18
|
+
export declare const APP_CONFIGS: Record<string, AppConfig>;
|
|
19
|
+
export declare const getAppConfig: (appName: string) => AppConfig;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const CURRENT_APP = process.env.NEXT_PUBLIC_APP_NAME || 'robosystems';
|
|
2
|
+
/**
|
|
3
|
+
* Each app's fixed brand hex. Cross-app safe: unlike the Tailwind `primary`
|
|
4
|
+
* tokens (which only ever resolve to the *currently-running* app's palette),
|
|
5
|
+
* these literals correctly represent any app from inside any other app — used
|
|
6
|
+
* by the colored AnimatedLogo and the AppSwitcher so each product's mark shows
|
|
7
|
+
* in its own brand color.
|
|
8
|
+
*/
|
|
9
|
+
export const BRAND_COLORS = {
|
|
10
|
+
robosystems: '#3B7AF5', // blue
|
|
11
|
+
roboledger: '#8B5CF6', // violet
|
|
12
|
+
roboinvestor: '#10B981', // emerald
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Per-app brand gradient (CSS) for the LogoBadge chip — a white mark on a
|
|
16
|
+
* brand-gradient rounded square. Cross-app safe literals (NOT tokens), so each
|
|
17
|
+
* product's chip renders its own gradient from inside any app (e.g. the
|
|
18
|
+
* AppSwitcher). Mirrors each app's landing hero gradient.
|
|
19
|
+
*/
|
|
20
|
+
export const BRAND_GRADIENTS = {
|
|
21
|
+
robosystems: 'linear-gradient(135deg, #06B6D4, #3B7AF5 55%, #6366F1)', // cyan → blue → indigo
|
|
22
|
+
roboledger: 'linear-gradient(135deg, #8B5CF6, #A855F7 55%, #D946EF)', // violet → purple → fuchsia
|
|
23
|
+
roboinvestor: 'linear-gradient(135deg, #10B981, #14B8A6 55%, #06B6D4)', // emerald → teal → cyan
|
|
24
|
+
};
|
|
25
|
+
export const APP_CONFIGS = {
|
|
26
|
+
roboinvestor: {
|
|
27
|
+
name: 'roboinvestor',
|
|
28
|
+
displayName: 'RoboInvestor',
|
|
29
|
+
url: process.env.NEXT_PUBLIC_ROBOINVESTOR_APP_URL || 'https://roboinvestor.ai',
|
|
30
|
+
description: 'Portfolio Management Agent',
|
|
31
|
+
initials: 'RI',
|
|
32
|
+
colorClass: 'bg-emerald-600 dark:bg-emerald-500',
|
|
33
|
+
brandColor: BRAND_COLORS.roboinvestor,
|
|
34
|
+
},
|
|
35
|
+
roboledger: {
|
|
36
|
+
name: 'roboledger',
|
|
37
|
+
displayName: 'RoboLedger',
|
|
38
|
+
url: process.env.NEXT_PUBLIC_ROBOLEDGER_APP_URL || 'https://roboledger.ai',
|
|
39
|
+
description: 'Accounting & Reporting Agent',
|
|
40
|
+
initials: 'RL',
|
|
41
|
+
colorClass: 'bg-violet-600 dark:bg-violet-500',
|
|
42
|
+
brandColor: BRAND_COLORS.roboledger,
|
|
43
|
+
},
|
|
44
|
+
robosystems: {
|
|
45
|
+
name: 'robosystems',
|
|
46
|
+
displayName: 'RoboSystems',
|
|
47
|
+
url: process.env.NEXT_PUBLIC_ROBOSYSTEMS_APP_URL || 'https://robosystems.ai',
|
|
48
|
+
description: 'Financial Intelligence Platform',
|
|
49
|
+
initials: 'RS',
|
|
50
|
+
colorClass: 'bg-blue-600 dark:bg-blue-500',
|
|
51
|
+
brandColor: BRAND_COLORS.robosystems,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
export const getAppConfig = (appName) => {
|
|
55
|
+
return APP_CONFIGS[appName] || APP_CONFIGS.robosystems;
|
|
56
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AuthUser } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates user-specific operations that can be used with auth context.
|
|
4
|
+
* This is a factory function that takes the auth client instance.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useUserHook(authClient: any, setUser: (user: AuthUser | null) => void): {
|
|
7
|
+
refreshUser: () => Promise<AuthUser | null>;
|
|
8
|
+
updateProfile: (data: {
|
|
9
|
+
name?: string;
|
|
10
|
+
email?: string;
|
|
11
|
+
}) => Promise<AuthUser>;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Hook to handle token expiry errors globally
|
|
15
|
+
* Automatically redirects to login on token expiry
|
|
16
|
+
*/
|
|
17
|
+
export declare function useTokenExpiryHandler(logout: (reason?: string) => Promise<void>): {
|
|
18
|
+
handleTokenExpiry: (error: unknown) => Promise<boolean>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useCallback, useEffect } from 'react';
|
|
3
|
+
import { TokenExpiredError } from './client';
|
|
4
|
+
/**
|
|
5
|
+
* Creates user-specific operations that can be used with auth context.
|
|
6
|
+
* This is a factory function that takes the auth client instance.
|
|
7
|
+
*/
|
|
8
|
+
export function useUserHook(authClient, setUser) {
|
|
9
|
+
const refreshUser = useCallback(async () => {
|
|
10
|
+
try {
|
|
11
|
+
const user = await authClient.getCurrentUser();
|
|
12
|
+
setUser(user);
|
|
13
|
+
return user;
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
setUser(null);
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}, [authClient, setUser]);
|
|
20
|
+
const updateProfile = useCallback(async (data) => {
|
|
21
|
+
// This would use SDK updateUser method
|
|
22
|
+
// For now, refresh user data after update
|
|
23
|
+
const user = await refreshUser();
|
|
24
|
+
if (!user) {
|
|
25
|
+
throw new Error('User not found after update');
|
|
26
|
+
}
|
|
27
|
+
return user;
|
|
28
|
+
}, [refreshUser]);
|
|
29
|
+
return {
|
|
30
|
+
refreshUser,
|
|
31
|
+
updateProfile,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Hook to handle token expiry errors globally
|
|
36
|
+
* Automatically redirects to login on token expiry
|
|
37
|
+
*/
|
|
38
|
+
export function useTokenExpiryHandler(logout) {
|
|
39
|
+
const handleTokenExpiry = useCallback(async (error) => {
|
|
40
|
+
if (error instanceof TokenExpiredError) {
|
|
41
|
+
// Token expired, redirect to login
|
|
42
|
+
await logout('session_expired');
|
|
43
|
+
return true; // Error was handled
|
|
44
|
+
}
|
|
45
|
+
return false; // Error was not handled
|
|
46
|
+
}, [logout]);
|
|
47
|
+
// Set up global error handler for unhandled promise rejections
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const handleUnhandledRejection = (event) => {
|
|
50
|
+
if (event.reason instanceof TokenExpiredError) {
|
|
51
|
+
event.preventDefault(); // Prevent default error logging
|
|
52
|
+
handleTokenExpiry(event.reason);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
if (typeof window !== 'undefined') {
|
|
56
|
+
window.addEventListener('unhandledrejection', handleUnhandledRejection);
|
|
57
|
+
return () => {
|
|
58
|
+
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}, [handleTokenExpiry]);
|
|
62
|
+
return { handleTokenExpiry };
|
|
63
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AppConfig, AuthUser } from './types';
|
|
2
|
+
export declare class SSOManager {
|
|
3
|
+
private authClient;
|
|
4
|
+
constructor(apiUrl: string);
|
|
5
|
+
/**
|
|
6
|
+
* Check if user is already authenticated via SSO
|
|
7
|
+
*/
|
|
8
|
+
checkSSOAuthentication(): Promise<AuthUser | null>;
|
|
9
|
+
/**
|
|
10
|
+
* Generate SSO token for cross-app authentication
|
|
11
|
+
*/
|
|
12
|
+
generateSSOToken(): Promise<import("./types").SSOTokenResponse>;
|
|
13
|
+
/**
|
|
14
|
+
* Get SSO redirect URL using secure POST-based flow
|
|
15
|
+
*/
|
|
16
|
+
getSSORedirectUrl(targetApp: string, returnUrl?: string): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Handle SSO login using secure POST-based flow
|
|
19
|
+
*/
|
|
20
|
+
handleSSOLogin(): Promise<AuthUser | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Get available apps for the authenticated user
|
|
23
|
+
*/
|
|
24
|
+
getAvailableApps(): AppConfig[];
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize error objects for safe logging in development
|
|
27
|
+
*/
|
|
28
|
+
private sanitizeErrorForLogging;
|
|
29
|
+
/**
|
|
30
|
+
* Navigate to another app with secure SSO
|
|
31
|
+
*/
|
|
32
|
+
navigateToApp(targetApp: string, returnUrl?: string): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Hook for SSO functionality
|
|
36
|
+
*/
|
|
37
|
+
export declare function useSSO(apiUrl: string): {
|
|
38
|
+
checkSSOAuthentication: () => Promise<AuthUser>;
|
|
39
|
+
handleSSOLogin: () => Promise<AuthUser>;
|
|
40
|
+
navigateToApp: (targetApp: string, returnUrl?: string) => Promise<void>;
|
|
41
|
+
getAvailableApps: () => AppConfig[];
|
|
42
|
+
};
|
package/auth-core/sso.js
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { RoboSystemsAuthClient } from './client';
|
|
3
|
+
import { APP_CONFIGS } from './config';
|
|
4
|
+
// Configuration constants
|
|
5
|
+
const AUTH_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
6
|
+
const NAVIGATION_DELAY_MS = 100; // 100ms delay for navigation
|
|
7
|
+
// Debug logging helper
|
|
8
|
+
const debugLog = (message, error) => {
|
|
9
|
+
if (process.env.NODE_ENV === 'development') {
|
|
10
|
+
console.debug(`[SSO] ${message}`, error);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
export class SSOManager {
|
|
14
|
+
constructor(apiUrl) {
|
|
15
|
+
this.authClient = new RoboSystemsAuthClient(apiUrl);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Check if user is already authenticated via SSO
|
|
19
|
+
*/
|
|
20
|
+
async checkSSOAuthentication() {
|
|
21
|
+
try {
|
|
22
|
+
return await this.authClient.checkAuthentication();
|
|
23
|
+
}
|
|
24
|
+
catch (_a) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Generate SSO token for cross-app authentication
|
|
30
|
+
*/
|
|
31
|
+
async generateSSOToken() {
|
|
32
|
+
return await this.authClient.generateSSOToken();
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get SSO redirect URL using secure POST-based flow
|
|
36
|
+
*/
|
|
37
|
+
async getSSORedirectUrl(targetApp, returnUrl) {
|
|
38
|
+
const ssoData = await this.generateSSOToken();
|
|
39
|
+
const appConfig = APP_CONFIGS[targetApp];
|
|
40
|
+
if (!appConfig) {
|
|
41
|
+
throw new Error(`Unknown app: ${targetApp}`);
|
|
42
|
+
}
|
|
43
|
+
// Step 1: Exchange SSO token for secure session ID
|
|
44
|
+
const exchangeResult = await this.authClient.ssoExchange(ssoData.token, targetApp);
|
|
45
|
+
// Step 2: Create URL that will trigger the POST-based flow
|
|
46
|
+
const url = new URL('/login', appConfig.url);
|
|
47
|
+
// Pass session ID via URL parameters for cross-domain compatibility
|
|
48
|
+
url.searchParams.set('session_id', exchangeResult.session_id);
|
|
49
|
+
if (returnUrl) {
|
|
50
|
+
url.searchParams.set('returnUrl', returnUrl);
|
|
51
|
+
}
|
|
52
|
+
// Store only non-sensitive metadata in sessionStorage as backup for same-domain cases
|
|
53
|
+
// Note: We avoid storing the session_id in sessionStorage for security
|
|
54
|
+
try {
|
|
55
|
+
sessionStorage.setItem('sso_target_app', targetApp);
|
|
56
|
+
if (returnUrl) {
|
|
57
|
+
sessionStorage.setItem('sso_return_url', returnUrl);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// Storage error - continue without backup storage
|
|
62
|
+
debugLog('sessionStorage write failed during SSO URL generation', error);
|
|
63
|
+
}
|
|
64
|
+
return url.toString();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handle SSO login using secure POST-based flow
|
|
68
|
+
*/
|
|
69
|
+
async handleSSOLogin() {
|
|
70
|
+
// Check for session ID from URL parameters (cross-domain compatible)
|
|
71
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
72
|
+
const sessionId = urlParams.get('session_id');
|
|
73
|
+
const returnUrl = urlParams.get('returnUrl');
|
|
74
|
+
// Check sessionStorage for return URL fallback (same-domain case)
|
|
75
|
+
// Note: We don't store session_id in sessionStorage for security
|
|
76
|
+
let sessionStorageReturn = null;
|
|
77
|
+
try {
|
|
78
|
+
sessionStorageReturn = sessionStorage.getItem('sso_return_url');
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// Storage access error - continue without fallback
|
|
82
|
+
debugLog('sessionStorage read failed during SSO login', error);
|
|
83
|
+
}
|
|
84
|
+
const finalSessionId = sessionId; // Only use URL parameter for session ID
|
|
85
|
+
const finalReturnUrl = returnUrl || sessionStorageReturn;
|
|
86
|
+
if (!finalSessionId) {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
// Complete SSO authentication using session ID
|
|
91
|
+
const authResponse = await this.authClient.ssoComplete(finalSessionId);
|
|
92
|
+
// Clean up both URL parameters and session storage
|
|
93
|
+
if (sessionId) {
|
|
94
|
+
// Clean up URL parameters
|
|
95
|
+
const newUrl = new URL(window.location.href);
|
|
96
|
+
newUrl.searchParams.delete('session_id');
|
|
97
|
+
newUrl.searchParams.delete('returnUrl');
|
|
98
|
+
window.history.replaceState({}, '', newUrl.toString());
|
|
99
|
+
}
|
|
100
|
+
// Clean up session storage
|
|
101
|
+
try {
|
|
102
|
+
sessionStorage.removeItem('sso_target_app');
|
|
103
|
+
sessionStorage.removeItem('sso_return_url');
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// Storage access error - continue silently
|
|
107
|
+
debugLog('sessionStorage cleanup failed during SSO success', error);
|
|
108
|
+
}
|
|
109
|
+
// Handle return URL if provided
|
|
110
|
+
if (finalReturnUrl && finalReturnUrl !== window.location.pathname) {
|
|
111
|
+
// Delay navigation to allow authentication to complete
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
window.location.href = finalReturnUrl;
|
|
114
|
+
}, NAVIGATION_DELAY_MS);
|
|
115
|
+
}
|
|
116
|
+
return authResponse.user;
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Clean up both URL parameters and session storage on error
|
|
120
|
+
if (sessionId) {
|
|
121
|
+
const newUrl = new URL(window.location.href);
|
|
122
|
+
newUrl.searchParams.delete('session_id');
|
|
123
|
+
newUrl.searchParams.delete('returnUrl');
|
|
124
|
+
window.history.replaceState({}, '', newUrl.toString());
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
sessionStorage.removeItem('sso_target_app');
|
|
128
|
+
sessionStorage.removeItem('sso_return_url');
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
// Storage access error - continue silently
|
|
132
|
+
debugLog('sessionStorage cleanup failed during SSO error', error);
|
|
133
|
+
}
|
|
134
|
+
// Log errors for monitoring in production (sanitized)
|
|
135
|
+
if (process.env.NODE_ENV === 'development') {
|
|
136
|
+
console.error('SSO login failed:', this.sanitizeErrorForLogging(error));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Production error monitoring - log sanitized errors for observability
|
|
140
|
+
console.error('SSO authentication failed:', {
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
error: this.sanitizeErrorForLogging(error),
|
|
143
|
+
user_agent: (navigator === null || navigator === void 0 ? void 0 : navigator.userAgent) || 'unknown',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get available apps for the authenticated user
|
|
151
|
+
*/
|
|
152
|
+
getAvailableApps() {
|
|
153
|
+
return Object.values(APP_CONFIGS);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Sanitize error objects for safe logging in development
|
|
157
|
+
*/
|
|
158
|
+
sanitizeErrorForLogging(error) {
|
|
159
|
+
var _a;
|
|
160
|
+
if (error instanceof Error) {
|
|
161
|
+
return {
|
|
162
|
+
name: error.name,
|
|
163
|
+
message: error.message,
|
|
164
|
+
// Exclude potentially sensitive stack traces in some cases
|
|
165
|
+
stack: ((_a = error.stack) === null || _a === void 0 ? void 0 : _a.split('\n').slice(0, 3).join('\n')) + '...',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
if (typeof error === 'object' && error !== null) {
|
|
169
|
+
// Remove any fields that might contain sensitive data
|
|
170
|
+
const sanitized = Object.assign({}, error);
|
|
171
|
+
delete sanitized.token;
|
|
172
|
+
delete sanitized.session_id;
|
|
173
|
+
delete sanitized.password;
|
|
174
|
+
delete sanitized.key;
|
|
175
|
+
return sanitized;
|
|
176
|
+
}
|
|
177
|
+
return String(error);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Navigate to another app with secure SSO
|
|
181
|
+
*/
|
|
182
|
+
async navigateToApp(targetApp, returnUrl) {
|
|
183
|
+
const appConfig = APP_CONFIGS[targetApp];
|
|
184
|
+
if (!appConfig) {
|
|
185
|
+
throw new Error(`Unknown app: ${targetApp}`);
|
|
186
|
+
}
|
|
187
|
+
// Check local auth cache first to avoid unnecessary SSO token generation
|
|
188
|
+
if (typeof window !== 'undefined') {
|
|
189
|
+
let cachedUser = null;
|
|
190
|
+
try {
|
|
191
|
+
cachedUser = sessionStorage.getItem('auth_user_cache');
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
// Storage access error - proceed with SSO flow
|
|
195
|
+
debugLog('sessionStorage read failed during navigation cache check', error);
|
|
196
|
+
}
|
|
197
|
+
if (cachedUser) {
|
|
198
|
+
try {
|
|
199
|
+
const userData = JSON.parse(cachedUser);
|
|
200
|
+
const cacheAge = Date.now() - (userData.cached_at || 0);
|
|
201
|
+
// REMOVED: Cache bypass logic that was skipping SSO flow
|
|
202
|
+
// Always use proper SSO flow for cross-app authentication
|
|
203
|
+
debugLog('Proceeding with full SSO flow for secure cross-app auth');
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
// Invalid cache, proceed with SSO flow
|
|
207
|
+
debugLog('Invalid cache found, proceeding with SSO flow', error);
|
|
208
|
+
try {
|
|
209
|
+
sessionStorage.removeItem('auth_user_cache');
|
|
210
|
+
}
|
|
211
|
+
catch (removeError) {
|
|
212
|
+
// Storage error - continue silently
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
try {
|
|
218
|
+
// Fallback to full SSO flow for stale/missing cache
|
|
219
|
+
const ssoData = await this.generateSSOToken();
|
|
220
|
+
// Step 2: Exchange token for secure session ID
|
|
221
|
+
const exchangeResult = await this.authClient.ssoExchange(ssoData.token, targetApp);
|
|
222
|
+
// Step 3: Navigate to target app with secure session handoff
|
|
223
|
+
const targetUrl = new URL('/login', appConfig.url);
|
|
224
|
+
// Pass session ID via URL parameters for cross-domain compatibility
|
|
225
|
+
targetUrl.searchParams.set('session_id', exchangeResult.session_id);
|
|
226
|
+
if (returnUrl) {
|
|
227
|
+
targetUrl.searchParams.set('returnUrl', returnUrl);
|
|
228
|
+
}
|
|
229
|
+
// Store only non-sensitive metadata in sessionStorage as backup for same-domain cases
|
|
230
|
+
// Note: We avoid storing the session_id in sessionStorage for security
|
|
231
|
+
if (typeof window !== 'undefined') {
|
|
232
|
+
try {
|
|
233
|
+
sessionStorage.setItem('sso_target_app', targetApp);
|
|
234
|
+
if (returnUrl) {
|
|
235
|
+
sessionStorage.setItem('sso_return_url', returnUrl);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
// Storage error - continue without backup storage
|
|
240
|
+
debugLog('sessionStorage write failed during SSO navigation', error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Navigate to target app
|
|
244
|
+
window.location.href = targetUrl.toString();
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (process.env.NODE_ENV === 'development') {
|
|
248
|
+
console.error('Failed to navigate to app:', error);
|
|
249
|
+
}
|
|
250
|
+
debugLog('SSO navigation failed, attempting fallback', error);
|
|
251
|
+
// Fallback to direct navigation
|
|
252
|
+
try {
|
|
253
|
+
const targetUrl = new URL(appConfig.url);
|
|
254
|
+
if (returnUrl) {
|
|
255
|
+
targetUrl.searchParams.set('returnUrl', returnUrl);
|
|
256
|
+
}
|
|
257
|
+
window.location.href = targetUrl.toString();
|
|
258
|
+
}
|
|
259
|
+
catch (locationError) {
|
|
260
|
+
if (process.env.NODE_ENV === 'development') {
|
|
261
|
+
console.error('Fallback navigation failed:', locationError);
|
|
262
|
+
}
|
|
263
|
+
debugLog('All navigation methods failed', locationError);
|
|
264
|
+
// Final fallback using window.open
|
|
265
|
+
window.open(appConfig.url, '_self');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Hook for SSO functionality
|
|
272
|
+
*/
|
|
273
|
+
export function useSSO(apiUrl) {
|
|
274
|
+
const ssoManager = useMemo(() => new SSOManager(apiUrl), [apiUrl]);
|
|
275
|
+
return useMemo(() => ({
|
|
276
|
+
checkSSOAuthentication: () => ssoManager.checkSSOAuthentication(),
|
|
277
|
+
handleSSOLogin: () => ssoManager.handleSSOLogin(),
|
|
278
|
+
navigateToApp: (targetApp, returnUrl) => ssoManager.navigateToApp(targetApp, returnUrl),
|
|
279
|
+
getAvailableApps: () => ssoManager.getAvailableApps(),
|
|
280
|
+
}), [ssoManager]);
|
|
281
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Token Storage Management
|
|
3
|
+
*
|
|
4
|
+
* Provides secure storage for JWT tokens using localStorage
|
|
5
|
+
* for cross-domain authentication support and persistent sessions.
|
|
6
|
+
*/
|
|
7
|
+
export interface TokenData {
|
|
8
|
+
token: string;
|
|
9
|
+
expiresAt: number;
|
|
10
|
+
refreshThreshold?: number;
|
|
11
|
+
}
|
|
12
|
+
export type TokenStatus = 'valid' | 'warning' | 'expired';
|
|
13
|
+
/**
|
|
14
|
+
* Store JWT token in localStorage
|
|
15
|
+
* @param token - The JWT token from login response
|
|
16
|
+
* @param expiresIn - Optional expiry time in seconds (default 30 minutes)
|
|
17
|
+
* @param refreshThreshold - Optional refresh threshold in seconds (default 5 minutes)
|
|
18
|
+
*/
|
|
19
|
+
export declare function storeToken(token: string, expiresIn?: number, refreshThreshold?: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Retrieve JWT token from localStorage
|
|
22
|
+
* @returns The stored token or null if not found/expired
|
|
23
|
+
*/
|
|
24
|
+
export declare function getToken(): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Clear JWT token from storage
|
|
27
|
+
*/
|
|
28
|
+
export declare function clearToken(): void;
|
|
29
|
+
/**
|
|
30
|
+
* Get current token status
|
|
31
|
+
* @returns 'valid' if token is fresh, 'warning' if approaching expiry, 'expired' if expired
|
|
32
|
+
*/
|
|
33
|
+
export declare function getTokenStatus(): TokenStatus;
|
|
34
|
+
/**
|
|
35
|
+
* Get time until token expiry in milliseconds
|
|
36
|
+
* @returns Time until expiry in ms, or 0 if expired
|
|
37
|
+
*/
|
|
38
|
+
export declare function getTimeUntilExpiry(): number;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a valid token exists
|
|
41
|
+
* @returns True if a non-expired token exists
|
|
42
|
+
*/
|
|
43
|
+
export declare function hasValidToken(): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Get the Authorization header value for API requests
|
|
46
|
+
* @returns Bearer token header or null
|
|
47
|
+
*/
|
|
48
|
+
export declare function getAuthHeader(): string | null;
|
|
49
|
+
/**
|
|
50
|
+
* Extract token from login response and store it
|
|
51
|
+
* @param response - The auth response from login/refresh
|
|
52
|
+
*/
|
|
53
|
+
export declare function handleAuthResponse(response: any): void;
|
|
54
|
+
/**
|
|
55
|
+
* Get valid token (backend handles refresh via grace period)
|
|
56
|
+
* @returns Valid token or null if expired
|
|
57
|
+
*/
|
|
58
|
+
export declare function getValidToken(): Promise<string | null>;
|