Skip to content

feat: v2 for create preflight job to support session token#1887

Closed
ravverma wants to merge 3 commits intomainfrom
preflight-job-v2
Closed

feat: v2 for create preflight job to support session token#1887
ravverma wants to merge 3 commits intomainfrom
preflight-job-v2

Conversation

@ravverma
Copy link
Contributor

@ravverma ravverma commented Mar 2, 2026

Summary

Add POST /preflight/jobs-v2 endpoint (createPreflightJobV2) that reads the promise token from the x-promise-token request header instead of creating one from the Authorization header via IMS. Mark the existing POST /preflight/jobs as deprecated.

What changed

  • src/controllers/preflight.js

    • Added createPreflightJobV2 — validates x-promise-token header upfront (early exit), forwards it to SQS for promise-based authoring types (CS, CS_CW, AMS). Rest of the logic is identical to v1.
    • Marked createPreflightJob as @deprecated.
  • src/routes/index.js — Added POST /preflight/jobs-v2 route.

  • docs/openapi/preflight-api.yaml

    • Marked POST /preflight/jobs as deprecated: true with deprecation notice.
    • Added full OpenAPI spec for POST /preflight/jobs-v2 with required x-promise-token header parameter.
  • docs/openapi/api.yaml — Added path reference for /preflight/jobs-v2.

  • test/controllers/preflight.test.js — Added 13 unit tests for createPreflightJobV2 covering all code paths (missing/empty header, all authoring types, site lookup, auth flag, CI/prod URL, error handling, SQS rollback).

  • test/routes/index.test.js — Added mock and route key for v2.

Key differences from v1

v1 POST /preflight/jobs (deprecated) v2 POST /preflight/jobs-v2
Promise token source Created from Authorization header via IMS Read directly from x-promise-token header
Header requirement Authorization (only for promise-based sites) x-promise-token (required for all requests)
Failure mode Late — after site lookup and HEAD probe Early exit — first thing checked
IMS dependency Yes (getIMSPromiseToken) No

Test plan

  • Unit tests pass (38 passing)
  • Route tests pass
  • OpenAPI spec lints clean (npm run docs:lint)
  • OpenAPI docs build (npm run docs:build)

@codecov
Copy link

codecov bot commented Mar 2, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link

github-actions bot commented Mar 3, 2026

This PR will trigger a minor release when merged.

$ref: './project-api.yaml#/project-sites-by-name'
/preflight/jobs:
$ref: './preflight-api.yaml#/preflight-jobs'
/preflight/jobs-v2:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This versioning feels really off from industry standards. The suggestion from the api first group was that the existing api should be backwards compatible. Why do we need to introduce a new api here. If it is mandatory then I would consider /v2/preflight/jobs. This is a larger question for @solaris007 and how spacecat apis should be versioned.

If the presence of x-promise-token is the header, the could we not take that path in the code and keep the /preflight/jobs endpoint as it is.

Copy link
Member

@solaris007 solaris007 Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strongly agree. The backward-compatible approach is the right call here - details in the PR review.

- api_key: [ ]
- ims_key: [ ]
summary: Submit a new preflight async job
summary: Submit a new preflight async job (deprecated)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we really want to deprecate this or make the api backwards compatible.

Comment on lines +205 to +208
const promiseToken = context.request?.headers?.get?.('x-promise-token');
if (!hasText(promiseToken)) {
return badRequest('x-promise-token header is required and must be a non-empty string');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just look for this logic in createPreflightJob above?

Copy link
Member

@solaris007 solaris007 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @ravverma,

Thanks for tackling the promise token handoff - moving away from the server-side IMS exchange toward a client-supplied token is a reasonable direction. However, I have concerns about the approach taken here. Agreeing with @bhellema's comments - details below.

API Versioning

The /preflight/jobs-v2 URL suffix is a non-standard versioning pattern. Industry conventions for API versioning (when actually warranted) are path-prefix (/v2/preflight/jobs), header-based (Accept: application/vnd.adobe.v2+json), or query-param. The suffix approach doesn't scale (-v3? -v4?) and breaks URL semantics.

More importantly, versioning isn't warranted here at all. The request/response contract is identical between v1 and v2 - same request body, same 202 response shape. The only difference is an implementation detail: where the promise token comes from. This is a textbook case for backward-compatible evolution, not a new endpoint.

Code Duplication

createPreflightJobV2 is ~107 lines nearly identical to createPreflightJob. The actual behavioral delta is ~7 lines (header read + token assignment). Everything else - validateRequestData, site lookup, HEAD probe, AsyncJob.create, SQS message construction, rollback on SQS failure, poll URL formatting - is character-for-character identical. This means every future bug fix or enhancement to the preflight flow must be applied in two places.

The 13 new tests similarly mirror the existing v1 suite, re-testing the same shared business logic through a different entry point.

Token Shape Mismatch

The v1 flow passes the full IMS response object to SQS:

// v1: from IMS
{ promise_token, expires_in, token_type }

The v2 flow constructs only:

// v2: manual wrap
{ promise_token: promiseToken }

If the downstream audit worker reads expires_in or token_type, v2 will silently break it. This needs to be verified before merging.

Encryption Mismatch

In v1, ImsPromiseClient.getPromiseToken() can optionally encrypt the promise token before it goes to SQS (controlled by AUTOFIX_CRYPT_SECRET / AUTOFIX_CRYPT_SALT). In v2, the raw header value goes directly into the SQS message without encryption.

If the audit worker environment has encryption enabled, the consumer will attempt to decrypt the raw (unencrypted) token and fail. This likely breaks v2 entirely in environments where encryption is configured.

Token Required for Non-Promise-Based Sites

v2 requires x-promise-token for ALL requests, but only uses it for promise-based authoring types (CS, CS_CW, AMS). For non-promise-based sites, the token is validated as present but discarded. This forces callers who don't need a promise token to supply one anyway - a confusing API contract that violates least-privilege.

Recommendation

Adopt the backward-compatible approach @bhellema suggested. Detect the x-promise-token header in the existing handler and branch:

if (promiseBasedTypes.includes(site.getAuthoringType())) {
  const headerToken = context.request?.headers?.get?.('x-promise-token');
  if (hasText(headerToken)) {
    promiseTokenResponse = { promise_token: headerToken };
  } else {
    promiseTokenResponse = await getIMSPromiseToken(context);
  }
}

This eliminates the new endpoint, the deprecation, ~100 lines of duplicated code, the duplicated OpenAPI spec, and most of the duplicated tests. Net change should be well under 30 lines, plus 2-3 new test cases for the header-present path.

Before any approach is merged, the encryption and token shape questions above need answers from whoever owns the audit worker consumer side.

@ravverma
Copy link
Contributor Author

ravverma commented Mar 5, 2026

@solaris007 @bhellema Thanks, indeed it was lame approach :)
Closing this one and will share a new PR which will have backward compatible changes for preflight and autofix , because both use promise token

@bhellema
Copy link
Contributor

bhellema commented Mar 5, 2026

@solaris007 @bhellema Thanks, indeed it was lame approach :) Closing this one and will share a new PR which will have backward compatible changes for preflight and autofix , because both use promise token

Please also take another iteration on #1863

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants