11import type { Clerk , RequestState } from '@clerk/backend' ;
22import { constants } from '@clerk/backend' ;
3+ import { handleValueOrFn , isHttpOrHttps , isProxyUrlRelative , isValidProxyUrl } from '@clerk/shared' ;
34import cookie from 'cookie' ;
45import type { IncomingMessage , ServerResponse } from 'http' ;
56
@@ -21,6 +22,25 @@ export const authenticateRequest = (opts: {
2122 const { clerkClient, apiKey, secretKey, frontendApi, publishableKey, req, options } = opts ;
2223 const cookies = parseCookies ( req ) ;
2324 const { jwtKey, authorizedParties } = options || { } ;
25+
26+ const requestUrl = getRequestUrl ( req ) ;
27+ const isSatellite =
28+ handleValueOrFn ( options ?. isSatellite , requestUrl ) || process . env . CLERK_IS_SATELLITE === 'true' || false ;
29+ const domain = handleValueOrFn ( options ?. domain , requestUrl ) || process . env . CLERK_DOMAIN || '' ;
30+ const signInUrl = options ?. signInUrl || process . env . CLERK_SIGN_IN_URL || '' ;
31+ const proxyUrl = absoluteProxyUrl (
32+ handleValueOrFn ( options ?. proxyUrl , requestUrl , process . env . CLERK_PROXY_URL ) as string ,
33+ requestUrl . toString ( ) ,
34+ ) ;
35+
36+ if ( isSatellite && ! proxyUrl && ! domain ) {
37+ throw new Error ( satelliteAndMissingProxyUrlAndDomain ) ;
38+ }
39+
40+ if ( isSatellite && ! isHttpOrHttps ( signInUrl ) && isDevelopmentFromApiKey ( secretKey ) ) {
41+ throw new Error ( satelliteAndMissingSignInUrl ) ;
42+ }
43+
2444 return clerkClient . authenticateRequest ( {
2545 apiKey,
2646 secretKey,
@@ -35,7 +55,11 @@ export const authenticateRequest = (opts: {
3555 forwardedPort : req . headers [ constants . Headers . ForwardedPort ] as string ,
3656 forwardedHost : req . headers [ constants . Headers . ForwardedHost ] as string ,
3757 referrer : req . headers . referer ,
38- userAgent : req . headers [ 'user-agent' ] as string ,
58+ userAgent : req . headers [ constants . Headers . UserAgent ] as string ,
59+ proxyUrl,
60+ isSatellite,
61+ domain,
62+ signInUrl,
3963 } ) ;
4064} ;
4165export const handleUnknownCase = ( res : ServerResponse , requestState : RequestState ) => {
@@ -57,3 +81,40 @@ export const decorateResponseWithObservabilityHeaders = (res: ServerResponse, re
5781 requestState . reason && res . setHeader ( constants . Headers . AuthReason , encodeURIComponent ( requestState . reason ) ) ;
5882 requestState . status && res . setHeader ( constants . Headers . AuthStatus , encodeURIComponent ( requestState . status ) ) ;
5983} ;
84+
85+ const isDevelopmentFromApiKey = ( apiKey : string ) : boolean =>
86+ apiKey . startsWith ( 'test_' ) || apiKey . startsWith ( 'sk_test_' ) ;
87+
88+ const getRequestUrl = ( req : IncomingMessage ) : URL => {
89+ return new URL ( req . url as string , `${ getRequestProto ( req ) } ://${ req . headers . host } ` ) ;
90+ } ;
91+
92+ const getRequestProto = ( req : IncomingMessage ) : string => {
93+ // @ts -ignore Optimistic attempt to get the protocol in case
94+ // req extends IncomingMessage in a useful way. No guarantee
95+ // it'll work.
96+ const mightWork = req . connection ?. encrypted ? 'https' : 'http' ;
97+ // The x-forwarded-proto header takes precedence.
98+ const proto = ( req . headers [ constants . Headers . ForwardedProto ] as string ) || mightWork ;
99+ if ( ! proto ) {
100+ throw new Error ( missingProto ) ;
101+ }
102+ // Sometimes the x-forwarded-proto header does not come as a
103+ // single value.
104+ return proto . split ( ',' ) [ 0 ] . trim ( ) ;
105+ } ;
106+
107+ const absoluteProxyUrl = ( relativeOrAbsoluteUrl : string , baseUrl : string ) : string => {
108+ if ( ! relativeOrAbsoluteUrl || ! isValidProxyUrl ( relativeOrAbsoluteUrl ) || ! isProxyUrlRelative ( relativeOrAbsoluteUrl ) ) {
109+ return relativeOrAbsoluteUrl ;
110+ }
111+ return new URL ( relativeOrAbsoluteUrl , baseUrl ) . toString ( ) ;
112+ } ;
113+
114+ const satelliteAndMissingProxyUrlAndDomain =
115+ 'Missing domain and proxyUrl. A satellite application needs to specify a domain or a proxyUrl' ;
116+ const satelliteAndMissingSignInUrl = `
117+ Invalid signInUrl. A satellite application requires a signInUrl for development instances.
118+ Check if signInUrl is missing from your configuration or it is not a absolute URL.` ;
119+ const missingProto =
120+ "Cannot determine the request protocol. Please make sure you've set the X-Forwarded-Proto header with the request protocol (http or https)." ;
0 commit comments