fix: use promise token cookie in preflight and suggestions controller#1917
fix: use promise token cookie in preflight and suggestions controller#1917radhikagpt1208 merged 10 commits intomainfrom
Conversation
|
This PR will trigger a patch release when merged. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
Hi @solaris007, CORS for Credentialed Preflight FlowProblemThe Preflight MFE uses
With credentials, the browser requires:
Solution OverviewEnable Lambda-based CORS in the api-service for deployed environments and align configuration across dev/stage/prod. 1. Use Lambda for CORS (Remove "Local Only")File: Update the comment so CORS is not treated as local-only: /**
* CORS middleware wrapper for credentialed cross-origin requests.
* When ENABLE_CORS=true, reflects the requesting origin (if allowlisted) and
* sets Access-Control-Allow-Credentials for cookie-bearing requests.
* Required for Preflight MFE flow (POST /auth/promise, POST /preflight/jobs).
*/No code changes to 2. Environment ConfigurationAdd the following to secrets/params for each environment. dev{
"ENABLE_CORS": "true",
"CORS_ALLOWED_ORIGINS": "https://preflight-dev.adobe.com,https://localhost:3000,http://localhost:3000"
}stage{
"ENABLE_CORS": "true",
"CORS_ALLOWED_ORIGINS": "https://preflight-stage.adobe.com,https://stage-preflight.example.com"
}prod{
"ENABLE_CORS": "true",
"CORS_ALLOWED_ORIGINS": "https://preflight.adobe.com"
}Replace with the real Preflight MFE origins per environment. 3. API Gateway ConfigurationLambda responses must not be overwritten by API Gateway CORS.
Action: In the AWS console or IaC for api-service, review CORS settings and remove any 4. OPTIONS Preflight HandlingBoth the OPTIONS response and the actual POST response must include correct CORS headers. API service: Requests go through
If OPTIONS is handled by API Gateway (mock/integration), it may return
5. Include
|
| # | Task | Owner |
|---|---|---|
| 1 | Update localCORSWrapper comment |
Dev |
| 2 | Add ENABLE_CORS and CORS_ALLOWED_ORIGINS to dev/stage/prod secrets |
DevOps/Config |
| 3 | Confirm Preflight MFE origins per env | Product/Frontend |
| 4 | Check API Gateway CORS config; remove * override |
DevOps |
| 5 | Add Cookie to Access-Control-Allow-Headers |
Dev |
| 6 | Verify OPTIONS for /preflight/jobs hit Lambda |
Dev/QA |
| 7 | Test credentialed flow (auth/promise → preflight/jobs) in each env | QA |
Testing
- From Preflight MFE origin, call
POST /auth/promisewithcredentials: 'include'and validatepromiseTokencookie. - Call
POST /preflight/jobswithcredentials: 'include'and confirm the cookie is sent and the request succeeds. - Check for CORS errors in the browser console.
- Verify response headers:
Access-Control-Allow-Origin: <exact-origin>,Access-Control-Allow-Credentials: true.
|
@ravverma @radhikagpt1208 - the CORS solution proposed in the comment above is on the right track but there's a blocker layer that needs to be addressed first. Fastly VCL overrides Lambda CORS headers
set resp.http.access-control-allow-origin = "*";
set resp.http.access-control-allow-headers = "authorization, x-api-key, ...";
// Access-Control-Allow-Credentials is never setEven if Two additional blockers
Required VCL change
if (req.url ~ "^/api/v1" || req.url ~ "^/api/ci" || req.url ~ "^/api/stage" || req.url ~ "^/api/prod") {
set resp.http.access-control-allow-methods = "GET, HEAD, PATCH, POST, PUT, OPTIONS, DELETE";
set resp.http.access-control-allow-headers = "authorization, x-api-key, x-import-api-key, origin, x-requested-with, content-type, accept, x-trigger-audits, x-promise-token, cookie";
if (req.http.Origin ~ "preflight.*\.adobe\.com" || req.http.Origin ~ "^https?://localhost") {
set resp.http.access-control-allow-origin = req.http.Origin;
set resp.http.access-control-allow-credentials = "true";
} else {
set resp.http.access-control-allow-origin = "*";
}
}The exact origin regex needs to be updated with the real Preflight MFE origins per environment. The Lambda-side changes ( |
Thanks for the detailed explanation, @solaris007 @ravverma. |
There was a problem hiding this comment.
Hey @radhikagpt1208,
changes looks clean , some points need to be addressed before merge
Must Fix
getCookieValue is not exported from @adobe/spacecat-shared-http-utils
The import resolves to undefined at runtime — getCookieValue is not in the package's public index. Only authWrapper, s2sAuthWrapper, enrichPathInfo, hashWithSHA256, and the four auth handlers are exported. This means the cookie is never read and every request silently falls through to IMS, making the change a no-op.
Approach: Implement getCookieValue locally in src/support/utils.js and import from there, or confirm a new version of the shared package exports it and bump the dependency.
Should Fix
JSDoc still describes the old x-promise-token header behaviour
The method-level comment and the @param for context.pathInfo.headers still reference x-promise-token. Update both to describe the promiseToken cookie source and the IMS fallback.
Minor
Check if hasText import is still needed
If the if (hasText(headerToken)) guard is fully removed, verify hasText is still used elsewhere in the file — if not, remove it from the import to keep things clean.
ravverma
left a comment
There was a problem hiding this comment.
Need to address some changes
solaris007
left a comment
There was a problem hiding this comment.
Hey @radhikagpt1208,
Strengths
- The architectural direction is correct - moving from the
x-promise-tokenheader to the HttpOnly cookie that/auth/promisealready sets. The MFE cannot programmatically read an HttpOnly cookie to forward it as a header, so the old design had a gap. This closes it by relying on the browser'scredentials: 'include'behavior. - Cookie name is consistent between writer and reader: auth-service sets
promiseToken=...atpromise.js:153, and this PR readsgetCookieValue(context, 'promiseToken'). Names match exactly. - The IMS fallback is preserved, maintaining backward compatibility for non-browser callers.
- Test coverage is solid for the three core scenarios: cookie present (skips IMS), cookie absent (falls back to IMS), and other cookies present without
promiseToken(falls back to IMS).
Issues
Critical (Must Fix)
1. Deep internal import bypasses package public API - src/controllers/preflight.js:20
import { getCookieValue } from '@adobe/spacecat-shared-http-utils/src/auth/handlers/utils/cookie.js';getCookieValue is not exported from @adobe/spacecat-shared-http-utils's public API (src/index.js). The package has "main": "src/index.js" and no exports map, so Node resolves the deep path at runtime, but this couples the service to the internal file layout of a shared library. Any refactor of that internal structure will silently break this import at deploy time. The shared library team has no visibility that an external consumer depends on this path.
Fix: Either (a) add getCookieValue to the shared package's public exports and bump the version, or (b) write a trivial local cookie parser in src/support/utils.js - it's ~5 lines.
2. Cookie value truncation for tokens containing = - @adobe/spacecat-shared-http-utils/src/auth/handlers/utils/cookie.js:40
const [cookieName, cookieValue] = cookie.trim().split('=');split('=') splits on ALL = characters, but destructuring only captures the first two elements. If the promise token contains = (common in base64 or JWT padding, e.g., promiseToken=eyJ...Q==), the value is silently truncated - eyJ...Q instead of eyJ...Q==. The truncated token will fail when used for AEM authoring operations.
This is especially relevant here because auth-service calls getPromiseToken(accessToken, true) with encryption enabled (promise.js:75), making it likely the token contains = padding.
Fix: If writing a local parser (per issue #1), use indexOf('=') + substring:
const idx = cookie.indexOf('=');
const name = cookie.substring(0, idx).trim();
const value = cookie.substring(idx + 1).trim();If keeping the shared utility, file a bug against spacecat-shared-http-utils and fix line 40 before merge.
Important (Should Fix)
3. Fastly VCL will block this flow in deployed environments
The Fastly VCL at spacecat-infrastructure/fastly/vcl/8_deliver/deliver-100-set-cors-response-headers.vcl unconditionally sets Access-Control-Allow-Origin: * for all /api/* requests, overwriting any headers Lambda returns. Per the Fetch spec, browsers reject credentials: 'include' responses when Access-Control-Allow-Origin is *. The VCL also does not set Access-Control-Allow-Credentials: true or include Cookie in Access-Control-Allow-Headers.
This means the browser will never send the cookie on the cross-origin POST. The getCookieValue() call returns null, falling through to getIMSPromiseToken(), which requires an Authorization header the MFE may not send.
This isn't a blocker for merging the Lambda-side change, but it needs a coordinating VCL update before the feature works end-to-end. Consider adding a note in the PR description about this dependency.
4. Suggestions controller still uses x-promise-token header - src/controllers/suggestions.js:1074-1077
The suggestions controller has the same x-promise-token header pattern. This PR only changes preflight.js, creating divergence between two endpoints that share identical promise-token semantics. If the intent is to move to cookies, both controllers should be updated together or as a coordinated pair.
5. No test for cookie values containing = - test/controllers/preflight.test.js
All tests use promiseToken=promiseToken123 - a value with no =. Add a test with a realistic token format (e.g., promiseToken=eyJhbGci...sig==) to verify the full value is preserved through the parsing pipeline.
Minor (Nice to Have)
6. JSDoc partially updated - src/controllers/preflight.js:89-97
The description mentions the cookie now, but @param for context.pathInfo.headers says (including cookie) which is vague. Could be more specific about the promiseToken cookie expectation.
Recommendations
- The cleanest path: add
getCookieValuetospacecat-shared-http-utils's public exports, fix thesplit('=')bug while there, publish a patch, then import from the clean public path here. Two small PRs. - If the shared package change is blocked: write a local
getCookieValue(context, name)insrc/support/utils.jswith correct=handling. - Verify in a deployed environment that the cookie path/scope set by auth-service (
Path=${apiGatewayPrefix}) covers the/preflight/jobsendpoint. - Confirm the encrypted promise token format - if it never contains
=, the truncation bug is not a blocker, but it should still be fixed in the shared utility.
Assessment
Not ready to merge. The deep internal import (Critical #1) must be resolved. The = truncation risk (Critical #2) needs verification against the actual token format and likely a fix. The Fastly CORS blocker (Important #3) needs a coordinating VCL change before the feature works end-to-end in deployed environments.
…stions controllers Replace deep internal import of getCookieValue from spacecat-shared-http-utils with a local implementation in src/support/utils.js that uses indexOf-based splitting to correctly handle cookie values containing '=' (base64/JWT tokens). Also migrate suggestions controller from x-promise-token header to cookie-based resolution for consistency with the preflight controller.
|
@ravverma, looks like you reviewed a previous version of the PR:
This was already updated in the PR. The method description now references the promiseToken cookie and the
Acknowledged. This PR is safe to merge on its own since the IMS fallback preserves backward compatibility. The coordinating VCL change is tracked in SITES-41543 and has already been updated.
Fixed. Updated
Added.
Fixed. The @param for Common comment:
|
solaris007
left a comment
There was a problem hiding this comment.
Hey @radhikagpt1208,
Thanks for the thorough updates - all six findings from our prior review are addressed.
Prior Issues - All Resolved
| # | Finding | Status |
|---|---|---|
| 1 | Critical - Deep internal import | Fixed - local getCookieValue in src/support/utils.js |
| 2 | Critical - Cookie value truncation on = |
Fixed - indexOf-based parsing preserves base64 padding |
| 3 | Important - Fastly VCL blocker | Addressed - documented in PR description, tracked in SITES-41543 |
| 4 | Important - Suggestions controller divergence | Fixed - suggestions.js now uses identical cookie pattern |
| 5 | Important - No test for = values |
Fixed - base64 test in preflight + 7 unit tests for getCookieValue |
| 6 | Minor - JSDoc | Fixed - updated in both controllers |
Strengths
- The local
getCookieValueimplementation is clean and correctly handles all edge cases (=in values, missing cookies, empty strings). Well-tested with 7 unit tests plus integration coverage. - Both controllers now use the identical pattern, keeping the codebase consistent.
- The IMS fallback guarantees zero behavioral change until the VCL dependency ships - safe to merge independently.
- Fastly VCL dependency is clearly documented in the PR description with the actual VCL snippet and SITES-41543 tracking link.
Issues
Minor (Nice to Have)
1. Orphaned JSDoc block - src/support/utils.js:622-631
The getCookieValue function was inserted between getIMSPromiseToken's JSDoc block (ending at line 631) and its function definition (line 660+). This leaves getIMSPromiseToken's @returns/@throws documentation orphaned, and the function itself undocumented. Move getCookieValue above the getIMSPromiseToken JSDoc block, or move the JSDoc back down to sit directly above its function.
Recommendations
- Follow-up: Update SITES-41543 VCL to include suggestions autofix path. The current VCL regex only covers
/api/ci/preflight/and/api/v1/preflight/. The suggestions autofix endpoint (/api/v1/sites/:siteId/opportunities/:opportunityId/suggestions/auto-fix) is not matched, so the cookie flow won't work for autofix until the VCL paths are expanded. - Follow-up: File issue to fix
getCookieValuetruncation bug in@adobe/spacecat-shared-http-utils. The JWT handler uses the shared package's version forsessionToken- samesplit('=')bug exists there.
Assessment
Ready to merge. All prior critical and important issues are resolved. The local cookie parser is correct and well-tested. Backward compatibility is preserved via IMS fallback. The minor JSDoc placement issue can be fixed in a follow-up.
## [1.351.3](v1.351.2...v1.351.3) (2026-03-17) ### Bug Fixes * use promise token cookie in preflight and suggestions controller ([#1917](#1917)) ([ef3feb3](ef3feb3))
|
🎉 This PR is included in version 1.351.3 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
JIRA: https://jira.corp.adobe.com/browse/SITES-33748
Summary
Updates the preflight controller to read the
promiseTokenfrom the browser cookie (set byPOST /auth/promise) instead of thex-promise-tokenrequest header or minting a new token via IMS on every request.Problem
For promise-based authoring types (CS, CS_CW, AMS), the preflight controller previously relied on either:
x-promise-tokenrequest header (which the MFE cannot set since the cookie isHttpOnlyand unreadable by JS), orThe
auth/promiseendpoint sets the promise token as anHttpOnlycookie viaSet-Cookie. When the MFE callsPOST /preflight/jobswithcredentials: 'include', the browser automatically sends this cookie in the Cookie request header. The controller was never reading it.Changes
src/controllers/preflight.js: Replacedx-promise-tokenheader lookup and IMS fallback withgetCookieValue(context, 'promiseToken')to read the promiseToken cookie fromcontext.pathInfo.headers.cookie(populated byenrichPathInfomiddleware). Falls back to IMS if cookie is absent.test/controllers/preflight.test.js: Updated tests to reflect cookie-based token resolution - covers cookie present, cookie absent (IMS fallback), and cookie missing promiseToken key.Dependency
Fastly VCL update required for end-to-end cookie flow
spacecat-infrastructure/fastly/vcl/8_deliver/deliver-100-set-cors-response-headers.vclunconditionally setsAccess-Control-Allow-Origin: *for all /api/* requests, overwriting any CORS headers Lambda returns.Access-Control-Allow-Origin is *.Access-Control-Allow-Credentials: trueor include Cookie in Access-Control-Allow-Headers.getCookieValue()call will return null and fall through togetIMSPromiseToken().Fastly config update for Preflight request
access-control-allow-originto the origin andaccess-control-allow-credentials: true:Follow-up: Fastly config update for Suggestions request