Gateway Branding, OAuth Code Flow, and Tower Auth
Version: 1.1.0 Date: 2026-07-15
Note: This change shipped without a
.unreleased/fragment in neo-backend; this entry is reconstructed from merge commitd9ca567(feat/tower-nexus-login) and the gateway/tower-auth implementation.
Goal: Support Nexus and Tower login flows with per-company branding, a secure OAuth-style authorization-code exchange for Tower (without exposing long-lived tokens in redirect URLs), and explicit Tower URL registration with drift detection when an operator moves Tower to a new origin.
Solution: Extend the existing gateway domain with public branding, flow-scoped OAuth/magic completion, and in-memory one-time auth codes. Add a dedicated tower-auth domain for activation and heartbeat. Document the token design space under docs/bcyip-token/.
Features:
Public company branding
GET /gateway/branding?company_id=— public route (excluded from auth middleware)- Returns
logoDataUrlfromcompanies.customization.logo(handles BSONBinarylogo payloads) - Used by Nexus login UI before the user authenticates
OAuth / Magic login with optional code flow (Tower)
GET /gateway/initiate?email=&redirect_uri=&state=— whenredirect_uriis present:- Validates redirect shape via
AuthCompletionService.assertValidRedirectShape - Resolves company by Tower origin (
findCompanyByTowerUrl) — rejects unknown origins withTowerUrlMismatch - Creates an opaque
flowIdviaGatewayFlowServicebindingredirectUri,clientState,expectedCompanyId,expectedTowerUrl
- Validates redirect shape via
- OAuth and Magic Link callbacks complete through
AuthCompletionService.complete()— can redirect back to Tower with a short-lived code instead of a raw token in the URL POST /gateway/exchange— Tower exchanges{ code, companyId }for{ token, email }- Codes are 60s TTL, single-use, stored hashed (SHA-256 of code) in
AuthCodeService
- Codes are 60s TTL, single-use, stored hashed (SHA-256 of code) in
GatewayExceptionFiltermapsTowerUrlMismatchto redirect/activate?reason=url_mismatchfor browser flows
Tower URL registration and drift
POST /tower/activate— validates activation token, registers normalized Tower origin oncompanies.towerUrl(idempotent re-activation with same URL)POST /tower/heartbeat— compares announced URL to stored URL; returnsurlChangePending: truewhen they differ (silent reject — operator must re-activate explicitly)normalizeTowerUrl()— canonical bare origin (lowercased scheme/host, no trailing slash)
Design documentation
- New
docs/bcyip-token/folder — use cases (always-on session, Tower ingestion token, platform company session, document read scope, etc.) and authorization model notes
Implementation Details:
AuthCodeService— in-memory store, periodic purge, codes never logged in plaintextGatewayFlowService— binds OAuth/magic completion to expected company + Tower URLAuthCompletionService— shared completion path for OAuth callback and magic validateTowerAuthService.resolveCompanyFromToken()— shared token validation for activate/heartbeatTowerAuthModuleno longer exportsTowerAuthService(no external consumers)
Files Modified:
src/domains/gateway/gateway.controller.ts- Branding, initiate with flow,POST /exchangesrc/domains/gateway/services/auth-code.service.ts- One-time code store (new)src/domains/gateway/services/auth-completion.service.ts- Login completion + redirect (new)src/domains/gateway/services/gateway-flow.service.ts- Flow state for Tower redirect (new)src/domains/gateway/services/oauth.service.ts- Flow-aware OAuth callbacksrc/domains/gateway/services/magic-link.service.ts- Flow-aware magic validationsrc/domains/gateway/exceptions/gateway-exception.filter.ts-TowerUrlMismatchhandlingsrc/domains/tower-auth/tower-auth.controller.ts- Activate + heartbeat routes (new)src/domains/tower-auth/tower-auth.service.ts- URL registration and drift detection (new)src/domains/tower-auth/tower-auth.module.ts- Module wiring (new)src/core/auth/auth.service.ts-findCompanyBranding(),findCompanyByTowerUrl()src/core/error/error-reference.ts-TowerUrlMismatcherror typesrc/app.module.ts- Register TowerAuthModuledocs/bcyip-token/README.mdand use-case docs (new)
Open questions (see docs/bcyip-token/sso-plan-token-design.md):
- Legacy opaque API token still issued at end of gateway flow — long-term JWT/refresh model not yet shipped