Repository navigation
One container, and real authentication - #6
Merged
Merged
Conversation
Two changes you asked for. SINGLE CONTAINER Deletes the three-service split. apps/server is now the only entrypoint: one Bun process that serves the Hono API in-process, supervises the Next PWA as a loopback child and proxies to it, and runs the background loop. One Dockerfile, one railway.json, one deploy, one log stream. Next ships its own server with no supported way to mount it inside another Bun server, hence the child process. Only the supervisor binds a public port. The worker loop is single-instance by design — two copies would both claim the same pending deletion job — so railway.json pins numReplicas to 1 and says why. AUTHENTICATION Replaces the shared bearer token as the human path. - argon2id password hashing via Bun.password - sessions stored as a SHA-256 hash of the cookie, never the value, so a database leak yields no usable sessions - httpOnly + SameSite=Lax cookies, Secure in production - identical response for unknown user and wrong password, so the endpoint cannot enumerate accounts - lockout after repeated failures - workspace scoping derived from membership, not from client headers - registration provisions an organization and first workspace, because a user without a workspace cannot do anything here The service token survives for internal machine callers only, and now needs explicit scope headers and a constant-time comparison. Also wires the approval buttons, which were decorative: approve, edit, skip and do-not-contact now call the API, and a policy denial shows the gate that blocked it rather than failing silently. 289 tests, typecheck clean, PWA builds. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The container started against an unmigrated database and every write failed with 'no such table: workspaces'. Serving against a schema that is not there produces confusing 500s on every route, so the supervisor now applies pending migrations before anything listens, and exits rather than starting if they fail. Boot-time migration is safe here specifically because the deployment is one container pinned to one replica; the usual objection is replicas racing, which cannot happen at numReplicas 1. RUN_MIGRATIONS=false opts out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Two changes, both requested.
One container
Deletes the three-service split.
apps/serveris now the only entrypoint — a single Bun process that:/api/v1and/health/*One Dockerfile, one
railway.json, one deploy, one log stream, one set of environment variables.Next ships its own server with no supported way to mount it inside another Bun server, hence the child process. Only the supervisor binds a public port; the child is loopback-only.
The worker loop is single-instance by design — two copies would both claim the same pending deletion job — so
railway.jsonpinsnumReplicas: 1and records why. Scaling horizontally later means splitting the loop back out behind a lock.Removed:
docker/{api,web,worker}.Dockerfile,apps/{web,worker}/railway.json,apps/api/src/index.ts,apps/worker/src/index.ts.Real authentication
The API previously accepted one shared bearer token. That is now the machine path only; humans get sessions.
Bun.password— no hand-rolled cryptohttpOnly,SameSite=Lax,Securein productionThe service token now additionally requires explicit scope headers and is compared in constant time.
The approval buttons were decorative
Zero
onClickhandlers. Approve, edit, skip and do-not-contact now call the API. A policy denial surfaces the gate that blocked it instead of failing silently, and do-not-contact confirms first, because a suppression tombstone deliberately outlives the prospect record.CI
The docker matrix is replaced by one
imagejob that boots the container and asserts the API is healthy, the Next child answers through the proxy, unauthenticated calls are 401, and register/login/me works end to end. Docker is not installed on the authoring machine, so this job is the only thing that has ever run these images.Verification
289 tests (24 new for auth), typecheck clean,
next buildclean, prettier clean.🤖 Generated with Claude Code