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>
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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...</>;
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user