Skip to content

fix(express): warn when external delivery returns no delivery payload#91

Merged
Bccorb merged 1 commit into
mainfrom
fix/warn-on-missing-external-delivery
Jul 20, 2026
Merged

fix(express): warn when external delivery returns no delivery payload#91
Bccorb merged 1 commit into
mainfrom
fix/warn-on-missing-external-delivery

Conversation

@Bccorb

@Bccorb Bccorb commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Why

seamless-auth-api is tightening its external delivery gate (fells-code/seamless-auth-api#92): a validated internal service token is required in every environment, not only production. This adapter already sends the right token with the right claims (buildAuthorization.ts:14-26 mints iss=seamless-portal-api, aud=seamless-auth, and a subject), so the supported path keeps working and no code change is required for correctness.

What the change does expose is a configuration failure that this adapter currently swallows.

The problem

All four auth-message routes had the same shape:

if (result.body && typeof result.body === "object" && "delivery" in result.body) {
  await deliverAuthMessage(opts.messaging, (result.body as { delivery?: any }).delivery);
  return res.status(result.status).json(stripDelivery(result.body as any));
}

return res.status(result.status).json(result.body);

If delivery is absent, the adapter falls through, returns 200 with the upstream's "If an account exists, a login link has been sent", sends no email or SMS, and logs nothing. Until now a serviceSecret that did not match the auth server's API_SERVICE_TOKEN was invisible outside production, because the API returned the payload anyway. After the API change it is a real, silent non-delivery.

What changed

  • The duplicated branch is now one helper, applyExternalDelivery in internal/deliverAuthMessage.ts. It delivers and strips as before, and logs a warning when messaging is configured but no payload came back.
  • requestOtp, requestMagicLink, register, and bootstrapAdminInvite call the helper. Response bodies, status codes, and the .end() behavior in register are unchanged.
  • No warning is emitted when messaging is not configured, since the adapter never asks for external delivery in that case. There is a test for that.
  • The messaging section of the express README now documents serviceSecret as a prerequisite for auth-message delivery, and says what a mismatch looks like.

The four endpoints routed through this helper always include delivery when the API accepts the request as externally delivered, so the warning should not fire spuriously. (/registration/phone on the API can legitimately return no delivery when the phone is unchanged and already verified, but this adapter does not route that endpoint.)

Tests

Two added to packages/express/tests/messagingDelivery.test.js: a missing payload warns and sends nothing, and a no-messaging config does not warn.

  • pnpm build passes for both packages
  • pnpm test passes, 60 core tests and 78 express tests

One caveat: on one intermediate run, tests/oauthRoutes.test.js reported a single failure. It passed in isolation and on eight subsequent full runs, and five runs on main were clean. I did not capture the message and could not reproduce it. That suite is untouched by this change, and the express test script rebuilds dist before running jest, so a rebuild race is the likeliest explanation. Flagging it rather than leaving it out.

Scope

@seamless-auth/express only. @seamless-auth/core is unchanged.

One thing I noticed but did not touch: core/src/authFetch.ts:22 falls back to options.authorization when serviceAuthorization is absent, so a direct core consumer setting externalDelivery: true without serviceAuthorization sends the end-user's access token in the service-token slot. Those claims will not validate upstream. The express adapter never hits this, and it predates this change, so it seems worth its own PR rather than folding in here.

The four auth-message routes each fell through to a plain success response
when the upstream body had no delivery field. A serviceSecret that does not
match the auth server's API_SERVICE_TOKEN therefore produced a
successful-looking response with no message sent and nothing logged.

The shared delivery branch is now a single applyExternalDelivery helper that
logs a warning when messaging is configured but no payload came back. Response
bodies and status codes are unchanged.

Also documents serviceSecret as a prerequisite for auth-message delivery in the
messaging section of the express README.
@Bccorb
Bccorb force-pushed the fix/warn-on-missing-external-delivery branch from fe41a7d to 471d2d1 Compare July 20, 2026 02:40
@Bccorb
Bccorb merged commit 2b71440 into main Jul 20, 2026
1 of 2 checks passed
@Bccorb
Bccorb deleted the fix/warn-on-missing-external-delivery branch July 20, 2026 02:46
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.

1 participant