@@ -16,7 +16,7 @@ import {
1616 X_ENCODED_HEADERS ,
1717 CONTENT_SECURITY_POLICY ,
1818 SLAS_TOKEN_RESPONSE_ENDPOINTS ,
19- SLAS_ENDPOINTS_REQUIRING_ACCESS_TOKEN
19+ SLAS_LOGOUT_ENDPOINT
2020} from './constants'
2121import {
2222 catchAndLog ,
@@ -64,7 +64,7 @@ import {ApiGatewayV1Adapter} from '@h4ad/serverless-adapter/lib/adapters/aws'
6464import { ExpressFramework } from '@h4ad/serverless-adapter/lib/frameworks/express'
6565import { is as typeis } from 'type-is'
6666import { getConfig } from '../../utils/ssr-config'
67- import { applyHttpOnlySessionCookies } from './process-token-response'
67+ import { setHttpOnlySessionCookies } from './process-token-response'
6868
6969/**
7070 * An Array of mime-types (Content-Type values) that are considered
@@ -94,6 +94,47 @@ export const isBinary = (headers) => {
9494 return isContentTypeBinary ( headers )
9595}
9696
97+ /**
98+ * Inject Bearer token and refresh token from HttpOnly cookies for the SLAS logout endpoint.
99+ * Reads the access token and refresh token from cookies keyed by siteId (from the x-site-id header),
100+ * sets the Authorization header, and appends refresh_token to the query string.
101+ * @private
102+ */
103+ export const setTokensInLogoutRequest = ( proxyRequest , incomingRequest ) => {
104+ const cookieHeader = incomingRequest . headers . cookie
105+ if ( ! cookieHeader ) return
106+
107+ const cookies = cookie . parse ( cookieHeader )
108+ const siteId = incomingRequest . headers [ 'x-site-id' ]
109+ if ( ! siteId ) {
110+ logger . warn (
111+ 'x-site-id header is missing on SLAS logout request. ' +
112+ 'Token injection skipped. ' +
113+ 'Ensure the x-site-id header is set in CommerceApiProvider headers.' ,
114+ { namespace : 'setTokensInLogoutRequest' }
115+ )
116+ return
117+ }
118+
119+ // Inject Bearer token from access token cookie
120+ const accessToken = cookies [ `cc-at_${ siteId } ` ]
121+ if ( accessToken ) {
122+ proxyRequest . setHeader ( 'Authorization' , `Bearer ${ accessToken } ` )
123+ }
124+
125+ // Inject refresh_token into query string from HttpOnly cookie
126+ const refreshToken = cookies [ `cc-nx_${ siteId } ` ]
127+ if ( refreshToken ) {
128+ const separator = proxyRequest . path . includes ( '?' ) ? '&' : '?'
129+ proxyRequest . path += `${ separator } refresh_token=${ encodeURIComponent ( refreshToken ) } `
130+ } else {
131+ logger . warn (
132+ `Refresh token cookie (cc-nx_${ siteId } ) not found for ${ incomingRequest . path } . The logout request may fail.` ,
133+ { namespace : 'setTokensInLogoutRequest' }
134+ )
135+ }
136+ }
137+
97138/**
98139 * Environment variables that must be set for the Express app to run remotely.
99140 *
@@ -170,12 +211,6 @@ export const RemoteServerFactory = {
170211 // cookies applied when that feature is enabled. Users can override this in ssr.js.
171212 tokenResponseEndpoints : SLAS_TOKEN_RESPONSE_ENDPOINTS ,
172213
173- // A regex for identifying which SLAS auth endpoints (/shopper/auth/) require the
174- // shopper's access token in the Authorization header (Bearer token from HttpOnly cookie).
175- // Most SLAS auth endpoints use Basic Auth with client credentials, but some like logout
176- // require the shopper's Bearer token. Users can override this in ssr.js.
177- slasEndpointsRequiringAccessToken : SLAS_ENDPOINTS_REQUIRING_ACCESS_TOKEN ,
178-
179214 // Custom callback to modify the SLAS private client proxy request. This callback is invoked
180215 // after the built-in proxy request handling. Users can provide additional
181216 // request modifications (e.g., custom headers).
@@ -233,16 +268,6 @@ export const RemoteServerFactory = {
233268 // Note: HttpOnly session cookies are controlled by the MRT_DISABLE_HTTPONLY_SESSION_COOKIES
234269 // env var (set by MRT in production, pwa-kit-dev locally). Read directly where needed.
235270
236- // Extract siteId from app configuration for SCAPI auth
237- // This will be used to read the correct access token cookie
238- try {
239- const config = getConfig ( { buildDirectory : options . buildDir } )
240- options . siteId = config ?. app ?. commerceAPI ?. parameters ?. siteId || null
241- } catch ( e ) {
242- // Config may not be available yet (e.g., during build), that's okay
243- options . siteId = null
244- }
245-
246271 return options
247272 } ,
248273
@@ -399,8 +424,7 @@ export const RemoteServerFactory = {
399424 * @private
400425 */
401426 _configureProxyConfigs ( options ) {
402- const siteId = options . siteId || null
403- configureProxyConfigs ( options . appHostname , options . protocol , siteId )
427+ configureProxyConfigs ( options . appHostname , options . protocol )
404428 } ,
405429
406430 /**
@@ -979,39 +1003,9 @@ export const RemoteServerFactory = {
9791003 proxyRequest . setHeader ( 'Authorization' , `Basic ${ encodedSlasCredentials } ` )
9801004 } else if (
9811005 process . env . MRT_DISABLE_HTTPONLY_SESSION_COOKIES === 'false' &&
982- incomingRequest . path ?. match ( options . slasEndpointsRequiringAccessToken )
1006+ incomingRequest . path ?. match ( SLAS_LOGOUT_ENDPOINT )
9831007 ) {
984- // Inject tokens from HttpOnly cookies for endpoints like /oauth2/logout
985- const cookieHeader = incomingRequest . headers . cookie
986- if ( cookieHeader ) {
987- const cookies = cookie . parse ( cookieHeader )
988- const siteId = options . mobify ?. app ?. commerceAPI ?. parameters ?. siteId
989- if ( siteId ) {
990- const site = siteId . trim ( )
991-
992- // Inject Bearer token from access token cookie
993- const accessToken = cookies [ `cc-at_${ site } ` ]
994- if ( accessToken ) {
995- proxyRequest . setHeader ( 'Authorization' , `Bearer ${ accessToken } ` )
996- }
997-
998- // Inject refresh_token into query string from HttpOnly cookie
999- // refresh_token ishouls required for /oauth2/logout
1000- const refreshToken = cookies [ `cc-nx_${ site } ` ]
1001- if ( refreshToken ) {
1002- const url = new URL ( proxyRequest . path , 'http://localhost' )
1003- url . searchParams . set ( 'refresh_token' , refreshToken )
1004- proxyRequest . path = url . pathname + url . search
1005- } else {
1006- logger . warn (
1007- `Registered refresh token cookie (cc-nx_${ site } ) not found for ${ incomingRequest . path } . The logout request may fail.` ,
1008- {
1009- namespace : '_setupSlasPrivateClientProxy'
1010- }
1011- )
1012- }
1013- }
1014- }
1008+ setTokensInLogoutRequest ( proxyRequest , incomingRequest )
10151009 }
10161010
10171011 // Allow users to apply additional custom modifications to the proxy request
@@ -1043,7 +1037,7 @@ export const RemoteServerFactory = {
10431037 isTokenEndpoint
10441038 ) {
10451039 try {
1046- workingBuffer = applyHttpOnlySessionCookies (
1040+ workingBuffer = setHttpOnlySessionCookies (
10471041 workingBuffer ,
10481042 proxyRes ,
10491043 req ,
@@ -1499,10 +1493,7 @@ export const RemoteServerFactory = {
14991493 * @param {RegExp } [options.tokenResponseEndpoints] - A regex pattern to match SLAS endpoints
15001494 * that return tokens in the response body. Used to determine which responses should have HttpOnly
15011495 * session cookies applied. Defaults to /\/oauth2\/(token|passwordless\/token)$/.
1502- * @param {RegExp } [options.slasEndpointsRequiringAccessToken] - A regex pattern to match SLAS auth
1503- * endpoints (/shopper/auth/) that require the shopper's access token in the Authorization header (Bearer token).
1504- * Most SLAS auth endpoints use Basic Auth with client credentials, but some like logout require the shopper's
1505- * Bearer token. Defaults to /\/oauth2\/logout/.
1496+
15061497 * @param {function } [options.onSLASPrivateProxyReq] - Custom callback to modify SLAS private client
15071498 * proxy requests. Called after built-in request handling. Signature: (proxyRequest, incomingRequest, res) => void.
15081499 * Use this to add custom headers or modify the proxy request.
0 commit comments