Version Packages - #121
Merged
Merged
Conversation
github-actions
Bot
force-pushed
the
changeset-release/main
branch
13 times, most recently
from
July 30, 2026 03:49
e753b5f to
6cf3569
Compare
Bccorb
self-requested a review
July 30, 2026 03:50
Bccorb
previously approved these changes
Jul 30, 2026
github-actions
Bot
force-pushed
the
changeset-release/main
branch
from
July 30, 2026 04:28
6cf3569 to
e5d4ebe
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated.
Releases
@seamless-auth/core@0.11.0
Minor Changes
f032a64: Move the remaining guard decisions into core, and fully specify a cookie's lifetime in the response contract.
checkOrigin,authenticateCookie, andauthorizeRolesjoincheckProxyIdentity: each takes the request facts a guard needs and returns aGuardRejectionor nothing, so an adapter reads headers and cookies and writes a response but decides nothing. The Express origin guard,requireAuth, andrequireRolenow call them, which is a straight substitution with no behavior change.SeamlessAuthUsercomes from@seamless-auth/types, which already defines it, rather than being declared again here. It is re-exported from@seamless-auth/coreand@seamless-auth/expressunder the same name, so nothing changes for adopters. The re-export is type-only, so it is erased at compile time and neitherzodnor the schema barrel enters the runtime module graph.SetCookieCommandandClearCookieCommandnow carry anexpiresalongsidemaxAgeSeconds. Previously the command specified only a max age, and two adapters could satisfy it while emitting different headers: Express sent bothExpiresandMax-Age, and a second adapter sent onlyMax-Age, which older clients treat as a session cookie. Specifying both means every adapter emits the same header for the same instruction. Clearing carries the epoch for the same reason.No change to what
@seamless-auth/expresssends. Theexpiresit now receives explicitly is the value it was already deriving.519a1b0: Give the auth API's contract values one home in
@seamless-auth/core.The external-delivery header and the service-token identity were written out at each call site: the
x-seamless-auth-delivery-mode: "external"header in three core handlers, the fixed issuer and audience in three places across the adapter, and thedev-mainkey id fallback in three more. Each is defined byseamless-auth-api, so changing one is coordinated cross-repo work, and finding every copy was part of the job.New exports:
AUTH_DELIVERY_MODE_HEADER,EXTERNAL_DELIVERY_MODE,EXTERNAL_DELIVERY_HEADERS,SERVICE_TOKEN_ISSUER,SERVICE_TOKEN_AUDIENCE,DEV_JWKS_KID,EXTERNAL_DELIVERY_TOKEN_SUBJECT, andbuildExternalDeliveryAuthorization, which mints theAuthorizationvalue for an external-delivery request.No behavior change. The minted tokens carry the same header and claims as before, confirmed by decoding them. A new test asserts each contract value literally, so a change to one breaks a named test rather than surfacing as an upstream rejection at runtime.
The service-token issuer and audience are fixed by the API and are not the adopter's configured audience, which applies to user tokens. That is now stated where the constants are defined rather than in a comment at one of the call sites.
Part of epic: strengthen core so new framework adapters reuse more #72.
3c5c1c5: Move the response contract into
@seamless-auth/core.Turning a handler result into an HTTP response was adapter knowledge: sign each session cookie, mirror the set attributes when clearing, clear before setting, write cookies before the body, send an upstream failure body untouched but wrap a code as
{ error, details }. The Express adapter carried it, copied across nine handlers plus the cookie middleware, and every new adapter would have had to reproduce all of it correctly.Core now owns it. New exports:
applyResult(result, adapter, opts)applies a result to a response.applyCookies(result, adapter, opts)applies only the cookie instructions, for middleware that continues the request instead of answering it.ResponseAdapter, the three things an adapter must provide:setCookie,clearCookie, andsend.signSessionCookie,resolveCookieSameSite, and theCookieSameSite,CookieSecurityOptions,SetCookieCommand,ClearCookieCommand,SessionCookie, andAppliableResulttypes.Cookie signing moves to core with them, because the cookie format is core's: an adapter that signed differently would mint sessions this package cannot read back.
The Express adapter drops 291 lines of source and 5.5KB of bundle, and
@seamless-auth/expressno longer carries its own cookie module. Nothing is removed from its public surface.CookieSameSiteis now re-exported from core rather than declared locally, soSeamlessAuthServerOptionsis unchanged for adopters.Responses are unchanged with one exception, noted below. Status, body, and every
Set-Cookieheader were captured on both revisions across eleven scenarios covering session set and clear, secure and insecure policy, a custom cookie domain, coded and passthrough failures, an empty failure body, and success bodies. All are byte-identical, includingHttpOnly,Secure,SameSite,Domain,Path, andMax-Age.Empty responses are now consistent about their content type. A route whose upstream returned success with no body previously sent
Content-Type: application/jsonwith a zero-length body, because the handler called the framework's JSON method withundefined. It now sends no content type, matching the routes that already ended the response instead.Content-Length: 0is unchanged either way, and a client reading the body sees nothing in both cases, since parsing an empty body fails regardless of the content type. Anything asserting on the content type of an empty response needs updating.Part of epic: strengthen core so new framework adapters reuse more #72.
d17896b: Move the passthrough proxy into
@seamless-auth/core, and fix repeated query parameters being joined.The 33 organizations, step-up, TOTP, users, and admin passthrough routes existed only inside the Express adapter, with no core equivalent, so a new adapter would have had to rebuild both the upstream call and the session gate that guards it. New exports:
proxyRequest({ authServerUrl, path, method, authorization, serviceAuthorization, forwardedClientIp, query, body })forwards a request and returns the upstream status and body unchanged.checkProxyIdentity({ subject, cookies, identity, ...cookieNames })is the pure session gate, returning the rejection to send orundefinedto proceed.buildQueryStringandbuildUpstreamUrlreplace three separate querystring builders that had drifted apart.Fix: a repeated query parameter reached the auth API joined into a single comma-separated value on the admin and internal-metrics routes.
GET /admin/auth-events?type=login&type=logoutwas forwarded astype=login,logout, and the API'sAuthEventQuerySchemaacceptstypeas an array, so the joined value matched no event type and the filter silently returned the wrong set. Array parameters are now forwarded as repeated parameters on every route. Nested objects are dropped rather than stringified, so a query like?filter[from]=xcan no longer reach the API asfilter=[object Object].The Express adapter drops 38 lines,
createServer.tsdrops from 705 to 667 lines, and the proxy handler is now a gate check, a call, and a response.9735f83: Export the remaining handlers and verifiers from the package root.
The admin, session, internal-metrics, and system-config handlers, along with
verifySignedAuthResponseandverifyRefreshCookie, were reachable only through a subpath import. Everything else came from the root, so which import an adapter needed depended on which handler it wanted. 27 names are now available from@seamless-auth/coreas well.Purely additive. Nothing is removed or renamed, the
./handlers/*subpaths keep working, and a test asserts that a subpath import and a root import resolve to the same function rather than two copies.The README's public API overview is rewritten to match, grouped by what an adapter author is looking for, and now covers the response contract, proxy, delivery, and contract-value exports added earlier in this epic that it had never listed.
Part of epic: strengthen core so new framework adapters reuse more #72.
17c5487: Move auth message delivery into
@seamless-auth/core.deliverAuthMessage,applyExternalDelivery, andstripDeliverylived in the Express adapter but had no framework coupling: their only imports were the messaging types, which already came from core. Every future adapter would have had to reimplement or copy them. They now sit beside the messaging contract in core and are exported from the package root, and the adapter imports them.applyExternalDelivery,deliverAuthMessage, andstripDeliveryare new named exports of@seamless-auth/core. Nothing is removed from@seamless-auth/express: the three helpers were internal to it and were never exported.The warning logged when external delivery is requested but the auth API returns no delivery payload is now prefixed
[SeamlessAuth]rather than[SEAMLESS-AUTH-EXPRESS], matching the rest of core. The text is unchanged. Anything matching on that prefix in log processing needs updating.Part of epic: strengthen core so new framework adapters reuse more #72.
8e03099: Split the handler result
errorfield intoerrorCodeanderrorBody.BREAKING for direct consumers of the handler result types. Released as a minor because these packages are pre-1.0, where a minor is the breaking bump. The details are below.
errormeant two different things depending on which handler produced it. On 12 sites it held the auth API's whole failure body, forwarded to the caller unchanged. On 8 sites it held a short code that the adapter wrapped as{ error }. The declared types could not describe either honestly, andFinishLoginResultdeclarederror?: stringwhile assigning the whole body. Nothing in the type told an adapter which rendering applied, which is the first thing a second adapter has to get right.Failures are now reported through
ResultFailure, exported from@seamless-auth/core:errorCode?: stringis a code this package chose. Adapters render it as{ error, details }.errorBody?: unknownis the auth API's own failure body. Adapters forward it unchanged.BREAKING for code that reads
result.erroroff a handler imported from@seamless-auth/core/handlers/*. ReaderrorBodyfor the auth-flow handlers (login, finishLogin, register, finishRegister, requestOtp, verifyLoginOtp, requestMagicLink, pollMagicLinkConfirmation, verifyMagicLink, switchOrganization, and the OAuth handlers) anderrorCodefor the rest (me, ensureCookies, admin, sessions, internalMetrics, systemConfig). The OAuth handlers return a union, so"error" in resultbecomes"errorBody" in result.The HTTP responses are unchanged, so no adopter application, SDK, or dashboard needs to change. This was verified rather than assumed: the adapter's failure responses were captured on both revisions across 6 upstream body shapes and 3 route kinds and compared, and all 18 are byte-identical. They were also run through
extractMessageandgetOAuthErrorCodefromseamless-auth-react, with identical results, including the OAuthcodethat has to stay at the top level of the body.A new
failureWireFormattest in@seamless-auth/expresslocks these shapes so later refactors cannot move them silently.Patch Changes
c52b5d1: Take
@seamless-auth/types0.4.0.That release adds a
z.inferalias for each of the 43 exported schemas thatlacked one, so the naming convention now covers all 123. It is additive: no
existing export changed name or shape, and nothing core imports from the package
moved. Core keeps importing the same type names it always has.
Adopters who resolve
@seamless-auth/typesthrough core pick up the new aliasesand can name a response body without adding a direct
zoddependency to callz.inferthemselves.9e04625: Take the remaining duplicated types from
@seamless-auth/types.SeamlessUseris now an alias of the types package'sMeUser, and the eight messaging wire shapes (MessagingChannel,DeliveryResult,EmailMessage,SmsMessage,SendOtpEmailInput,SendOtpSmsInput,SendMagicLinkEmailInput,AuthDeliveryInstruction) are re-exported rather than declared again. Each was field for field identical to a definition that already existed upstream, which is the drift@seamless-auth/typesexists to prevent.What stays declared here is what genuinely belongs to this package: the transport interfaces, which carry provider implementations, and the adopter-facing configuration (
EmailTransport,SmsTransport,AuthMessageOverrideContext,AuthMessageOverrides,AuthMessagingHandlers,SeamlessAuthMessagingOptions).No public API change and no runtime cost. Every name is still exported from
@seamless-auth/coreand both adapters, the re-exports are type-only so they are erased at compile time, and the built output still imports only@seamless-auth/types/role/matchingat runtime, so neitherzodnor the schema barrel enters the module graph.Closes Take the remaining duplicated types from @seamless-auth/types #133.
82fc15a: Return a code instead of an empty response when the auth API fails with no body.
The auth-flow routes forward the API's failure body rather than interpreting it, and an empty body left nothing to forward: the caller got a bare 4xx with no content, and
seamless-auth-reactfell back to its per-call generic message with no way to tell an expired session from a rate limit from an upstream outage. The proxy routes already handled this, so the two families disagreed on the one case where the caller had least to go on.An empty failure body now becomes
{ "error": "upstream_error" }. A body that is present is still forwarded untouched, including the top-levelcodethe React SDK reads to tell OAuth failures apart.New exports:
readPassthroughFailureandUPSTREAM_ERROR_CODE.Closes A 4xx with an empty upstream body returns an empty response on passthrough routes #125.
d7d408d: Extract the copy-pasted session helpers, with a typed
UpstreamSessionResponse.Seven handlers repeated the same block: verify the signed access token, check it describes the same subject as the response body, read the
sidclaim, then build the access and refresh cookie payloads. Seven copies of a security-relevant check is seven places to get an early return wrong, and the upstream fields were read untyped, so a rename on the API side surfaced as an undefined cookie field at runtime rather than a type error.issueSessionCookiesnow does the whole thing in one call,verifyUpstreamSessionis available for the one flow that verifies without issuing a session (login, which issues only the pre-auth cookie), andUpstreamSessionResponsestates what the auth API returns when it issues a session. The handlers drop 220 lines.One behavior change: the access cookie issued by
POST /webAuthn/register/finishnow carriesorganizationId: nullwhere it previously omitted the key. Every other flow already included it, and the refresh path writes it on every reissue, so that session disagreed with itself after a single refresh. It is now consistent from the start.New exports:
issueSessionCookies,verifyUpstreamSession,UpstreamSessionResponse,VerifiedUpstreamSession,IssueSessionCookiesOptions.Closes Extract the copy-pasted session helpers in core, with a typed UpstreamSessionResponse #136.
744418b: Let adopters route this package's diagnostics somewhere other than
console.Core wrote to
consoledirectly, so an adopter could not capture, level, or silence its output. An adapter with its own logger still leaked core's lines toconsole, which meant one request could produce output in two places.setSeamlessLogger(logger)accepts anything withwarnanderror, which a platform logger already satisfies, andsetSeamlessLogger()with no argument goes back toconsole. Nothing changes for callers that do not set one.The logger is process-wide, not per-request, so it changes where core's diagnostics go rather than attaching request context to them. Core logs two things (a failed signature verification and a misconfigured external-delivery setup), and neither depends on request context. Threading a per-request logger through every handler would be a larger change.
New exports:
setSeamlessLogger,getSeamlessLogger,SeamlessLogger.Closes Injectable logger in core #137.
583271a: Preserve the upstream error detail on the admin, session, internal-metrics, and system-config proxy routes.
These handlers read the failure code out of the upstream body's
errorkey and fell back to a constant (admin_request_failed,session_request_failed,internal_request_failed,failed_to_fetch_roles,failed_to_fetch_config,failed_to_update_config) whenever that key was missing. The auth API answers a validation failure with a Zod body shaped{ name, message }and noerrorkey, so every validation failure collapsed to the constant. APATCH /admin/users/:idrejected for itsphonefield came back as{"error":"admin_request_failed"}, with nothing naming the field, and the detail was not recoverable from the API's request logs either.The handler results and the Express responses now carry the upstream detail.
erroris the upstreamerrorstring when present, otherwise the upstreammessagestring, and only then the constant fallback for an empty or non-JSON-object body. A new optionaldetailsfield carries the parsed upstream body whenever it holds more than the derivederrorstring, so a Zod body reaches the caller intact.This is additive: a response that already carried an upstream
errorcode is unchanged and gains nodetailskey. Callers that switch on the constant fallback for validation failures should readdetails(or the now-descriptiveerror) instead.a5e3070: Take the scoped-role matching from
@seamless-auth/typesinstead of maintaining a second copy.packages/core/src/scopedRoles.tsreimplemented the same logic as the auth API'ssrc/lib/scopedRoles.ts. They agreed, but nothing kept them in step, and they are the two places that decide whether a request is authorized. A divergence there means the API and an adopter's server disagree about who can do what. Both sides now takeroleGrantsAccessandhasScopedRolefrom@seamless-auth/types, so there is one definition.No public API change.
@seamless-auth/coreexports the same two names, and@seamless-auth/expressalready re-exported them from core. Behavior is unchanged: the replacement was checked against the deleted implementation over every granted/required pair built from a 594-string role corpus (352,836 pairs), including wildcard grants, unscoped grants, write-implies-read, empty and whitespace-padded roles, and non-array or non-stringgrantedRoles, with no differences.Core imports the
@seamless-auth/types/role/matchingentry point, which carries the matching helpers with no dependencies of its own, so this adds nothing to what an adopter loads at runtime:zodstays out of the module graph and coldimportof@seamless-auth/coreis unchanged.@seamless-auth/express@0.11.0
Minor Changes
8e03099: Split the handler result
errorfield intoerrorCodeanderrorBody.BREAKING for direct consumers of the handler result types. Released as a minor because these packages are pre-1.0, where a minor is the breaking bump. The details are below.
errormeant two different things depending on which handler produced it. On 12 sites it held the auth API's whole failure body, forwarded to the caller unchanged. On 8 sites it held a short code that the adapter wrapped as{ error }. The declared types could not describe either honestly, andFinishLoginResultdeclarederror?: stringwhile assigning the whole body. Nothing in the type told an adapter which rendering applied, which is the first thing a second adapter has to get right.Failures are now reported through
ResultFailure, exported from@seamless-auth/core:errorCode?: stringis a code this package chose. Adapters render it as{ error, details }.errorBody?: unknownis the auth API's own failure body. Adapters forward it unchanged.BREAKING for code that reads
result.erroroff a handler imported from@seamless-auth/core/handlers/*. ReaderrorBodyfor the auth-flow handlers (login, finishLogin, register, finishRegister, requestOtp, verifyLoginOtp, requestMagicLink, pollMagicLinkConfirmation, verifyMagicLink, switchOrganization, and the OAuth handlers) anderrorCodefor the rest (me, ensureCookies, admin, sessions, internalMetrics, systemConfig). The OAuth handlers return a union, so"error" in resultbecomes"errorBody" in result.The HTTP responses are unchanged, so no adopter application, SDK, or dashboard needs to change. This was verified rather than assumed: the adapter's failure responses were captured on both revisions across 6 upstream body shapes and 3 route kinds and compared, and all 18 are byte-identical. They were also run through
extractMessageandgetOAuthErrorCodefromseamless-auth-react, with identical results, including the OAuthcodethat has to stay at the top level of the body.A new
failureWireFormattest in@seamless-auth/expresslocks these shapes so later refactors cannot move them silently.Patch Changes
f032a64: Move the remaining guard decisions into core, and fully specify a cookie's lifetime in the response contract.
checkOrigin,authenticateCookie, andauthorizeRolesjoincheckProxyIdentity: each takes the request facts a guard needs and returns aGuardRejectionor nothing, so an adapter reads headers and cookies and writes a response but decides nothing. The Express origin guard,requireAuth, andrequireRolenow call them, which is a straight substitution with no behavior change.SeamlessAuthUsercomes from@seamless-auth/types, which already defines it, rather than being declared again here. It is re-exported from@seamless-auth/coreand@seamless-auth/expressunder the same name, so nothing changes for adopters. The re-export is type-only, so it is erased at compile time and neitherzodnor the schema barrel enters the runtime module graph.SetCookieCommandandClearCookieCommandnow carry anexpiresalongsidemaxAgeSeconds. Previously the command specified only a max age, and two adapters could satisfy it while emitting different headers: Express sent bothExpiresandMax-Age, and a second adapter sent onlyMax-Age, which older clients treat as a session cookie. Specifying both means every adapter emits the same header for the same instruction. Clearing carries the epoch for the same reason.No change to what
@seamless-auth/expresssends. Theexpiresit now receives explicitly is the value it was already deriving.519a1b0: Give the auth API's contract values one home in
@seamless-auth/core.The external-delivery header and the service-token identity were written out at each call site: the
x-seamless-auth-delivery-mode: "external"header in three core handlers, the fixed issuer and audience in three places across the adapter, and thedev-mainkey id fallback in three more. Each is defined byseamless-auth-api, so changing one is coordinated cross-repo work, and finding every copy was part of the job.New exports:
AUTH_DELIVERY_MODE_HEADER,EXTERNAL_DELIVERY_MODE,EXTERNAL_DELIVERY_HEADERS,SERVICE_TOKEN_ISSUER,SERVICE_TOKEN_AUDIENCE,DEV_JWKS_KID,EXTERNAL_DELIVERY_TOKEN_SUBJECT, andbuildExternalDeliveryAuthorization, which mints theAuthorizationvalue for an external-delivery request.No behavior change. The minted tokens carry the same header and claims as before, confirmed by decoding them. A new test asserts each contract value literally, so a change to one breaks a named test rather than surfacing as an upstream rejection at runtime.
The service-token issuer and audience are fixed by the API and are not the adopter's configured audience, which applies to user tokens. That is now stated where the constants are defined rather than in a comment at one of the call sites.
Part of epic: strengthen core so new framework adapters reuse more #72.
3c5c1c5: Move the response contract into
@seamless-auth/core.Turning a handler result into an HTTP response was adapter knowledge: sign each session cookie, mirror the set attributes when clearing, clear before setting, write cookies before the body, send an upstream failure body untouched but wrap a code as
{ error, details }. The Express adapter carried it, copied across nine handlers plus the cookie middleware, and every new adapter would have had to reproduce all of it correctly.Core now owns it. New exports:
applyResult(result, adapter, opts)applies a result to a response.applyCookies(result, adapter, opts)applies only the cookie instructions, for middleware that continues the request instead of answering it.ResponseAdapter, the three things an adapter must provide:setCookie,clearCookie, andsend.signSessionCookie,resolveCookieSameSite, and theCookieSameSite,CookieSecurityOptions,SetCookieCommand,ClearCookieCommand,SessionCookie, andAppliableResulttypes.Cookie signing moves to core with them, because the cookie format is core's: an adapter that signed differently would mint sessions this package cannot read back.
The Express adapter drops 291 lines of source and 5.5KB of bundle, and
@seamless-auth/expressno longer carries its own cookie module. Nothing is removed from its public surface.CookieSameSiteis now re-exported from core rather than declared locally, soSeamlessAuthServerOptionsis unchanged for adopters.Responses are unchanged with one exception, noted below. Status, body, and every
Set-Cookieheader were captured on both revisions across eleven scenarios covering session set and clear, secure and insecure policy, a custom cookie domain, coded and passthrough failures, an empty failure body, and success bodies. All are byte-identical, includingHttpOnly,Secure,SameSite,Domain,Path, andMax-Age.Empty responses are now consistent about their content type. A route whose upstream returned success with no body previously sent
Content-Type: application/jsonwith a zero-length body, because the handler called the framework's JSON method withundefined. It now sends no content type, matching the routes that already ended the response instead.Content-Length: 0is unchanged either way, and a client reading the body sees nothing in both cases, since parsing an empty body fails regardless of the content type. Anything asserting on the content type of an empty response needs updating.Part of epic: strengthen core so new framework adapters reuse more #72.
d17896b: Move the passthrough proxy into
@seamless-auth/core, and fix repeated query parameters being joined.The 33 organizations, step-up, TOTP, users, and admin passthrough routes existed only inside the Express adapter, with no core equivalent, so a new adapter would have had to rebuild both the upstream call and the session gate that guards it. New exports:
proxyRequest({ authServerUrl, path, method, authorization, serviceAuthorization, forwardedClientIp, query, body })forwards a request and returns the upstream status and body unchanged.checkProxyIdentity({ subject, cookies, identity, ...cookieNames })is the pure session gate, returning the rejection to send orundefinedto proceed.buildQueryStringandbuildUpstreamUrlreplace three separate querystring builders that had drifted apart.Fix: a repeated query parameter reached the auth API joined into a single comma-separated value on the admin and internal-metrics routes.
GET /admin/auth-events?type=login&type=logoutwas forwarded astype=login,logout, and the API'sAuthEventQuerySchemaacceptstypeas an array, so the joined value matched no event type and the filter silently returned the wrong set. Array parameters are now forwarded as repeated parameters on every route. Nested objects are dropped rather than stringified, so a query like?filter[from]=xcan no longer reach the API asfilter=[object Object].The Express adapter drops 38 lines,
createServer.tsdrops from 705 to 667 lines, and the proxy handler is now a gate check, a call, and a response.0b384e2: Take the messaging types from
@seamless-auth/coreinstead of redeclaring them.packages/express/src/messaging.tswas byte-identical topackages/core/src/authMessaging.ts, so every messaging type was declared twice and the two copies could drift without anything failing. The express copy is deleted and the re-exports now point at core, matching the pattern already used forSeamlessUser,hasScopedRole, androleGrantsAccess.No public surface change.
@seamless-auth/expressexports the same type names from its package root, and the deleted module was never reachable on its own because the package declares only a root export. The types the adapter and the core now share are one declaration rather than two structurally identical ones.1d0c45c: Encode the provider id on the OAuth provider admin routes before forwarding it upstream.
PATCHandDELETE /system-config/oauth-providers/:idinterpolatedreq.params.idstraight into the upstream URL. Every other proxied route param goes throughencodeURIComponent, and these two were missed when that pass landed. A param carrying?,#, or an encoded/was decoded into the URL raw, so it could append or override upstream query parameters or reshape the upstream path.An id of
abc?admin=1was forwarded as/system-config/oauth-providers/abc?admin=1, turning attacker-controlled input into an upstream query parameter. It is now forwarded as a single encoded path segment, which upstream rejects as an unknown id.The routes require an authenticated access session, so this is not reachable anonymously.
17c5487: Move auth message delivery into
@seamless-auth/core.deliverAuthMessage,applyExternalDelivery, andstripDeliverylived in the Express adapter but had no framework coupling: their only imports were the messaging types, which already came from core. Every future adapter would have had to reimplement or copy them. They now sit beside the messaging contract in core and are exported from the package root, and the adapter imports them.applyExternalDelivery,deliverAuthMessage, andstripDeliveryare new named exports of@seamless-auth/core. Nothing is removed from@seamless-auth/express: the three helpers were internal to it and were never exported.The warning logged when external delivery is requested but the auth API returns no delivery payload is now prefixed
[SeamlessAuth]rather than[SEAMLESS-AUTH-EXPRESS], matching the rest of core. The text is unchanged. Anything matching on that prefix in log processing needs updating.Part of epic: strengthen core so new framework adapters reuse more #72.
583271a: Preserve the upstream error detail on the admin, session, internal-metrics, and system-config proxy routes.
These handlers read the failure code out of the upstream body's
errorkey and fell back to a constant (admin_request_failed,session_request_failed,internal_request_failed,failed_to_fetch_roles,failed_to_fetch_config,failed_to_update_config) whenever that key was missing. The auth API answers a validation failure with a Zod body shaped{ name, message }and noerrorkey, so every validation failure collapsed to the constant. APATCH /admin/users/:idrejected for itsphonefield came back as{"error":"admin_request_failed"}, with nothing naming the field, and the detail was not recoverable from the API's request logs either.The handler results and the Express responses now carry the upstream detail.
erroris the upstreamerrorstring when present, otherwise the upstreammessagestring, and only then the constant fallback for an empty or non-JSON-object body. A new optionaldetailsfield carries the parsed upstream body whenever it holds more than the derivederrorstring, so a Zod body reaches the caller intact.This is additive: a response that already carried an upstream
errorcode is unchanged and gains nodetailskey. Callers that switch on the constant fallback for validation failures should readdetails(or the now-descriptiveerror) instead.Updated dependencies [f032a64]
Updated dependencies [c52b5d1]
Updated dependencies [519a1b0]
Updated dependencies [3c5c1c5]
Updated dependencies [d17896b]
Updated dependencies [9735f83]
Updated dependencies [9e04625]
Updated dependencies [82fc15a]
Updated dependencies [d7d408d]
Updated dependencies [744418b]
Updated dependencies [17c5487]
Updated dependencies [583271a]
Updated dependencies [a5e3070]
Updated dependencies [8e03099]
@seamless-auth/fastify@0.1.0
Minor Changes
f032a64: Add
@seamless-auth/fastify, a Fastify adapter serving the same routes as the Express adapter.Register it under a prefix and it serves the auth flows, the passthrough proxy routes, and the admin, session, metrics, and system-config routes, managing the session cookies they depend on.
requireAuthandrequireRoleare exported aspreHandlerhooks for an adopter's own routes, alongsidegetSeamlessUser.Both adapters emit identical responses. A parity suite runs the same requests through each against the same mocked auth API and asserts the status, body, and every
Set-Cookieheader match, so the two cannot drift.createSeamlessConsoleProxyhas no Fastify equivalent yet. It proxies the admin console's static assets and is separate from the auth routes.Patch Changes