Skip to content

Commit

Permalink
fix(backend): Refactor the new IsomorphicRequest utilities
Browse files Browse the repository at this point in the history
Also, manipulate headers objects to be compatible with Headers constructor
  • Loading branch information
anagstef committed Jun 30, 2023
1 parent 0ddea16 commit a06196f
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 89 deletions.
80 changes: 41 additions & 39 deletions package-lock.json

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

38 changes: 13 additions & 25 deletions packages/backend/src/tokens/request.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { API_URL, API_VERSION, constants } from '../constants';
import { assertValidSecretKey } from '../util/assertValidSecretKey';
import { isDevelopmentFromApiKey } from '../util/instance';
import { parseIsomorphicRequestCookies } from '../util/IsomorphicRequest';
import { buildRequest, stripAuthorizationHeader } from '../util/IsomorphicRequest';
import { parsePublishableKey } from '../util/parsePublishableKey';
import type { RequestState } from './authStatus';
import { AuthErrorReason, interstitial, signedOut, unknownState } from './authStatus';
Expand Down Expand Up @@ -111,32 +111,24 @@ function assertProxyUrlOrDomain(proxyUrlOrDomain: string | undefined) {
}

export async function authenticateRequest(options: AuthenticateRequestOptions): Promise<RequestState> {
const { request: isomorphicRequest } = options;
const isomorphicRequestCookies = isomorphicRequest ? parseIsomorphicRequestCookies(isomorphicRequest) : undefined;
const isomorphicRequestSearchParams = isomorphicRequest?.url
? new URL(isomorphicRequest.url)?.searchParams
: undefined;

const getHeaderFromIsomorphicReq = (key: string) => isomorphicRequest?.headers?.get(key) || undefined;
const { cookies, headers, searchParams } = buildRequest(options?.request);

options = {
...options,
frontendApi: parsePublishableKey(options.publishableKey)?.frontendApi || options.frontendApi,
apiUrl: options.apiUrl || API_URL,
apiVersion: options.apiVersion || API_VERSION,
headerToken:
stripAuthorizationHeader(options.headerToken) ||
stripAuthorizationHeader(getHeaderFromIsomorphicReq(constants.Headers.Authorization)),
cookieToken: options.cookieToken || isomorphicRequestCookies?.(constants.Cookies.Session),
clientUat: options.clientUat || isomorphicRequestCookies?.(constants.Cookies.ClientUat),
origin: options.origin || getHeaderFromIsomorphicReq(constants.Headers.Origin),
host: options.host || getHeaderFromIsomorphicReq(constants.Headers.Host),
forwardedHost: options.forwardedHost || getHeaderFromIsomorphicReq(constants.Headers.ForwardedHost),
forwardedPort: options.forwardedPort || getHeaderFromIsomorphicReq(constants.Headers.ForwardedPort),
forwardedProto: options.forwardedProto || getHeaderFromIsomorphicReq(constants.Headers.ForwardedProto),
referrer: options.referrer || getHeaderFromIsomorphicReq(constants.Headers.Referrer),
userAgent: options.userAgent || getHeaderFromIsomorphicReq(constants.Headers.UserAgent),
searchParams: options.searchParams || isomorphicRequestSearchParams || undefined,
headerToken: stripAuthorizationHeader(options.headerToken || headers?.(constants.Headers.Authorization)),
cookieToken: options.cookieToken || cookies?.(constants.Cookies.Session),
clientUat: options.clientUat || cookies?.(constants.Cookies.ClientUat),
origin: options.origin || headers?.(constants.Headers.Origin),
host: options.host || headers?.(constants.Headers.Host),
forwardedHost: options.forwardedHost || headers?.(constants.Headers.ForwardedHost),
forwardedPort: options.forwardedPort || headers?.(constants.Headers.ForwardedPort),
forwardedProto: options.forwardedProto || headers?.(constants.Headers.ForwardedProto),
referrer: options.referrer || headers?.(constants.Headers.Referrer),
userAgent: options.userAgent || headers?.(constants.Headers.UserAgent),
searchParams: options.searchParams || searchParams || undefined,
};

assertValidSecretKey(options.secretKey || options.apiKey);
Expand Down Expand Up @@ -210,7 +202,3 @@ export const debugRequestState = (params: RequestState) => {
};

export type DebugRequestSate = ReturnType<typeof debugRequestState>;

const stripAuthorizationHeader = (authValue: string | undefined | null): string | undefined => {
return authValue?.replace('Bearer ', '');
};
30 changes: 26 additions & 4 deletions packages/backend/src/util/IsomorphicRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,24 @@ import { parse } from 'cookie';

import runtime from '../runtime';

type IsomorphicRequestOptions = (Request: Request, Headers: Headers) => Request;

type IsomorphicRequestOptions = (Request: typeof runtime.Request, Headers: typeof runtime.Headers) => Request;
export const createIsomorphicRequest = (cb: IsomorphicRequestOptions): Request => {
return cb(runtime.Request as unknown as Request, runtime.Headers as unknown as Headers);
return cb(runtime.Request, runtime.Headers);
};

export const buildRequest = (req?: Request) => {
if (!req) {
return {};
}
const cookies = parseIsomorphicRequestCookies(req);
const headers = getHeaderFromIsomorphicRequest(req);
const searchParams = getSearchParamsFromIsomorphicRequest(req);

return {
cookies,
headers,
searchParams,
};
};

const decode = (str: string): string => {
Expand All @@ -15,7 +29,7 @@ const decode = (str: string): string => {
return str.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent);
};

export const parseIsomorphicRequestCookies = (req: Request) => {
const parseIsomorphicRequestCookies = (req: Request) => {
const cookies = req.headers && req.headers?.get('cookie') ? parse(req.headers.get('cookie') as string) : {};
return (key: string): string | undefined => {
const value = cookies?.[key];
Expand All @@ -25,3 +39,11 @@ export const parseIsomorphicRequestCookies = (req: Request) => {
return decode(value);
};
};

const getHeaderFromIsomorphicRequest = (req: Request) => (key: string) => req?.headers?.get(key) || undefined;

const getSearchParamsFromIsomorphicRequest = (req: Request) => (req?.url ? new URL(req.url)?.searchParams : undefined);

export const stripAuthorizationHeader = (authValue: string | undefined | null): string | undefined => {
return authValue?.replace('Bearer ', '');
};
27 changes: 18 additions & 9 deletions packages/fastify/src/withClerkMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,24 @@ export const withClerkMiddleware = (options: ClerkFastifyOptions) => {
publishableKey,
apiKey: constants.API_KEY,
frontendApi: constants.FRONTEND_API,
request: createIsomorphicRequest((Request: any, Headers: any) => {
const headers = new Headers(req.headers);
headers.set(
constants.Headers.ForwardedHost,
getSingleValueFromArrayHeader(req.headers?.[constants.Headers.ForwardedHost]),
request: createIsomorphicRequest((Request, Headers) => {
const requestHeaders = Object.keys(req.headers).reduce(
(acc, key) => Object.assign(acc, { [key]: req?.headers[key] }),
{},
);
headers.set(
constants.Headers.ForwardedPort,
getSingleValueFromArrayHeader(req.headers?.[constants.Headers.ForwardedPort]),
const headers = new Headers(requestHeaders);
const forwardedHostHeader = getSingleValueFromArrayHeader(
headers?.get(constants.Headers.ForwardedHost) || undefined,
);
if (forwardedHostHeader) {
headers.set(constants.Headers.ForwardedHost, forwardedHostHeader);
}
const forwardedPortHeader = getSingleValueFromArrayHeader(
headers?.get(constants.Headers.ForwardedPort) || undefined,
);
if (forwardedPortHeader) {
headers.set(constants.Headers.ForwardedPort, forwardedPortHeader);
}
const reqUrl = isRelativeUrl(req.url) ? getAbsoluteUrlFromHeaders(req.url, headers) : req.url;
return new Request(reqUrl, {
method: req.method,
Expand Down Expand Up @@ -66,7 +74,8 @@ export const withClerkMiddleware = (options: ClerkFastifyOptions) => {
};

// TODO: Move the utils below to shared package

// Creating a Request object requires a valid absolute URL
// Fastify's req.url is relative, so we need to construct an absolute URL
const getAbsoluteUrlFromHeaders = (url: string, headers: Headers): URL => {
const forwardedProto = headers.get(constants.Headers.ForwardedProto);
const forwardedPort = headers.get(constants.Headers.ForwardedPort);
Expand Down
4 changes: 2 additions & 2 deletions packages/gatsby-plugin-clerk/src/ssr/authenticateRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export function authenticateRequest(context: GetServerDataProps, options: WithSe
secretKey: SECRET_KEY,
frontendApi: FRONTEND_API,
publishableKey: PUBLISHABLE_KEY,
request: createIsomorphicRequest((Request: any, Headers: any) => {
const headers = new Headers(context.headers);
request: createIsomorphicRequest((Request, Headers) => {
const headers = new Headers(Object.fromEntries(context.headers) as Record<string, string>);
headers.set(
constants.Headers.ForwardedHost,
returnReferrerAsXForwardedHostToFixLocalDevGatsbyProxy(context.headers),
Expand Down
8 changes: 1 addition & 7 deletions packages/nextjs/src/server/authenticateRequest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { createIsomorphicRequest } from '@clerk/backend';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';

Expand Down Expand Up @@ -29,12 +28,7 @@ export const authenticateRequest = async (req: NextRequest, opts: WithAuthOption
domain,
signInUrl,
proxyUrl,
request: createIsomorphicRequest((Request: any, Headers: any) => {
return new Request(req.url, {
method: req.method,
headers: new Headers(req.headers),
});
}),
request: req,
});
};

Expand Down
Loading

0 comments on commit a06196f

Please sign in to comment.