Skip to content

Commit

Permalink
Improve auth redirects handling
Browse files Browse the repository at this point in the history
  • Loading branch information
JiriLojda committed Sep 26, 2023
1 parent cb775bb commit 5396d46
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 89 deletions.
2 changes: 2 additions & 0 deletions lib/constants/cookies.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const envIdCookieName = "currentEnvId";

export const previewApiKeyCookieName = "currentPreviewApiKey";

export const ignoreMissingApiKeyCookieName = "ignoreMissingApiKey";
30 changes: 19 additions & 11 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from 'next/server'

import { envIdCookieName, previewApiKeyCookieName } from './lib/constants/cookies';
import { envIdCookieName, ignoreMissingApiKeyCookieName, previewApiKeyCookieName } from './lib/constants/cookies';
import { createQueryString } from './lib/routing';
import { defaultEnvId } from './lib/utils/env';

Expand Down Expand Up @@ -36,27 +36,34 @@ const handleExplicitProjectRoute = (currentEnvId: string) => (prevResponse: Next
return prevResponse;
}

if (request.nextUrl.pathname.includes("/api/exit-preview") && request.cookies.get(ignoreMissingApiKeyCookieName)) {
return prevResponse;
}

if (routeEnvId === defaultEnvId) {
const res = NextResponse.redirect(new URL(createUrlWithQueryString(remainingUrl, request.nextUrl.searchParams), request.nextUrl.origin));
res.cookies.set(envIdCookieName, routeEnvId, { path: '/', sameSite: 'none', secure: true });
res.cookies.set(previewApiKeyCookieName, '', { path: '/', sameSite: 'none', secure: true });
res.cookies.set(envIdCookieName, routeEnvId, cookieOptions);
res.cookies.set(previewApiKeyCookieName, '', cookieOptions);

return res
}

if (routeEnvId !== currentEnvId || !request.cookies.get(previewApiKeyCookieName)) {
const res = NextResponse.redirect(new URL(`/getPreviewApiKey?path=${encodeURIComponent(createUrlWithQueryString(remainingUrl, request.nextUrl.searchParams.entries()))}`, request.url))
const originalPath = encodeURIComponent(createUrlWithQueryString(remainingUrl, request.nextUrl.searchParams.entries()));
const redirectPath = `/api/exit-preview?callback=${encodeURIComponent(`/getPreviewApiKey?path=${originalPath}`)}`;
const res = NextResponse.redirect(new URL(redirectPath, request.url));

res.cookies.set(envIdCookieName, routeEnvId, { path: '/', sameSite: 'none', secure: true });
res.cookies.set(previewApiKeyCookieName, '', { path: '/', sameSite: 'none', secure: true });
res.cookies.set(envIdCookieName, routeEnvId, cookieOptions);
res.cookies.set(previewApiKeyCookieName, '', cookieOptions);
res.cookies.set(ignoreMissingApiKeyCookieName, "true", cookieOptions);

return res;
}

return NextResponse.redirect(new URL(`${remainingUrl ?? ''}?${createQueryString(Object.fromEntries(request.nextUrl.searchParams.entries()))}`, request.nextUrl.origin));
}

const handleArticlesRoute = (currentEnvId: string) => (prevResponse: NextResponse, request: NextRequest,) => request.nextUrl.pathname === '/articles'
const handleArticlesRoute = (currentEnvId: string) => (prevResponse: NextResponse, request: NextRequest) => request.nextUrl.pathname === '/articles'
? NextResponse.rewrite(new URL(`/${currentEnvId}/articles/category/all/page/1`, request.url))
: prevResponse;

