You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The core/adapter boundary is clean (zero Express imports in core), but core is under-weighted: it owns per-endpoint upstream calls yet not the delivery, cookie-format, service-token, proxy, or contract logic every adapter needs. Strengthening core lets us spin off new framework adapters (Fastify, Hono, Koa, Next.js) with far less reimplementation. This is a tracking epic; sub-items land as separate small PRs.
Direction
Core owns the request/response contract. Adapters supply primitives (read cookie, set cookie, send) plus a route table, and core defines what applying a result means. Sub-items 1 and 6 below both reduce to the same gap: core returns a result but does not define how to apply one, so every adapter reimplements that knowledge.
Ordering
Re-measured against the current code and ordered by lines a second adapter would not have to rewrite:
Item
Adapter lines saved
Risk
proxyRequest (1)
~285 in createServer.ts, 33 routes
Medium
Move deliverAuthMessage (2)
198
Very low
Cookie apply + result render (6)
~135 across 9 files
Low
Contract constants (4)
~10
Very low
Core surface tidy (5)
0 (DX, not reuse)
Trivial
Session helpers (3)
0
Medium
Injectable logger (7)
0
Low
Sub-items
Step 0: unify the failure contract.result.error meant the whole upstream body on 12 sites and a short code on 8, and FinishLoginResult declared error?: string while assigning the body. No single applyResult was possible until a failure had one meaning. Split into errorCode and errorBody on an exported ResultFailure. Landed in refactor(core,express)!: split result error into errorCode and errorBody #124.
PR 1: move message delivery into core. Relocate packages/express/src/internal/deliverAuthMessage.ts beside packages/core/src/authMessaging.ts. Its imports are types only, so it is a lift and shift with no framework coupling. The duplicate packages/express/src/messaging.ts is already gone (refactor(express): take messaging types from core #122), and the types are re-exported from core.
PR 2: applyResult and adapter primitives. Core defines how a result becomes a response; adapters supply setCookie and send. Absorbs the cookieSigner block copied across 9 files, the set-cookie loop, handle(res, result) in 3 files, and routeParam.
PR 3: core.proxyRequest({ authServerUrl, path, method, authorization, forwardedClientIp, query, body }) -> { status, body }. The 33 organizations/step-up/totp/users passthrough routes exist only in proxyWithIdentity (packages/express/src/createServer.ts) with no core equivalent. Fold in the querystring builder duplicated 3x (createServer.ts:113, core/src/handlers/admin.ts:29, core/src/handlers/internalMetrics.ts:24).
PR 4: centralize the adapter contract. Route path strings, the x-seamless-auth-delivery-mode: "external" literal, and the external-delivery token identity (buildInternalServiceAuthorization), so cross-repo changes with seamless-auth-api are a single edit.
PR 5: tidy core's public surface. Export admin, sessions, internalMetrics, systemConfig, verifySignedAuthResponse, and verifyRefreshCookie from packages/core/src/index.ts (currently subpath-only).
Corrections to the original list
Recorded so the deltas are not rediscovered later:
The messaging sub-item was half done by refactor(express): take messaging types from core #122, which deleted the byte-identical packages/express/src/messaging.ts. Only the deliverAuthMessage relocation remains, now PR 1.
The public-surface sub-item listed bootstrapAdminInvite, which was removed entirely in fa1c736. Dropped from the list.
The contract sub-item placed the x-seamless-auth-delivery-mode literal at register.ts:41 "and 3 more" in the adapter. It is 3 sites and all three are in core (register.ts, requestOtpHandler.ts, requestMagicLinkHandler.ts), not the adapter. Smaller than described.
The logger sub-item described core as writing to console directly. There is one call site, in verifySignedAuthResponse.ts, not a pervasive pattern.
Moved out of this epic
Both are worthwhile, but neither saves a new adapter any lines, so they should not compete for slots here:
Extract the two copy-pasted core session helpers (verifySignedAuthResponse plus sub-check, and the session-cookie payload build, each repeated ~7x) and introduce a typed UpstreamSessionResponse, since upstream fields are read as any today. Core hygiene.
Injectable logger in core, which matters for serverless and edge adapters. DX.
Summary (epic)
The core/adapter boundary is clean (zero Express imports in core), but core is under-weighted: it owns per-endpoint upstream calls yet not the delivery, cookie-format, service-token, proxy, or contract logic every adapter needs. Strengthening core lets us spin off new framework adapters (Fastify, Hono, Koa, Next.js) with far less reimplementation. This is a tracking epic; sub-items land as separate small PRs.
Direction
Core owns the request/response contract. Adapters supply primitives (read cookie, set cookie, send) plus a route table, and core defines what applying a result means. Sub-items 1 and 6 below both reduce to the same gap: core returns a result but does not define how to apply one, so every adapter reimplements that knowledge.
Ordering
Re-measured against the current code and ordered by lines a second adapter would not have to rewrite:
proxyRequest(1)createServer.ts, 33 routesdeliverAuthMessage(2)Sub-items
result.errormeant the whole upstream body on 12 sites and a short code on 8, andFinishLoginResultdeclarederror?: stringwhile assigning the body. No singleapplyResultwas possible until a failure had one meaning. Split intoerrorCodeanderrorBodyon an exportedResultFailure. Landed in refactor(core,express)!: split result error into errorCode and errorBody #124.packages/express/src/internal/deliverAuthMessage.tsbesidepackages/core/src/authMessaging.ts. Its imports are types only, so it is a lift and shift with no framework coupling. The duplicatepackages/express/src/messaging.tsis already gone (refactor(express): take messaging types from core #122), and the types are re-exported from core.applyResultand adapter primitives. Core defines how a result becomes a response; adapters supplysetCookieandsend. Absorbs thecookieSignerblock copied across 9 files, the set-cookie loop,handle(res, result)in 3 files, androuteParam.core.proxyRequest({ authServerUrl, path, method, authorization, forwardedClientIp, query, body }) -> { status, body }. The 33 organizations/step-up/totp/users passthrough routes exist only inproxyWithIdentity(packages/express/src/createServer.ts) with no core equivalent. Fold in the querystring builder duplicated 3x (createServer.ts:113,core/src/handlers/admin.ts:29,core/src/handlers/internalMetrics.ts:24).x-seamless-auth-delivery-mode: "external"literal, and the external-delivery token identity (buildInternalServiceAuthorization), so cross-repo changes withseamless-auth-apiare a single edit.admin,sessions,internalMetrics,systemConfig,verifySignedAuthResponse, andverifyRefreshCookiefrompackages/core/src/index.ts(currently subpath-only).Corrections to the original list
Recorded so the deltas are not rediscovered later:
packages/express/src/messaging.ts. Only thedeliverAuthMessagerelocation remains, now PR 1.bootstrapAdminInvite, which was removed entirely infa1c736. Dropped from the list.x-seamless-auth-delivery-modeliteral atregister.ts:41"and 3 more" in the adapter. It is 3 sites and all three are in core (register.ts,requestOtpHandler.ts,requestMagicLinkHandler.ts), not the adapter. Smaller than described.consoledirectly. There is one call site, inverifySignedAuthResponse.ts, not a pervasive pattern.Moved out of this epic
Both are worthwhile, but neither saves a new adapter any lines, so they should not compete for slots here:
verifySignedAuthResponseplus sub-check, and the session-cookie payload build, each repeated ~7x) and introduce a typedUpstreamSessionResponse, since upstream fields are read asanytoday. Core hygiene.Related