2 Commits

Author SHA1 Message Date
poduck
edc896b10e Add auto-authentication for new tabs opened from embedded mode
When opening flows/runs in new tabs from within the embedded Activepieces
iframe, users were being redirected to a login page because the JWT token
stored in sessionStorage wasn't shared across tabs.

Changes:
- Modify /authenticate route to accept standalone token parameter
- Update useNewWindow hook to pass JWT token via URL in embedded mode
- Add click handler in ApSidebarItem for authenticated new-window links

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-29 21:19:52 -05:00
poduck
76be5377d9 Remove 'Open in new tab' link from Automations page (requires iframe auth) 2025-12-29 21:07:45 -05:00
4 changed files with 80 additions and 20 deletions

View File

@@ -2,6 +2,7 @@ import { LockKeyhole } from 'lucide-react';
import { ComponentType, SVGProps } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { useEmbedding } from '@/components/embed-provider';
import { buttonVariants } from '@/components/ui/button';
import { Dot } from '@/components/ui/dot';
import {
@@ -15,6 +16,7 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { authenticationSession } from '@/lib/authentication-session';
import { cn } from '@/lib/utils';
export type SidebarItemType = {
@@ -34,10 +36,26 @@ export type SidebarItemType = {
export const ApSidebarItem = (item: SidebarItemType) => {
const location = useLocation();
const { state } = useSidebar();
const { embedState } = useEmbedding();
const isLinkActive =
location.pathname.startsWith(item.to) || item.isActive?.(location.pathname);
const isCollapsed = state === 'collapsed';
// Handle new window clicks with authentication in embedded mode
const handleNewWindowClick = (e: React.MouseEvent) => {
if (item.newWindow && embedState.isEmbedded) {
e.preventDefault();
const token = authenticationSession.getToken();
if (token) {
const encodedRedirect = encodeURIComponent(item.to);
const authUrl = `/authenticate?token=${encodeURIComponent(token)}&redirect=${encodedRedirect}`;
window.open(authUrl, '_blank', 'noopener');
} else {
window.open(item.to, '_blank', 'noopener noreferrer');
}
}
};
return (
<SidebarMenuItem
onClick={(e) => e.stopPropagation()}
@@ -51,6 +69,7 @@ export const ApSidebarItem = (item: SidebarItemType) => {
to={item.to}
target={item.newWindow ? '_blank' : ''}
rel={item.newWindow ? 'noopener noreferrer' : undefined}
onClick={handleNewWindowClick}
className={cn(
buttonVariants({ variant: 'ghost', size: 'icon' }),
isLinkActive && 'bg-sidebar-accent hover:!bg-sidebar-accent',
@@ -83,6 +102,7 @@ export const ApSidebarItem = (item: SidebarItemType) => {
to={item.to}
target={item.newWindow ? '_blank' : ''}
rel={item.newWindow ? 'noopener noreferrer' : undefined}
onClick={handleNewWindowClick}
>
<div className="w-full flex items-center justify-between gap-2">
<div className="flex items-center gap-2 w-full">

View File

@@ -9,14 +9,22 @@ const AuthenticatePage = () => {
const searchParams = new URLSearchParams(location.search);
const response = searchParams.get('response');
const token = searchParams.get('token');
const redirectTo = searchParams.get('redirect') || '/flows';
useEffect(() => {
if (response) {
// Handle full response object (legacy)
const decodedResponse = JSON.parse(response);
authenticationSession.saveResponse(decodedResponse, false);
navigate('/flows');
navigate(redirectTo);
} else if (token) {
// Handle standalone JWT token (from embedded mode new tab)
// Save token directly to localStorage for persistence in new tabs
authenticationSession.saveResponse({ token }, false);
navigate(redirectTo);
}
}, [response]);
}, [response, token, redirectTo, navigate]);
return <>Please wait...</>;
};

View File

@@ -2,15 +2,30 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import { useEmbedding } from '../components/embed-provider';
import { authenticationSession } from './authentication-session';
export const useNewWindow = () => {
const { embedState } = useEmbedding();
const navigate = useNavigate();
if (embedState.isEmbedded) {
return (route: string, searchParams?: string) =>
// In embedded mode, open new tab with authentication token
return (route: string, searchParams?: string) => {
const token = authenticationSession.getToken();
const fullRoute = `${route}${searchParams ? '?' + searchParams : ''}`;
if (token) {
// Pass token for auto-authentication in new tab
const encodedRedirect = encodeURIComponent(fullRoute);
const authUrl = `/authenticate?token=${encodeURIComponent(token)}&redirect=${encodedRedirect}`;
window.open(authUrl, '_blank', 'noopener');
} else {
// Fallback to in-iframe navigation if no token
navigate({
pathname: route,
search: searchParams,
});
}
};
} else {
return (route: string, searchParams?: string) =>
window.open(
@@ -21,6 +36,35 @@ export const useNewWindow = () => {
}
};
/**
* Opens a route in a new browser tab with automatic authentication.
* For embedded contexts where sessionStorage isn't shared across tabs,
* this passes the JWT token via URL for auto-login.
*/
export const useOpenInNewTab = () => {
const { embedState } = useEmbedding();
return (route: string, searchParams?: string) => {
const token = authenticationSession.getToken();
if (embedState.isEmbedded && token) {
// In embedded mode, pass token for auto-authentication in new tab
const encodedRedirect = encodeURIComponent(
`${route}${searchParams ? '?' + searchParams : ''}`,
);
const authUrl = `/authenticate?token=${encodeURIComponent(token)}&redirect=${encodedRedirect}`;
window.open(authUrl, '_blank', 'noopener');
} else {
// Non-embedded mode - token is already in localStorage
window.open(
`${route}${searchParams ? '?' + searchParams : ''}`,
'_blank',
'noopener noreferrer',
);
}
};
};
export const FROM_QUERY_PARAM = 'from';
/**State param is for oauth2 flow, it is used to redirect to the page after login*/
export const STATE_QUERY_PARAM = 'state';

View File

@@ -1,7 +1,7 @@
import { useState, useEffect, useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { Bot, RefreshCw, AlertTriangle, Loader2, ExternalLink, Sparkles, RotateCcw, ChevronDown } from 'lucide-react';
import { Bot, RefreshCw, AlertTriangle, Loader2, Sparkles, RotateCcw, ChevronDown } from 'lucide-react';
import api from '../api/client';
import { usePlanFeatures } from '../hooks/usePlanFeatures';
import { UpgradePrompt } from '../components/UpgradePrompt';
@@ -387,18 +387,6 @@ export default function Automations() {
<RefreshCw className="h-5 w-5" />
</button>
{/* Open in new tab */}
{embedData?.embedUrl && (
<a
href={embedData.embedUrl}
target="_blank"
rel="noopener noreferrer"
className="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
title={t('automations.openInTab', 'Open in new tab')}
>
<ExternalLink className="h-5 w-5" />
</a>
)}
</div>
</div>
</div>