b994fc7c95e4a67f06c6b7abd4681edb194ac200
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2db5aaac14 |
Fase 6: deploy readiness (PWA + invitation rate-limit + JWT rotation)
Closes the deferrals from Fase 4 that gated a public deploy of the MVP.
PWA install
- Custom SW at apps/web/src/sw.ts (named sw.ts, not service-worker.ts, so
SvelteKit doesn't intercept it and block Workbox imports).
- vite.config.ts switched to injectManifest strategy with precache of the
built shell; no runtime caching of Supabase (offline writes stay on the
existing $lib/sync pending_ops queue).
- Root +layout.svelte onMount() calls registerSW({ immediate: true }) from
virtual:pwa-register — the plugin does not auto-register.
- Web manifest with 192/512/512-maskable icons + iOS meta tags
(apple-mobile-web-app-capable, apple-touch-icon, etc.) in app.html.
- Placeholder icons generated via ImageMagick; replace before public launch
(noted in apps/web/static/icons/README.md).
Kong rate-limit
- /rest/v1/collective_invitations POST: 20/hour per Authorization header.
Anti-spam on invitation creation is the only rate-limit that survived —
auth rate-limits were dropped during execution (see plan §6.3): Keycloak
already provides bruteForceProtected=true on the realm, and stacking a
Kong limit on /auth/v1/token throttled legitimate PKCE code exchanges
during E2E.
- Added 'rate-limiting' to KONG_PLUGINS in docker-compose.dev.yml.
- Discovered: strip_path=true on a full-table path sends "/" upstream and
PostgREST rejects POST to root with PGRST117. Route carries a
request-transformer plugin that rewrites the URI back.
JWT rotation + prod template
- infra/scripts/rotate-jwt.sh signs anon + service_role JWTs with a fresh
48-byte secret via openssl. Prints three values for the operator to
paste; does not touch files.
- Dev secret rotated as a dry-run. Updated both .env (local, gitignored)
and apps/web/.env.development. Existing sessions are invalidated — all
users must re-login once.
- .env.production.example committed with placeholders + rotation notes.
Lighthouse + RLS audit tooling
- `just lighthouse` boots the prod build + Supabase stack and runs
lighthouse CLI against PWA/A11y/Best-Practices.
- `just rls-audit` runs infra/scripts/rls-audit.sh which signs an Eva JWT
(non-member) and GETs 8 REST endpoints — all must return []. Complements
the Vitest rls-audit.test.ts suite.
Tests
- 236 green: 34 pgTAP + 140 Vitest integration + 15 unit + 46 Playwright.
- 1 rate-limit test gated behind RUN_RATE_LIMIT_TESTS=1 (Kong counters are
shared state); run via `just test-rate-limit` which force-recreates Kong
first to reset counters.
- 3 skipped in `just test-all` (2 Realtime presence upstream bug, 1 gated
rate-limit).
Deliberately out of scope: push notifications, CI ephemeral Docker, final
production icons, runtime caching of Supabase (SyncBanner + pending_ops
already covers offline).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
5a63e5fc4a |
feat(dev): expose full auth stack on LAN IP for on-device preview
The dev stack previously mixed two hostnames (`localhost` in some places,
`keycloak` in others). That only worked because the laptop's /etc/hosts
maps `keycloak` to 127.0.0.1. Phones, tablets, or a second laptop had no
such mapping, so the OAuth flow broke the moment you tried to log in from
any non-laptop device.
Switched the SPA, Kong, GoTrue, and Keycloak client to all agree on a
single external host — the laptop's LAN IP (192.168.1.167) — so the full
Keycloak → GoTrue → Supabase → SvelteKit round-trip works from any device
on the same Wi-Fi.
Changes
.env (root, ungitignored): PUBLIC_SUPABASE_URL / PUBLIC_KEYCLOAK_URL /
PUBLIC_APP_URL now use the LAN IP (updated locally; tracked via
apps/web/.env.development below).
apps/web/.env.development: mirror the LAN IP so `$env/static/public`
resolves the same on laptop and phone.
apps/web/vite.config.ts: `server.host: true` — bind Vite to 0.0.0.0.
apps/web/playwright.config.ts: `baseURL: PUBLIC_APP_URL ?? localhost`
so E2E uses the same origin the stack is configured with.
apps/web/tests/fixtures/login.ts:
- wait for the collective button in the sidebar to render before
returning (LAN-IP latency surfaced a pre-existing race in
`handleCreate` where Enter fired before `$currentCollective`
hydrated, silently no-op'ing).
- derive expected app origin from PUBLIC_APP_URL.
apps/web/tests/e2e/items.test.ts: scope D-04 delete assertion to the
`[role="listitem"]` locator — with undoQueue enabled the toast text
"Deleted <name>" also matches `getByText(itemName)` and shadowed the
original check.
infra/docker-compose.dev.yml:
- GOTRUE_EXTERNAL_KEYCLOAK_URL and REDIRECT_URI now read from
PUBLIC_KEYCLOAK_URL / PUBLIC_SUPABASE_URL so they follow the same
host as the rest of the stack.
- NEW: GOTRUE_URI_ALLOW_LIST with `/auth/callback` on both the LAN
IP and localhost. Without an explicit allow-list GoTrue rejected
`redirect_to=.../auth/callback` and fell back to SITE_URL root,
stranding `?code=` at `/` and re-triggering signIn → infinite loop.
keycloak/realm-export.json: `colectivo-web` client gains LAN-IP
redirectUris and webOrigins (alongside the existing localhost
entries). Persisted + live-applied via admin API.
.gitignore: add .claude/ (per-project scheduler runtime state).
Verification
just test-all → 224 passed, 4 skipped:
34 pgTAP
140 Vitest integration + 2 skipped (Realtime presence, upstream bug)
11 Vitest unit
39 Playwright + 2 skipped (mobile-swipe touch, WebKit-only)
Phone sanity check: open http://192.168.1.167:5173 on a LAN device,
log in as a seed user, full RLS-respecting session.
Caveats
LAN IP (192.168.1.167) is DHCP-assigned — if it rotates, rerun the
Keycloak admin API update (realm-export.json needs a new entry) and
update the three PUBLIC_* URLs. Consider a Tailscale magic-DNS name
as a stable replacement for the prod-deploy sprint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
f396897cb5 |
test: full test suite (Vitest + pgTAP + Playwright) + TDD plan restructure
Add a 4-layer test stack covering RLS, triggers, and UI flows for Fases 0–2a, then restructure every plan file so future fases start with tests and end with a verification gate. Test suite - packages/test-utils: Vitest integration tests signing HS256 JWTs via jose so each test acts as a specific seed user (createClientAs + createAdminClient) - supabase/tests: pgTAP for accept_invitation(), item_frequency trigger, and promote-on-admin-leave; each file self-installs pgtap extension - apps/web/tests: Playwright E2E with live Keycloak login per test (storageState caching doesn't rehydrate Supabase's session state reliably) - just test-all chains the three suites; test-db forwards POSTGRES_PASSWORD as PGPASSWORD with ON_ERROR_STOP=1 so failures abort the chain Supabase auth gotcha - PostgREST queries inside onAuthStateChange deadlock on GoTrue's navigator.locks auth lock (getAccessToken → getSession → initializePromise waits on the same lock that's held during event dispatch). Fix is two defenses: a pass-through lock in $lib/supabase, and a setTimeout(0) defer in root +layout.svelte to push loadUserCollectives out of the callback's microtask chain. Either alone is insufficient; both together unblock the Playwright suite. Env key rotation - apps/web/.env.development had a stale demo anon key signed with a different secret than root .env; Vite inlined that into the browser bundle so Kong (which uses the root .env value) rejected every request with 401. Aligned the two files and added a memory entry to flag this for the next rotation. Plan restructure (TDD) - Every fase now opens with X.0 Tests primero and closes with X.Z Verificación final. Completed fases (0, 1, 2a) show the pattern retroactively with the tests that currently cover them; pending fases (2b, 3, 4) list the tests to write before implementation. Docs - CLAUDE.md status line reports 54 + 12 + 15 = 81 green tests, adds gotchas #11 (auth-lock deadlock) and #12 (no storageState caching) - README.md adds a TDD methodology section and the test-all command - .gitignore excludes Playwright's generated reports and auth state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
b41dced1ba |
feat: add setup.sh for idempotent local dev environment setup
Automates the full first-time setup in one command (also safe to re-run): - Checks docker/pnpm; installs just if missing - Adds 127.0.0.1 keycloak to /etc/hosts (sudo) - Generates .env with dev JWT tokens computed from the dev secret - Fixes supabase/migrations permissions if root-owned - Starts the Docker stack and waits for postgres/kong/keycloak - Applies SQL migrations with tracking table (won't re-apply) - Seeds the database (no-op if already seeded) - Compiles Paraglide i18n output Also adds `just setup` recipe and fixes the dev anon key in .env.development. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
7d91705fc2 |
feat(fase-1): auth, collective management, and DB migrations
- SQL migrations: users, collectives, collective_members, collective_invitations,
full RLS policies, is_active_member/is_member/is_admin helpers,
accept_invitation SECURITY DEFINER function, admin auto-promote trigger,
avatars storage bucket
- Auth refactor: replace keycloak-js with Supabase OAuth (GoTrue OIDC proxy,
Option B). signInWithOAuth({ provider: 'keycloak' }) + PKCE flow
- GoTrue config: GOTRUE_EXTERNAL_KEYCLOAK_URL + GOTRUE_EXTERNAL_KEYCLOAK_REDIRECT_URI
added to docker-compose.dev.yml; Keycloak realm updated with GoTrue callback URI
- New routes: /auth/callback, /invitation/[token], /(app)/collective/manage
- Functional onboarding: create collective or join via invite link
- Full settings page: display name (debounced), avatar (initials/emoji/upload),
language switcher, Keycloak account console link
- Components: Avatar.svelte (initials/emoji/upload with fallback),
ImageCropper.svelte (cropperjs, 1:1 crop, lazy-loaded)
- i18n: added all Fase 1 message keys (en + es); renamed 'delete' → 'action_delete'
(JS reserved word); fixed plugin-m-function-matcher major-version URL format
- Database types: manually seeded packages/types/src/database.ts from migrations
- .env.development: committed public dev defaults for SvelteKit type checking
- CLAUDE.md: documented /etc/hosts requirement for keycloak hostname
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|