Repository navigation
feat(demo): public demo account, local install and security fixes - #3
Merged
Merged
Conversation
- Add ADR-037: the demo runs on four-points.stackbp.es with its database, demo users see the whole app and can generate schedules but not touch users, roles or passwords, and the repository goes public after it - TODO: rewrite the demo entry for the same site, with the solver limits, the daily reset including schedules and the local first-admin setup - ROADMAP and SESSION: the original is archived; going public waits for the demo Why: the site is only a showcase, and without a demo nobody reaching the repository can try the app.
- DECISIONS: ADR-038, one restricted demo admin entered with a button, an is_demo flag instead of the demo-admin role, daily reset on the first demo login, local install with MySQL in Docker; ADR-037 points to it - TODO: demo entry rewritten as seven steps; demo-admin entry now retires it - ROADMAP: phase 5 describes the agreed demo - SESSION: next step starts with the is_demo flag Why: the owner chose the simplest demo for visitors and to maintain, and a local install with full admin rights for whoever wants to see everything.
- users.is_demo column (idempotent migration, logged in INDEX.md) - login and refresh put a demo claim in the token; authenticateToken exposes it as req.user.isDemo (the old demo-admin role counts as demo) - demoRestriction blocks every multipart upload for the demo account; new denyDemo and denyDemoWrites guards on the routes it must not touch: user management, register, own profile/password/avatar, departments, the demo activity log, backoffice categories and the scheduling configuration (config, shifts, rules, employees, contracts) - tests for the guards, the token claim and a source check that keeps every guard on its route Why: the demo account is a real admin so the app works for it as for anyone, and only what could break the app, touch other accounts or survive the daily reset is blocked (ADR-038). Guards live on the routes, so they do not depend on how the URL is written.
- INDEX.md: 20261005_add_is_demo_to_users.sql applied on Aiven Why: the column has to exist before the code that reads it reaches preview.
- demoRestriction: the blocked-attempt log no longer throws when req.body is undefined (Express 5 leaves it so for DELETE and uploads) - authenticateToken: demoRestriction runs after the token try/catch - tests for blocking with no body and for an upload before multer Why: logging a blocked DELETE or upload threw, so the request got a 500, or a 401 "Token inválido" when it happened inside the token check. The request was never executed, but the answer was wrong.
- POST /api/auth/demo: only with DEMO_MODE=true (404 otherwise), own rate limit per IP, signs in the active is_demo account with demo tokens - password login rejects the demo account, even with the right password - scripts/create-demo-user.ts (pnpm demo:user, demo:user:aiven): creates or converts the `demo` user, admin role, is_demo, random unknown password - login page: "Prueba nuestra demo" button and hint, shown only with NEXT_PUBLIC_DEMO_MODE=true; useAuth.loginDemo shares the sign-in flow - DEMO_MODE and NEXT_PUBLIC_DEMO_MODE in both .env.example - tests for the route, its tokens and the password login Why: visitors try the whole app with one click (ADR-038); with no public password there is nothing to change or leak, and the entry exists only where the demo is switched on.
- demoWriteLimiter: 60 writes per 15 minutes per IP for the demo account, run by authenticateToken after demoRestriction; reads are not counted - demoGenerateLimiter on POST /months/:id/generate: 5 per hour per IP and 30 per hour across all demo visitors - other accounts are never limited by them; tests for both limits Why: the demo account is shared, so it is limited by IP; the solver loads OR-Tools on one 512 MB Render instance and runs one request at a time, so it also gets a total cap (ADR-038).
- demo_reset_log table (idempotent migration, logged in INDEX.md) - services/demo/demo-reset.service.ts: reloads mock-data.sql on its own multi-statement connection (DELIMITER blocks turned into plain SQL) and restores the scheduling base from demo_snapshot_* tables in one transaction; GET_LOCK keeps a single reset at a time - the demo entry resets once a day, on the first entry (Madrid date) - /api/demo/status, /reset and /snapshot: only with DEMO_MODE=true, for admins that are not the demo account - Settings → Demo tab (only with NEXT_PUBLIC_DEMO_MODE=true): last reset, saved base, reset now and save current schedules as the base - tests for the DELIMITER conversion, the route guards and the reset call Why: visitors may change anything, so the demo goes back to the same state every day (ADR-038). Render's free instance sleeps, so the reset runs on the first entry instead of a cron; the manual button covers the case where the owner needs it sooner.
- INDEX.md: 20261005_add_demo_reset_log.sql applied on Aiven Why: the table has to exist before the reset code reaches preview.
- login-timing.test.ts mocks services/demo/demo-reset.service.js Why: the auth controller now imports the demo reset, which opens the database config; CI has no .env, so the suite failed to load.
- mock-data.sql: cashier_shift_users uses INSERT IGNORE Why: with one user every role falls back to the same admin, so shift 2 listed it twice and the unique key stopped the load; a fresh local install (pnpm setup:local) has exactly one user.
- docker-compose.yml: MySQL 8.0 only, bound to 127.0.0.1, optional - scripts/setup-local.ts: MASTER_INSTALL.sql plus the scripts after its baseline, departments, an admin with a random password shown once and the mock data; refuses non-local hosts, another database name or a database with tables (unless --force) - fixes in memory, as exact replacements, the two baseline bugs that stop a fresh install (duplicate FK name in 11_cashier, FK to a later table in 19_scheduling); a test fails if the baseline text changes - 20261005_complete_fresh_install.sql: creates cashier_history, scheduling_assignments and demo_activity_log if missing and widens roles.name (no-op on Aiven; created demo_activity_log locally) - config/db-config.ts: connection settings without opening the pool, so a script can read them before the database exists - services/db/sql-script.ts: SOURCE and DELIMITER handling shared by the demo reset and the setup - README: try the demo, run it locally; .env.example defaults to local Why: anyone who clones the repository can run it as a full administrator (ADR-038). Checked on a clean MySQL 8.0 container: same columns, indexes, keys, triggers and routines as Aiven (1161 of 1161) and the same catalog rows; the app signs in with the generated admin.
- setup:local creates admin / fourpoints-local instead of a random password - README documents it; a test keeps script and README in agreement Why: whoever installs locally signs in without copying a one-off password; the account can only exist in a local database (the script refuses any other host), never on Aiven or the site.
- 20261005_fix_bo_monthly_summary_view.sql: v_bo_monthly_summary groups by year, month and '%Y-%m' (same columns and groups as before) - INDEX.md: logged; complete_fresh_install marked as applied on Aiven Why: the view returned the period without grouping by it, so with ONLY_FULL_GROUP_BY every query failed with error 1055 and GET /api/backoffice/stats/monthly always answered 500. Applied on local and Aiven, checked against a direct aggregate of bo_invoices.
…aviour - config/cors.ts: the Origin check moved out of index.ts, unchanged; a refused origin is answered 403 there (same body the error handler gave) - csrf-origin.test.ts: a real Express server with the real middleware; JSON, form, multipart and text/plain posts from another site, look-alike origins and localhost in production get 403 and never reach the route; our own site gets through with credentials - session-cookies.test.ts: the options login and the demo entry pass to res.cookie in production (HttpOnly, Secure, SameSite=Lax, shared domain) Why: the public demo opens writes to anyone, so the defences it relies on must be proven by what they do (ADR-038). Both suites fail when the check is weakened (callback(null, false), sameSite none).
- lib/helpers/safeHref.ts: allows http, https, mailto, tel and relative links, parsed with URL as the browser does - ChecklistNoteBanner: its own [text](url) parser now goes through it - safe-href.test.ts: javascript:, data:, vbscript:, file: and the spellings browsers still run (case, tabs, newlines, leading spaces) Why: the banner turned any URL in a note into a link, javascript: included. Notes come from repository JSON today, but nothing stopped one. The guide and reference pages use react-markdown, which already drops those URLs.
- roleCheck: demo-admin removed from every role list; isRealAdmin, which only told admin from demo-admin, replaced by isAdmin in the routes - demoRestriction: the old demo-admin write whitelist removed; the demo account is limited by is_demo alone (uploads, denyDemo, denyDemoWrites) - authenticateToken: isDemo comes only from the token's demo claim - module-access and the frontend isAdminRole: admin only - 20261005_drop_demo_admin_role.sql: deletes the role when no user has it - tests: the whitelist tests go; the log redaction test now uses the demo account; route sources follow the rename Why: the public demo is an admin marked is_demo (ADR-038); keeping a second, unused demo mechanism meant two sets of rules to keep in step.
- module docs and AGENTS.md: demo-admin removed from every role list; backoffice reads and writes are admin only - general/README: the public demo account and what it cannot do; local development points to the root README, with DEMO_MODE and NEXT_PUBLIC_DEMO_MODE among the variables - general/database: the demo tables and the two baseline bugs a fresh install works around - db-mysql/AGENTS.md: role 7 removed Why: the demo-admin role is retired; the docs described a mechanism the code no longer has.
- DECISIONS: ADR-039, the local database is a documented install again and Aiven stays the only shared one; ADR-015 points to it - TODO: the demo entry now lists only what is left (scheduling base, role migration on Aiven, production variables); demo-admin, local database removal and the schema comparison entries go; three findings added (excludeMantenimiento is a deny list, the old disable_demo_user script, blank Settings tab) - SESSION and ROADMAP: phase 5 built and tested on preview; next steps - VERIFY: one row per commit since the switch to the new repository - INDEX.md: 20261005_drop_demo_admin_role.sql logged Why: ADR-038 relies on the local database ADR-015 had dropped, and the pending list has to match what is done.
- INDEX.md: 20261005_drop_demo_admin_role.sql applied on Aiven - TODO and SESSION: the demo base is saved (October to December 2026, built by the solver; the months from real sick leave are gone); only the production variables are left before going public - VERIFY: 143882d checked on preview Why: the pending list has to match what is done.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This branch was successfully deployed
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.
Cambios
pnpm setup:localy MySQL opcional en Docker.demo-admin; vistav_bo_monthly_summarycorregida.Por qué
Publicar el repo con una demo usable y segura, y que cualquiera pueda instalarlo en local con todos los permisos.