diff --git a/next.config.mjs b/next.config.mjs index 4678774e..5a2b31e6 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,13 @@ +const devtoolsURL = process.env.DEVTOOLS_URL || "https://replay-devtools-new.vercel.app"; + /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + rewrites: async () => [ + { + source: "/recording/:path*", + destination: `${devtoolsURL}/recording/:path*` + } + ] +}; export default nextConfig; diff --git a/src/constants.ts b/src/constants.ts index e80d6d9b..4ae58e02 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,6 @@ export const COOKIES = { accessToken: "replay:dashboard:access-token", + authReturnTo: "replay:auth-return-to", browserAuth: "replay:browser-auth", defaultPathname: "replay:dashboard:default-pathname", mobileWarningDismissed: "replay:dashboard:mobile-warning-dismissed", diff --git a/src/pageComponents/user/settings/Account.tsx b/src/pageComponents/user/settings/Account.tsx index 6e1730d6..3c79d55a 100644 --- a/src/pageComponents/user/settings/Account.tsx +++ b/src/pageComponents/user/settings/Account.tsx @@ -14,7 +14,7 @@ export function Account() { deleteCookieValueClient(COOKIES.accessToken); - window.location.replace("/api/auth/logout"); + window.location.replace(`/api/auth/logout?${new URLSearchParams({ origin: location.origin })}`); }; return ( diff --git a/src/pages/api/auth/[auth0].ts b/src/pages/api/auth/[auth0].ts index 046d9b07..b5b0f04e 100644 --- a/src/pages/api/auth/[auth0].ts +++ b/src/pages/api/auth/[auth0].ts @@ -1,9 +1,12 @@ +import { COOKIES } from "@/constants"; import { getValueFromArrayOrString } from "@/utils/getValueFromArrayOrString"; -import { handleAuth, handleCallback, handleLogin } from "@auth0/nextjs-auth0"; +import { handleAuth, handleCallback, handleLogin, handleLogout } from "@auth0/nextjs-auth0"; +import cookie from "cookie"; import { NextApiRequest, NextApiResponse } from "next"; export default handleAuth({ login: (req: NextApiRequest, res: NextApiResponse) => { + const { origin, returnTo } = handleOriginAndReturnTo(req, res); handleLogin(req, res, { authorizationParams: { audience: "https://api.replay.io", @@ -12,9 +15,17 @@ export default handleAuth({ scope: "openid profile offline_access", prompt: getValueFromArrayOrString(req.query.prompt), connection: getValueFromArrayOrString(req.query.connection), - } + redirect_uri: `${origin}/api/auth/callback`, + }, + returnTo }); }, + + logout: (req: NextApiRequest, res: NextApiResponse) => { + const { returnTo } = handleOriginAndReturnTo(req, res); + handleLogout(req, res, { returnTo }); + }, + callback: (req: NextApiRequest, res: NextApiResponse) => { if (req.query.error_description) { const searchParams = new URLSearchParams({ @@ -23,7 +34,29 @@ export default handleAuth({ }); res.redirect(`/browser/error?${searchParams.toString()}`); } else { - handleCallback(req, res); + handleCallback(req, res, { redirectUri: req.cookies[COOKIES.authReturnTo] }); } }, }); + +// This app also handles auth for the domain of the devtools app. +function handleOriginAndReturnTo(req: NextApiRequest, res: NextApiResponse) { + const origin = getValueFromArrayOrString(req.query.origin) || process.env.AUTH0_BASE_URL!; + + const returnTo = origin + (getValueFromArrayOrString(req.query.returnTo) || "/"); + + // We'll need to pass returnTo to the callback handler, otherwise + // Auth0 will reject login requests for the domains of other apps. + // We store it in a cookie so that it's available when the callback handler is called. + res.setHeader( + "Set-Cookie", + cookie.serialize(COOKIES.authReturnTo, returnTo, { + secure: origin.startsWith("https://"), + httpOnly: true, + path: "/", + maxAge: 5 * 60 * 1000, + }) + ); + + return { origin, returnTo }; +} diff --git a/src/pages/api/token.ts b/src/pages/api/token.ts new file mode 100644 index 00000000..ad79320f --- /dev/null +++ b/src/pages/api/token.ts @@ -0,0 +1,10 @@ +import { getAccessToken } from "@auth0/nextjs-auth0"; +import { NextApiRequest, NextApiResponse } from "next"; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + const { accessToken } = await getAccessToken(req, res); + res.status(200).send(accessToken); +} diff --git a/src/pages/login.tsx b/src/pages/login.tsx index eccefac1..32e0e259 100644 --- a/src/pages/login.tsx +++ b/src/pages/login.tsx @@ -117,7 +117,7 @@ export default function Page({ const [ssoLogin, setSSOLogin] = useState(false); function onLogin(connection: string) { - let authUrl = `/api/auth/login?connection=${connection}&returnTo=${returnTo}`; + let authUrl = `/api/auth/login?${new URLSearchParams({ connection, returnTo, origin: location.origin })}`; if (switchAccount) { authUrl += "&prompt=login"; } diff --git a/src/utils/recording.ts b/src/utils/recording.ts index 2b94026d..7d3ba1b9 100644 --- a/src/utils/recording.ts +++ b/src/utils/recording.ts @@ -32,6 +32,6 @@ export function getURL(id: string, buildId: string) { const target = getRecordingTarget(buildId); return target === "chromium" - ? `https://app.replay.io/recording/${id}` + ? `/recording/${id}` : `https://legacy.replay.io/recording/${id}`; }