Expand All @@ -72,11 +79,10 @@ const handleArticlesCategoryWithNoPaginationRoute = (currentEnvId: string) => (p

const handleEmptyCookies = (prevResponse: NextResponse, request: NextRequest) => {
if (!request.cookies.get(envIdCookieName)?.value) {
prevResponse.cookies.set(envIdCookieName, defaultEnvId, { path: '/', sameSite: 'none', secure: true })
prevResponse.cookies.set(envIdCookieName, defaultEnvId, cookieOptions)
}
if (!request.cookies.get(envIdCookieName)?.value || request.cookies.get(envIdCookieName)?.value === defaultEnvId) {

prevResponse.cookies.set(previewApiKeyCookieName, KONTENT_PREVIEW_API_KEY, { path: '/', sameSite: 'none', secure: true })
prevResponse.cookies.set(previewApiKeyCookieName, KONTENT_PREVIEW_API_KEY, cookieOptions)
}


Expand All @@ -101,4 +107,6 @@ export const config = {
'/((?!api|_next/static|_next/image|favicon.png|getPreviewApiKey|logo.png|callback).*)',
'/'
],
}
};

const cookieOptions = { path: '/', sameSite: 'none', secure: true } as const;
106 changes: 53 additions & 53 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"@vercel/analytics": "^1.0.0",
"auth0-js": "^9.22.1",
"cookies-next": "^4.0.0",
"next": "13.4.19",
"next": "^13.5.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"swiper": "^10.2.0"
Expand Down
23 changes: 13 additions & 10 deletions pages/api/exit-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { NextApiHandler } from "next";

import { ignoreMissingApiKeyCookieName } from "../../lib/constants/cookies";

const handler: NextApiHandler = (req, res) => {
console.log("Exiting preview");
// Exit the current user from "Preview Mode". This function accepts no args.
res.clearPreviewData();

// Redirect the user back to the index page.
// Might be implemented return URL by the query string.
res.redirect("/");
}

export default handler;
// Exit the current user from "Preview Mode". This function accepts no args.
res.clearPreviewData();

res.setHeader("Set-Cookie", `${ignoreMissingApiKeyCookieName}=; Path=/; SameSite=None; Secure; Max-Age=-1`);

// Redirect the user back to the index page.
// Might be implemented return URL by the query string.
res.redirect(typeof req.query.callback === "string" ? req.query.callback : "/");
}

export default handler;
34 changes: 30 additions & 4 deletions pages/api/preview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NextApiHandler } from "next";
import { NextApiHandler, NextApiResponse } from "next";

import { envIdCookieName, previewApiKeyCookieName } from "../../lib/constants/cookies";
import { ResolutionContext, resolveUrlPath } from "../../lib/routing";
Expand All @@ -7,18 +7,23 @@ import { defaultEnvId } from "../../lib/utils/env";
const handler: NextApiHandler = async (req, res) => {
// TODO move secret to env variables
if (req.query.secret !== 'mySuperSecret' || !req.query.slug || !req.query.type) {
return res.status(401).json({ message: 'Invalid preview token, or no slug and type provided.' })
res.status(401).json({ message: 'Invalid preview token, or no slug and type provided.' });
return;
}

const currentEnvId = req.cookies[envIdCookieName];
const currentPreviewApiKey = req.cookies[previewApiKeyCookieName];

if (!currentPreviewApiKey && currentEnvId !== defaultEnvId) {
return res.redirect(`/getPreviewApiKey?path=${encodeURIComponent(req.url ?? '')}`);
res.redirect(`/api/exit-preview?callback=${`/getPreviewApiKey?path=${encodeURIComponent(req.url ?? '')}`}`);
return;
}

// Enable Preview Mode by setting the cookies
res.setPreviewData({ currentPreviewApiKey });
const newCookieHeader = makeCookiesCrossOrigin(res);
if (newCookieHeader) {
res.setHeader("Set-Cookie", newCookieHeader);
}

const path = resolveUrlPath({
type: req.query.type.toString(),
Expand All @@ -30,3 +35,24 @@ const handler: NextApiHandler = async (req, res) => {
}

export default handler;

const makeCookieCrossOrigin = (header: string) => {
const cookie = header.split(";")[0];

return cookie
? `${cookie}; Path=/; SameSite=None; Secure`
: "";
};

const makeCookiesCrossOrigin = (response: NextApiResponse) => {
const header = response.getHeader("Set-Cookie");

if (typeof header === "string") {
return makeCookieCrossOrigin(header);
}
if (Array.isArray(header)) {
return header.map(makeCookieCrossOrigin);
}

return header;
}
Loading

0 comments on commit 5396d46

Please sign in to comment.