Skip to content

feat: [SDK-5237] never sign the subscription-id routes and do not gate them on a token - #1506

Merged
sherwinski merged 3 commits into
sherwin/sdk-5238from
sherwin/sdk-5237
Sep 22, 2026
Merged

sherwinski merged 3 commits into
sherwin/sdk-5238from
sherwin/sdk-5237

Conversation

@sherwinski

@sherwinski sherwinski commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Description

1 Line Summary

PATCH subscriptions/{id} and DELETE subscriptions/{id} are never signed, and their operations do not wait for a token.

Details

Last pull request of the M3 stack for Identity Verification for Web SDK. Stacked on SDK-5238.

Re-scope. The ticket asked to forward the token per the server answer. The SDK-5220 matrix says the server rejects a bearer on PATCH subscriptions/{id} (401 auth-2 on the legacy Turbine path, the request proceeds without a header) and ignores a bearer on DELETE subscriptions/{id} (handlerWithoutAuth). So the answer is: do not sign either route, and do not hold either operation for a token. iOS made the same call (SDK-5137, item 13). Android attaches the bearer anyway; web does not follow it here.

src/core/requests/api.ts

  • updateSubscriptionById and deleteSubscriptionById take Omit<RequestMetadata, 'jwt'>, so a caller cannot pass a token by accident. The doc comments say why.

src/core/operations/UpdateSubscriptionOperation.ts, DeleteSubscriptionOperation.ts

  • _requiresJwt returns false. M2 added this hook on Operation for endpoints that do not check auth; these are the first 2 uses. The dispatch gate in OperationRepo._hasValidJwtIfRequired lets them through without a stored token.

src/core/operationRepo/OperationRepo.ts

  • The anonymous suppression at enqueue and the start-up purge exempt operations with _requiresJwt === false. Both exist because "an anonymous operation can never dispatch: the gate needs a token". For these 2 operations the gate does not need a token, so the reason does not hold. Without the exemption, an opt-out or a subscription removal after logout would be dropped with a "call login first" warning, and the server would keep the subscription.
  • _handleFailUnauthorized returns false for an operation with _requiresJwt === false, so the caller drops it. A 401 on a request that sent no bearer says nothing about the stored token. Without this guard, such a 401 would invalidate a valid token, ask the app for a new one, and retry unsigned in a loop (review finding on SDK-5236).

src/core/executors/SubscriptionOperationExecutor.ts

  • _updateSubscription and _deleteSubscription log an error on a 401 and return _FailNoretry. The SDK never signs these routes, so a 401 means the server contract changed (for example update_subscription_block_unauthorized turned on). The operation is dropped either way; the log makes the loss visible instead of silent.

Bundle size. page.es6.js grows by 91 B gzipped to 44.58 kB (2 _requiresJwt getters, 3 gate exemptions, the 401 log). The limit moves to the measured size.

Systems Affected

  • WebSDK
  • Backend
  • Dashboard

Validation

Tests

Info

  • src/core/executors/executorsIv.test.ts: IV active with a stored token, update and delete subscription send no Authorization header and hit /apps/{appId}/subscriptions/{id}; a 401 on either stays _FailNoretry and logs an error.
  • src/core/operations/Operation.test.ts: _requiresJwt is true for every operation except update and delete subscription.
  • src/core/operationRepo/OperationRepo.test.ts: under IV, an anonymous DeleteSubscriptionOperation is queued at enqueue with no warning and passes the dispatch gate; the start-up purge keeps it and still drops the other anonymous operations; a 401 on an operation that needs no JWT is dropped, keeps the stored token, and fires no userJwtInvalidated event.

vp check, vp test (764 tests), and vp run build:prod pass.

Checklist

  • All the automated tests pass or I explained why that is not possible
  • I have personally tested this on my machine or explained why that is not possible
  • I have included test coverage for these changes or explained why they are not needed

Programming Checklist
Interfaces:

  • Don't use default export
  • New interfaces are in model files

Functions:

  • Don't use default export
  • All function signatures have return types
  • Helpers should not access any data but rather be given the data to operate on.

Typescript:

  • No Typescript warnings
  • Avoid silencing null/undefined warnings with the exclamation point

Other:

  • Iteration: refrain from using elem of array syntax. Prefer forEach or use map
  • Avoid using global OneSignal accessor for context if possible. Instead, we can pass it to function/constructor so that we don't call OneSignal.context

Screenshots

Info

Not needed. No user-visible change.

Checklist

  • I have included screenshots/recordings of the intended results or explained why they are not needed

Related Tickets

… token

The server rejects a bearer on PATCH subscriptions/{id} with 401 and
accepts the request without one. It ignores a bearer on DELETE
subscriptions/{id}. updateSubscriptionById and deleteSubscriptionById
now refuse a jwt at the type level.

UpdateSubscriptionOperation and DeleteSubscriptionOperation set
_requiresJwt to false, so the dispatch gate does not hold them for a
token. The anonymous suppression at enqueue and the start-up purge
exempt them too; the gate lets them through, so a pending remove or
opt-out after logout still goes out.

The page bundle grows by 13 B gzipped to 44.57 kB.
…no token

A 401 on a request that sent no bearer says nothing about the stored
token. The unauthorized handler now drops such operations instead of
invalidating the token and re-queueing them, which would loop on every
new token the app supplies. page.es6.js grows by 2 B gzipped.
The SDK never signs PATCH or DELETE subscriptions/{id}. A 401 there
means the server contract changed, and the operation is dropped, so
the loss must be visible. page.es6.js grows by 51 B gzipped to 44.58 kB.
@sherwinski
sherwinski merged commit 87288bc into feat/identity-verification Sep 22, 2026
2 checks passed
@sherwinski
sherwinski deleted the sherwin/sdk-5237 branch September 22, 2026 23:37
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.

2 participants