Summary
Postline was fully open — every page and /postline API route. This gates the whole app behind a single-user account, making it deployable.
Flow
- First run:
/loginoffers "Create the workspace account" →RegisterFirstUserActioncreates the one account and returns a token. The endpoint returns 403 forever after, so strangers can't register on a deployed instance. - Sign in:
POST /login(frameworkLoginAction, declared inroutes/api.tssince the framework's copy lives in the feature-gated dashboard bundle this app doesn't load). The token is stored in theauth-tokencookie. - Sign out: button on Settings clears the cookie.
Enforcement
- Pages: stx-serve
protectedPathsserver-redirects queue/timeline/analytics/accounts/settings to/login; a client-side layout guard covers/(a/prefix rule would also match/loginand loop). The public blog stays open. - API:
authmiddleware on every/postlineroute except the LinkedIn/Instagram/Threads OAuth redirect endpoints (hit by providers without a bearer). - Bridge: the frontend's
/apiproxy injectsAuthorization: Bearer <cookie>when absent, so all existing client fetches work unchanged — no Authorization plumbing in page scripts. The API remains the security boundary.
Fixes along the way
userstable migration (was missing entirely)@stacksjs/authbuild emitted declarations underdist/src/leaving nodist/index.d.ts(TS7016 for any consumer) — dtsrootnow matches the other packages; auth added to the framework-types build list
Verification (browser + curl, end to end)
- Unauthenticated
/and/queue→ redirect to/login; API unauthenticated → 401 - First-run register → lands authenticated, provider toggles load through the cookie→bearer bridge
- Second register attempt → 403 "workspace already has an account"
- Sign in with correct password → token pair; wrong password → 401; sign out → gates re-engage
pickierclean,tsc --noEmitexit 0- Test account removed afterward — the DB ships in first-run state