Skip to content

Commit

Permalink
Paths: Forward graphql cookies from the backend to the client
Browse files Browse the repository at this point in the history
  • Loading branch information
juyrjola committed Oct 13, 2024
1 parent 300c3ab commit 2c581aa
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 10 deletions.
4 changes: 2 additions & 2 deletions apollo-paths.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// eslint-disable-next-line @typescript-eslint/no-var-requires
require('dotenv').config();

const { pathsApiUrl } = require('./common/environment');
const { pathsGqlUrl } = require('./common/environment');

const JS = '*.{js,jsx,ts,tsx,mjs}';

Expand All @@ -15,7 +15,7 @@ module.exports = {
],
service: {
name: 'kausal-paths',
url: `${pathsApiUrl}/graphql/`,
url: `${pathsGqlUrl}`,
},
},
};
20 changes: 15 additions & 5 deletions app/api/graphql-paths/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { headers } from 'next/headers';
import { NextResponse } from 'next/server';
import { NextRequest, NextResponse } from 'next/server';

import { captureException } from '@sentry/nextjs';

export const dynamic = 'force-dynamic';
import { forwardSetCookie, getClientCookieAsHeader } from '@/common/cookies';
import { pathsGqlUrl } from '@/common/environment';

const gqlUrl = 'https://api.paths.kausal.dev/v1/graphql/';
export const dynamic = 'force-dynamic';

const PASS_HEADERS = [
'x-paths-instance-identifier',
Expand All @@ -18,9 +19,14 @@ const PASS_HEADERS = [
'referer',
];

export async function POST(request: Request) {
const PATHS_COOKIE_PREFIX = 'paths_api_';

export async function POST(request: NextRequest) {
const headersList = headers();
const requestData = await request.json();
const backendCookieHeader = getClientCookieAsHeader(request, {
prefix: PATHS_COOKIE_PREFIX,
});

// Determine headers to send to the backend
const backendHeaders: Record<string, string> = {};
Expand All @@ -29,9 +35,12 @@ export async function POST(request: Request) {
if (value) backendHeaders[header] = value;
});
backendHeaders['Content-Type'] = 'application/json';
if (backendCookieHeader) {
backendHeaders['Cookie'] = backendCookieHeader;
}

// Do the fetch from the backend
const backendResponse = await fetch(gqlUrl, {
const backendResponse = await fetch(pathsGqlUrl, {
method: 'POST',
headers: backendHeaders,
body: JSON.stringify(requestData),
Expand Down Expand Up @@ -71,6 +80,7 @@ export async function POST(request: Request) {

try {
const data = await backendResponse.json();
forwardSetCookie(request, backendResponse, { prefix: PATHS_COOKIE_PREFIX });
return NextResponse.json(data, { status: 200, headers: responseHeaders });
} catch (error) {
return NextResponse.json(
Expand Down
58 changes: 58 additions & 0 deletions common/cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
parseSetCookie,
RequestCookie,
} from 'next/dist/compiled/@edge-runtime/cookies';
import { cookies } from 'next/headers';
import { NextRequest } from 'next/server';

export type BackendCookieOptions = {
prefix: string;
};

export function getClientCookies(
req: NextRequest,
opts: BackendCookieOptions
): RequestCookie[] {
const cookieHeader = req.headers.get('cookie');
if (!cookieHeader) return [];
const prefixedCookies = req.cookies
.getAll()
.filter((cookie) => cookie.name.startsWith(opts.prefix));
return prefixedCookies.map((cookie) => {
return {
name: cookie.name.slice(opts.prefix.length),
value: cookie.value,
};
});
}

export function getClientCookieAsHeader(
req: NextRequest,
opts: BackendCookieOptions
): string | null {
const cookies = getClientCookies(req, opts);
if (!cookies.length) return null;
return cookies
.map((cookie) => `${cookie.name}=${encodeURIComponent(cookie.value)}`)
.join('; ');
}

export function forwardSetCookie(
clientReq: NextRequest,
backendResponse: Response,
opts: BackendCookieOptions
) {
const setCookieHeaders = backendResponse.headers.getSetCookie();
if (!setCookieHeaders.length) return;

// Pass cookies to the client, modify some of the attributes along the way
setCookieHeaders.forEach((header) => {
const cookie = parseSetCookie(header);
if (!cookie) return;

cookie.sameSite = 'strict';
cookie.secure = process.env.NODE_ENV === 'production';
cookie.name = `${opts.prefix}${cookie.name}`;
cookies().set(cookie);
});
}
5 changes: 3 additions & 2 deletions common/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,6 @@ export const authIssuer = process.env.NEXT_PUBLIC_AUTH_ISSUER;
export const logGraphqlQueries =
isServer && process.env.LOG_GRAPHQL_QUERIES === 'true';

export const pathsApiUrl =
process.env.NEXT_PUBLIC_PATHS_API_URL || 'https://api.paths.kausal.dev/v1';
export const pathsBackendUrl =
process.env.NEXT_PUBLIC_PATHS_BACKEND_URL || 'https://api.paths.kausal.dev';
export const pathsGqlUrl = `${pathsBackendUrl}/v1/graphql/`;
4 changes: 3 additions & 1 deletion sentry.server.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
// https://docs.sentry.io/platforms/javascript/guides/nextjs/

import * as Sentry from '@sentry/nextjs';
import { deploymentType } from './common/environment';

import { deploymentType, gqlUrl, pathsGqlUrl } from './common/environment';

Sentry.init({
environment: deploymentType,
Expand All @@ -15,6 +16,7 @@ Sentry.init({
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.1,

tracePropagationTargets: ['localhost', /^\//, gqlUrl, pathsGqlUrl],
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
});

0 comments on commit 2c581aa

Please sign in to comment.