Skip to content

feat(magic-link): forward a redirect target to the auth API - #151

Merged
Bccorb merged 1 commit into
mainfrom
feat/magic-link-redirect-passthrough
Aug 31, 2026
Merged

feat(magic-link): forward a redirect target to the auth API#151
Bccorb merged 1 commit into
mainfrom
feat/magic-link-redirect-passthrough

Conversation

@Bccorb

@Bccorb Bccorb commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Follow-up to fells-code/seamless-auth-api#240, which added an optional redirectUri to GET /magic-link so a tenant's web and mobile clients can each receive a link that opens in the right place.

Without this the feature is unreachable from a browser. The adapters called ${authServerUrl}/magic-link with no query, so only a backend calling the API directly could use it, and a browser or mobile client, which is the case the API change exists for, could not.

What changed

  • RequestMagicLinkInput gains an optional redirectUri, put on the URL with the existing buildUpstreamUrl helper rather than string-concatenated.
  • Both adapters read it from the body of their own POST /magic-link and pass it through.

Additive. Omit it and the upstream URL is byte for byte what it was, so no adopter has to change anything. Against an auth API that predates the parameter it is simply ignored and the link keeps the tenant-wide destination.

Forwarded, not validated

The adapters do not check the value. The API validates it against the configured origins and answers 400, and an allowlist kept in two places is one that eventually disagrees with itself. A refusal is passed back through the existing readPassthroughFailure path, which one of the core tests asserts.

A value that is not a string is dropped rather than coerced, so an object or a number cannot become a query parameter meaning something the caller never sent.

Tests

Both adapters, because each reads its own request body and only the forwarding underneath them is shared:

  • core (4): default URL unchanged, target forwarded and encoded, a target carrying its own query survives encoding, an upstream refusal reaches the caller.
  • express (4): body to query, no body at all, empty body, non-string ignored.
  • fastify (2): added to the parity suite, asserting both adapters build the same upstream URL, which is what the file exists to check.

Verified non-vacuous by reverting each side in turn: dropping the core forwarding fails 2, dropping the fastify read fails the parity case.

Verification

pnpm build and pnpm test at the workspace root, per AGENTS.md:

core:     26 suites, 231 tests passed
express:  28 suites, 154 tests passed
fastify:   2 suites,  48 tests passed

Changeset marks all three packages minor.

Order

The API side (fells-code/seamless-auth-api#240) should land first. This is harmless before it, since an API that does not know the parameter ignores it, but the feature only works once both are out.

seamless-auth-api now takes an optional redirectUri on GET /magic-link deciding
where the emailed link lands. The adapters called that route with no query, so
the feature was reachable only by a backend calling the API directly and not by
a browser or mobile client, which was most of the point.

RequestMagicLinkInput gains an optional redirectUri, built onto the URL with the
existing buildUpstreamUrl helper. Both adapters read it from the body of their
own POST /magic-link. Omit it and the upstream URL is byte for byte what it was.

Forwarded rather than checked. The API validates against the configured origins
and answers 400, and an allowlist kept in two places is one that eventually
disagrees with itself. A non-string is dropped rather than coerced so it cannot
become a query parameter meaning something the caller did not send.

Covered on both adapters. Only the forwarding underneath is shared, since each
reads its own request body, so the fastify side goes in the parity suite next to
the express one.
@Bccorb
Bccorb merged commit fb5c039 into main Aug 31, 2026
2 checks passed
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