diff --git a/apps/api/.env.example b/apps/api/.env.example index 53a2b25..f62b165 100644 --- a/apps/api/.env.example +++ b/apps/api/.env.example @@ -2,3 +2,8 @@ DATABASE_URL=file:./data/app.db BETTER_AUTH_SECRET=change-me-to-a-long-random-string BETTER_AUTH_URL=http://localhost:3000 PORT=3000 + +# Comma-separated browser origins allowed for CORS + better-auth (the worker SPA). +# Add your phone's LAN origin to test on a device — no code edit needed, e.g.: +# CORS_ORIGINS=http://localhost:5173,http://192.168.1.50:5173 +CORS_ORIGINS=http://localhost:5173 diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index 435a644..11cff19 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -5,13 +5,14 @@ import { me } from './routes/me'; import { activitiesRoutes } from './routes/activities'; import { sessionsRoutes } from './routes/sessions'; import { auth } from './auth'; +import { env } from './env'; export function createApp(): Hono { const app = new Hono(); app.use( '/api/*', cors({ - origin: ['http://localhost:5173'], + origin: env.WEB_ORIGINS, allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowHeaders: ['Content-Type', 'Authorization'], exposeHeaders: ['set-auth-token'], // so the SPA can read the bearer token on sign-in diff --git a/apps/api/src/auth.ts b/apps/api/src/auth.ts index 0483a9e..edfa0a4 100644 --- a/apps/api/src/auth.ts +++ b/apps/api/src/auth.ts @@ -8,7 +8,7 @@ import { env } from './env'; export const auth = betterAuth({ secret: env.BETTER_AUTH_SECRET, baseURL: env.BETTER_AUTH_URL, - trustedOrigins: [env.BETTER_AUTH_URL, 'http://localhost:3000', 'http://localhost:5173'], + trustedOrigins: [env.BETTER_AUTH_URL, 'http://localhost:3000', ...env.WEB_ORIGINS], database: drizzleAdapter(db, { provider: 'sqlite', schema }), emailAndPassword: { enabled: true, diff --git a/apps/api/src/env.ts b/apps/api/src/env.ts index e01a7a8..882c883 100644 --- a/apps/api/src/env.ts +++ b/apps/api/src/env.ts @@ -1,6 +1,14 @@ +const webOrigins = process.env.CORS_ORIGINS?.split(',') + .map((s) => s.trim()) + .filter(Boolean); + export const env = { DATABASE_URL: process.env.DATABASE_URL ?? 'file:./data/app.db', BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET ?? 'dev-insecure-secret-change-me', BETTER_AUTH_URL: process.env.BETTER_AUTH_URL ?? 'http://localhost:3000', PORT: Number(process.env.PORT ?? 3000), + // Browser origins allowed for CORS + better-auth trustedOrigins. Set CORS_ORIGINS to a + // comma-separated list (e.g. "http://localhost:5173,http://192.168.1.50:5173") to let a + // phone on the LAN reach the API — no code edit needed. Defaults to the local Vite origin. + WEB_ORIGINS: webOrigins && webOrigins.length ? webOrigins : ['http://localhost:5173'], }; diff --git a/apps/worker/README.md b/apps/worker/README.md index cc4b113..530a3d3 100644 --- a/apps/worker/README.md +++ b/apps/worker/README.md @@ -26,7 +26,7 @@ Two processes: the API on `:3000` and the worker SPA on `:5173`. From the repo root: ```bash -yarn workspace @solelog/api db:migrate # apply migrations (creates ./.tmp DB on first run) +yarn workspace @solelog/api db:migrate # apply migrations (creates ./data/app.db on first run) yarn workspace @solelog/api db:seed # idempotent: seeds the reference activities yarn workspace @solelog/api start # Hono server on http://localhost:3000 ``` @@ -58,10 +58,14 @@ connected to the same Wi-Fi: ``` (On Windows PowerShell: `$env:VITE_API_URL='http://:3000'; yarn workspace @solelog/worker dev`.) -4. Add that origin (`http://:5173`) to the API's CORS `origin` list - (`apps/api/src/app.ts`) **and** to better-auth `trustedOrigins` (`apps/api/src/auth.ts`), then - restart the API — otherwise the cross-origin sign-in is blocked and the SPA cannot read the - `set-auth-token` response header. +4. Allow that origin on the API by setting `CORS_ORIGINS` when you start it — **no code edit**: + + ```bash + CORS_ORIGINS=http://localhost:5173,http://:5173 yarn workspace @solelog/api start + ``` + + (PowerShell: `$env:CORS_ORIGINS='http://localhost:5173,http://:5173'; yarn workspace @solelog/api start`.) + Otherwise the cross-origin sign-in is blocked and the SPA cannot read the `set-auth-token` header. No firewall punch-through, VPN, or tunnel is involved — just the LAN.