93 Commits

Author SHA1 Message Date
1bf19fff0e Security audit: fix 30 findings across auth, IDOR, XSS, path traversal, infrastructure
Comprehensive 6-domain security audit addressing 8 Critical, 17 Important,
and 5 Low findings. Key fixes:

Critical:
- Strip PII from unauthenticated ticket lookup (IDOR)
- Add role+permission checks to event check-in routes
- Validate tier-to-event ownership on update/delete (IDOR)
- Fix path traversal in video replace (resolve + prefix check)
- Enable MongoDB authentication for Rocket.Chat
- Disable Grafana anonymous access
- Sanitize CSV exports against formula injection (payments)
- Apply DOMPurify to richDescription on public event page (XSS)

Important:
- Require current password for self-service password changes
- Atomic password reset token consumption (race condition fix)
- Scope postMessage to specific origin (not wildcard)
- Validate redirect parameter against open redirect
- Replace weak temp passwords (5760 values → crypto.randomBytes)
- Move shift capacity check inside transaction (TOCTOU fix)
- Fix EVENTS_ADMIN privilege inversion in ticketed events
- Make ENCRYPTION_KEY required (remove optional fallback)
- Add internal Prometheus metrics endpoint for Docker scraping
- Add nginx-level rate limiting (limit_req_zone)
- Fix X-Forwarded-For to use $remote_addr (prevents spoofing)
- Replace CSP stripping with frame-ancestors in embed proxies
- Remove error.message from Fastify 500 responses
- Strip PII from volunteer canvass address data
- Wrap GrapesJS output in {% raw %} to prevent Jinja2 SSTI
- Scope SSE token query param to /sse path only
- Sanitize Listmonk email query against injection

Bunker Admin
2026-03-27 08:47:24 -06:00
39d74e7b85 Add guided tour, media enhancements, error handling, and DevOps improvements
Major additions: onboarding tour system, correlation-id middleware, media
error handler, restore script, env validation script, Dockerignore files.
Updates across 70+ admin components for improved UX and error handling.

Bunker Admin
2026-03-26 10:31:51 -06:00
0c634e100f Replace custom code-server (9GB) with upstream LinuxServer image (~1GB)
Drop the custom Dockerfile.code-server that bundled Claude Code CLI,
Python/MkDocs tooling, and build-essential on top of codercom base.
Switch to the already-mirrored linuxserver/code-server image instead.

- Both compose files: use code-server:latest, LinuxServer env vars
  (PUID/PGID/DEFAULT_WORKSPACE), port 8443, /config mount layout
- Nginx configs + templates: proxy to :8443 instead of :8080
- API env default: CODE_SERVER_URL updated to :8443
- build-and-push.sh: remove --include-code-server flag
- upgrade.sh: remove code-server conditional rebuild + registry fallback
- install.sh: add --ignore-pull-failures for optional missing images
- .env.example, CCP templates, bunker-ops template: updated

Bunker Admin
2026-03-25 20:10:36 -06:00
f2284a9cdf Fix curl|bash install: redirect stdin from /dev/tty for interactive prompts
When piped (curl | bash), stdin is the curl output, not the terminal.
All read prompts in config.sh were reading leftover pipe data or EOF,
causing infinite password validation loops and garbage domain values.

Bunker Admin
2026-03-25 19:45:29 -06:00
7287328148 Harden install pipeline: health checks, log rotation, backup timer
- install.sh: Add Docker daemon check, 10GB disk space pre-flight,
  error handling on pull/up, post-startup health polling with crash
  detection, cleanup trap on failure
- docker-compose: Fix nginx/listmonk depends_on to service_healthy,
  add x-logging anchor (10m/3 files) to all ~39 services
- config.sh: Preserve existing secrets on re-run (reconfigure mode),
  add automated daily backup timer (systemd, 02:00, 30-day retention)
- mirror-images.sh: Fix gotify source tag (2.9.0 not v2.9.0)
- build-release.sh: Ensure mkdocs/docs and mkdocs/overrides dirs exist
- .env.example: Add COMPOSE_PROFILES variable

Bunker Admin
2026-03-25 19:33:11 -06:00
3262d92065 Remove hardcoded container names for multi-instance deployment support
- Dashboard: auto-discovers containers from Docker network via socket
  proxy API instead of hardcoded 30-name list. Labels derived from
  docker compose service metadata.
- Email/Settings: mailhog host read from env.SMTP_HOST instead of
  hardcoded 'mailhog-changemaker' string
- Pangolin: grafana container derived from env.GRAFANA_URL hostname;
  newt container/service names from NEWT_CONTAINER_NAME/NEWT_COMPOSE_SERVICE
- SSRF blocklist: built dynamically from all service URL env vars
  instead of hardcoded hostname list
- New env vars: DOCKER_NETWORK_NAME, DOCKER_PROXY_URL,
  NEWT_CONTAINER_NAME, NEWT_COMPOSE_SERVICE

Bunker Admin
2026-03-25 17:35:05 -06:00
204e90dd3b Fix config.sh: read embed ports from .env for Homepage services.yaml
Rocket.Chat and Jitsi embed ports were hardcoded in the Homepage
services.yaml generation. Now reads from .env so multi-instance
deployments with custom ports get correct Homepage dashboard links.

Bunker Admin
2026-03-25 15:32:53 -06:00
81026b38db Remove stale granular admin roles migration (subsumed by baseline)
Bunker Admin
2026-03-25 15:26:39 -06:00
abdfd50cb8 Make embed proxy ports configurable via env vars for multi-instance deployments
All 13 nginx embed proxy ports (8881-8895) are now driven by environment
variables instead of being hardcoded. This prevents port conflicts when
running multiple Changemaker instances on the same host.

Chain: .env → docker-compose port mappings → nginx container env →
entrypoint.sh envsubst → services.conf.template listen directives →
API /services/config endpoint → frontend buildServiceUrl().

Existing deployments are unaffected (all vars default to current values).

Bunker Admin
2026-03-25 15:25:00 -06:00
63e05adcee Bunch more stability fixes 2026-03-23 22:12:24 -06:00
a56f8446f7 Fix Pangolin setup: root domain support + disable SSO auth on resources
- Omit subdomain field for root domain resources (Pangolin rejects empty
  string but accepts absent field)
- Set sso:false + blockAccess:false after resource creation so resources
  are publicly accessible without Pangolin auth redirects
- Make subdomain optional in CreateHttpResourcePayload type
- Applied to both /setup and /sync endpoints

Bunker Admin
2026-03-23 15:47:57 -06:00
a5a83f2d04 Whitespace: trigger upgrade test
Bunker Admin
2026-03-23 14:58:16 -06:00
c701f77237 Add :latest fallback to registry image pull in upgrade.sh
When --use-registry is set, the upgrade script tries to pull images
tagged with the current HEAD SHA. If images were built at an earlier
commit, that SHA tag won't exist. Now tries :latest before falling
back to a full source build. Also applies to nginx and code-server.

Bunker Admin
2026-03-23 14:50:16 -06:00
44931260c4 Fix build-release.sh Gitea URL for host-side uploads
GITEA_URL points to the internal Docker hostname (gitea-changemaker:3000),
which is unreachable from the host. Derive external URL from GITEA_REGISTRY
instead, which already contains the external hostname.

Bunker Admin
2026-03-23 14:07:36 -06:00
e0fd4fd7b7 Update CLAUDE.md with consolidated architecture docs
Bunker Admin
v2.2.0
2026-03-23 13:48:18 -06:00
0090bd2f54 some random png stuff 2026-03-23 13:07:05 -06:00
68ba45a689 Documentation editorial: Material theme hardening, metadata, and content polish
- Enable navigation.instant, prefetch, progress, content.code.select, content.tabs.link
- Fix edit_uri (main→v2), copyright year (2024→2024-2026), consent banner config
- Add abbreviations glossary (47 acronyms with hover tooltips via snippets auto-append)
- Add tags to all 72 doc pages with consistent taxonomy (audience/module/type)
- Add status:new badges to 16 recent feature pages, search:boost to 7 entry pages
- Rewrite Architecture page with 5 Mermaid diagrams and full component documentation
- Rewrite Troubleshooting page from 5 to 13 sections with actionable checklists
- Fix broken links (Monitoring/Contributing pointed to blog placeholder)
- Expand Admin Guide roles table from 5 to 11 roles
- Create custom 404 page, blog with authors and inaugural v2 announcement post
- Fresh Playwright screenshots for login, dashboard, campaigns, users, settings, locations, shifts
- Remove 5 test/dev files and orphan template override
- Add planning document (DOCS_NEXT_STEPS.md) for future editorial reference

Bunker Admin
2026-03-23 12:36:10 -06:00
bb1935027d Upgrade system finished 2026-03-22 21:47:09 -06:00
a71ba20176 Update CLAUDE.md with installer and release workflow documentation
- Quick Start: add pre-built install section with curl one-liner
- Directory Structure: expand scripts/ with all deployment scripts
- Registry Operations: add build-release, install commands, two-compose note
- V2 Gotchas: document release vs source detection, api/dist gitignore,
  api/.dockerignore purpose
- Key Config Files: add docker-compose.prod.yml and config.sh

Bunker Admin
2026-03-22 20:38:36 -06:00
8e6f0996de Add pre-built image installer and release tarball system
New install method: curl one-liner downloads a lightweight release
tarball (~9 MB) and runs the config wizard. No git clone needed,
no TypeScript compilation — pulls pre-built images from Gitea registry.

- docker-compose.prod.yml: production compose without build blocks or
  source code volume mounts; IMAGE_TAG defaults to latest
- scripts/install.sh: curl-friendly installer (downloads tarball,
  extracts, runs config.sh)
- scripts/build-release.sh: creates release tarball from dev repo
  with only runtime files (configs, scripts, docs, empty data dirs)
- config.sh: release-mode detection (VERSION file + no .git dir),
  auto-sets IMAGE_TAG=latest and NODE_ENV=production
- upgrade.sh: release-mode upgrade path (downloads new tarball from
  Gitea Releases API instead of git pull, always uses registry mode)
- upgrade-check.sh: release-mode version check via Gitea API
- .gitignore: exclude releases/ and api/dist/
- Docs: updated getting-started with pre-built install instructions

Bunker Admin
2026-03-22 20:34:49 -06:00
f550423c3f Add registry module and api .dockerignore to fix production build
- Create api/src/modules/registry/ (service + routes) so server.ts
  import resolves and TypeScript compiles all 38 modules cleanly
- Add api/.dockerignore to exclude stale local dist/ from Docker build
  context, preventing old compiled output from persisting in images
- Registry routes: GET /status (Gitea packages API), POST /build-push
  and POST /mirror (write trigger files for host watcher, SUPER_ADMIN only)

Bunker Admin
2026-03-22 19:49:36 -06:00
be2fa5d80b Fix media-api restart loop and add registry build scripts
- Fix @/utils/logger path alias (tsc doesn't transform @/ in output)
- Add JWT_INVITE_SECRET to media-api compose environment block
- Fix redis-exporter depends_on to use service name not container name
- Fix upgrade.sh: restore tracked files deleted by restore_user_paths
- Add scripts/build-and-push.sh for building + pushing production images
- Add scripts/mirror-images.sh for mirroring third-party images

Bunker Admin
2026-03-22 19:17:10 -06:00
e6e324262f Add JWT_INVITE_SECRET to API container environment
docker-compose.yml explicitly enumerates each env var passed to
containers, so the new JWT_INVITE_SECRET needed to be wired through
the environment block or the API would fail Zod env validation at
startup.

Bunker Admin
2026-03-22 12:40:34 -06:00
647efffdc4 Security hardening: JWT algorithm pinning, key separation, injection fixes
- Pin HS256 algorithm on all jwt.verify() calls (9 sites) and jwt.sign()
  calls (3 sites) — prevents algorithm confusion attacks
- Add JWT_INVITE_SECRET env var; volunteer invite tokens now use a
  dedicated key separate from access/refresh secrets
- Remove req.query.secret fallback from Listmonk webhook route — secrets
  must not appear in nginx access logs
- Replace child_process.spawn in email template seed endpoint with direct
  function import; add require.main guard to seed script
- Add sanitizeCsvField() to location CSV export to prevent formula
  injection in Excel/Sheets (=, +, -, @ prefix → apostrophe prefix)
- Cap QR endpoint text input at 2000 chars to prevent DoS via large payloads
- Fix pre-existing TS errors: type participantNeeds as UpsertNeedsInput
  in meeting-planner service; add sso field to UpdateResourcePayload

Bunker Admin
2026-03-22 12:35:04 -06:00
15fb9b93aa Merge branch 'v2' of https://gitea.bnkops.com/admin/changemaker.lite into v2 v2.1.0 v2.1.1 2026-03-15 13:50:13 -06:00
28e4bc9475 Bunch of updates to scheduling 2026-03-15 13:50:09 -06:00
12734aca16 Fix MkDocs search results not displaying with custom header
Use inline JS styles (applySearchLayout) instead of CSS-only approach
for search panel layout - fixes Firefox compatibility where cross-origin
Material stylesheets override !important rules. Adds explicit height,
flex layout, z-index, and background on search elements. Also fixes
click-to-exit by deferring DOM queries to DOMContentLoaded. Syncs
header-builder.service.ts with main.html changes.

Bunker Admin
2026-03-11 16:51:55 -06:00
admin
9267f070b3 Fix Vaultwarden iframe embedding by stripping upstream CSP header
Vaultwarden sends a restrictive Content-Security-Policy with frame-ancestors
that blocks iframe embedding. The embed proxy (port 8890) already stripped
this header, but the subdomain server block did not.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 18:26:41 -06:00
admin
33e1ff2907 Add WebSocket upgrade headers to nginx API proxy blocks for docs collaboration
The /api/ location blocks in both default.conf and services.conf templates
were missing Upgrade/Connection headers, preventing the Hocuspocus WebSocket
connection from establishing through nginx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 10:49:50 -06:00
b136396f3b Fix excalidraw image: switch to official excalidraw/excalidraw:latest
kiliandeca/excalidraw:sha-e42a510 tag doesn't exist. The kiliandeca fork
hasn't been updated since 2021. Official image is current (Jan 2026).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:36:34 -06:00
e0507e1f25 Fix docker-socket-proxy image tag: 0.4.2 → v0.4.2
The tag on Docker Hub requires the 'v' prefix, causing pull failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 16:27:36 -06:00
533783bcae Mkdocs search fixers 2026-03-09 16:05:25 -06:00
e2a1ac0113 Fix MkDocs search not displaying results with custom header
The collapsed Material header (height: 0, overflow: visible) left the
search input reachable but the __search checkbox was never toggled when
users typed directly into it. This prevented both Material's native CSS
and our custom CSS from revealing the results panel (opacity stayed 0,
scrollwrap max-height stayed 0).

- Add focusin/input event delegation to check __search on direct input
- Add search icon, dark mode toggle, and docs sidebar toggle to header
- Add CSS for hidden Material header, search positioning, palette, tabs
- Avoid Jinja2 block syntax inside JS comments (parsed as directives)

Bunker Admin
2026-03-09 15:55:01 -06:00
900a0affe5 Add CRM activity enrichment, notification bridging, crash-safe scheduled jobs, and quick wins
Workstream A — CRM & Notifications:
- Add fire-and-forget CRM activity helper (api/src/utils/crm-activity.ts) hooked into
  campaign email, canvass visit, donation, and purchase write sites
- Add 5 operational NotificationType enum values (shift_signup_confirmed, shift_reminder,
  shift_cancelled, canvass_session_summary, reengagement) via Prisma migration
- Bridge notification email queue to in-app notifications for volunteer-facing events
- Extend TYPE_TO_PREF map and NotificationsPage labels for new types

Workstream B — Quick Wins:
- Extract shared role constants (11 roles) to admin/src/utils/role-constants.ts,
  update 4 consuming pages
- Add Ad Analytics sidebar entry in payments submenu
- Gate 6 calendar routes with enableSocialCalendar feature flag
- Add GET /series/:id/count endpoint and fix hardcoded shiftsCount={0} in ShiftsPage
- Add influenceCampaignId to Order model for donation-campaign attribution,
  wire through Stripe checkout metadata

Workstream C — Crash-Safe Scheduled Jobs:
- Create BullMQ scheduled-jobs queue with 10 repeatable job types replacing
  setInterval blocks in server.ts (dynamic imports, concurrency: 2)
- Keep presenceService (1min) and challengeScoringService (5min) as setInterval

Bunker Admin
2026-03-09 14:15:30 -06:00
c192c04c79 Security audit: fix 25 findings across API, nginx, and Docker
Addresses data exposure, access control, input validation, infrastructure
hardening, and supply chain security issues identified during audit.

Key changes:
- Strip internal fields from public campaign/profile/comment endpoints
- Restrict docs routes to CONTENT_ROLES, provisioning to SUPER_ADMIN
- Add SSE connection limits, social middleware fail-closed behavior
- Bind all non-nginx ports to 127.0.0.1, pin container image versions
- Add CSP header, conditional HSTS, token redaction in nginx logs
- Validate nav URLs, calendar schemas, video tracking batch events
- Reject default admin password placeholder, add SSRF protocol checks
- Exclude .env from Code Server, enforce RC admin password in compose
- Add Zod validation for achievement grant/revoke, webhook secret header
- Fix path traversal prefix attack, add calendar token expiry

Bunker Admin
2026-03-09 14:13:37 -06:00
bdb672c7ad Remove hardcoded "Explore More" section from public campaigns page
The section bypassed navigation settings and feature flags, linking
directly to Map, Shifts, and Gallery with its own visibility logic.

Bunker Admin
2026-03-09 12:44:27 -06:00
c8640ca4c7 Move Save Settings button to top of Docs Settings page
Bunker Admin
2026-03-09 12:36:28 -06:00
b061e2ce61 Add database migration phase and stale volume detection to upgrade script
Inserts Phase 5 (Database Migration) between container rebuild and service
restart. Detects failed/incomplete Prisma migrations via _prisma_migrations
query and auto-resolves them before running migrate deploy in a one-off
container — catching errors in the script rather than letting the API enter
a restart loop. Also detects when package.json/package-lock.json changed
and removes old API/admin containers to prevent stale anonymous volumes
from shadowing updated node_modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:19:53 -06:00
admin
ef11f94e76 Add systemd install script and improve reset-site.sh resilience
- Add scripts/systemd/install.sh to handle placeholder substitution
  (__PROJECT_DIR__, __USER__) and systemd unit installation in one command
- Simplify manual install instructions in SettingsPage and config.sh to
  reference the new install script
- Preserve existing home.html and home.css in reset-site.sh instead of
  overwriting with templates
- Add comments/ and partials/ to preserved directories list
- Fix nav removal in mkdocs.yml using Python regex instead of fragile sed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 01:03:50 -06:00
admin
12345f9816 Fix Pangolin sync siteId resolution, nginx media proxy, and upgrade script
- Resolve Pangolin site slug to numeric ID in sync route (fixes target creation 400 errors)
- Disable SSO on newly created Pangolin resources for public access
- Fix nginx media API proxy: use rewrite + set ordering for proper URI rewriting
- Upgrade script: clear skip-worktree flags, fix Docker-owned dir permissions, stash untracked files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 23:55:08 -06:00
008dfed3bc Updates to docs system and some other leftover code bits from mkdocs rebuilds 2026-03-08 23:44:18 -06:00
989f1dfa34 Fix docs editor not loading content on first file click
Add editorReady state flag to resolve race condition where Y.js
sync completes before Monaco editor mounts, causing the
MonacoBinding effect to skip binding creation on the initial
file selection.

Bunker Admin
2026-03-08 23:42:48 -06:00
76b87d9f3d Tonne of things 2026-03-08 18:11:26 -06:00
3f35e4b18d Harden MkDocs header auth-check: targeted postMessage, tighter CSP
- Replace postMessage wildcard ('*') with explicit parent origin passed
  via ?origin= parameter to prevent auth state disclosure to arbitrary
  embedders
- Tighten frame-ancestors CSP: production restricts to self + DOMAIN,
  dev restricts to localhost origins (was frame-ancestors *)
- Remove deprecated X-Frame-Options ALLOW-FROM header (CSP
  frame-ancestors is the modern replacement)
- Validate targetOrigin with URL constructor before use

Bunker Admin
2026-03-07 16:44:29 -07:00
eba6453981 Merge branch 'v2' of https://gitea.bnkops.com/admin/changemaker.lite into v2 2026-03-07 13:10:12 -07:00
1cca51e518 Tonne of updates to things like social systems, calendars, and the documentation system (making it mobile friendly and fixing up navigation) 2026-03-07 13:10:08 -07:00
08d8066157 Add ticketed events, Jitsi meeting integration, social features, and calendar system
- Ticketed events: full CRUD, ticket tiers (free/paid/donation), Stripe checkout,
  QR-based check-in scanner, public event pages, ticket confirmation emails
- Event formats: IN_PERSON/ONLINE/HYBRID with auto Jitsi meeting room lifecycle,
  ticket-gated meeting access, moderator JWT tokens, feature-flag guarded
- Social engagement: challenges with scoring/leaderboards, referral tracking,
  volunteer spotlight, impact stories, campaign celebrations, wall of fame
- Social calendar: personal calendar layers, shared calendar items with
  recurrence, scheduling polls, mobile day view
- MCP server: events tool pack with full admin CRUD + meeting token generation
- Unified calendar: eventFormat-aware tags, online event indicators
- Updated docs site, pangolin configs, and various admin UI improvements

Bunker Admin
2026-03-06 14:33:33 -07:00
62fc116c06 Clean up nginx conf.d: remove dead configs, gitignore generated files, fix templates
Remove V1 legacy configs (admin.conf, public.conf) and orphaned backup that
were never used by the container. Stop tracking generated .conf files (built
from *.template by envsubst at startup). Backport improvements to templates:
variable proxy_pass for media-api (fixes startup crash when container is down),
extended bot detection list, and mkdocs-proxy location for volunteer map docs.

Bunker Admin
2026-03-06 12:24:26 -07:00
e7890b0be1 Add admin user creation to gancio-init container
The gancio-init container only seeded default color palettes but never
created an admin user, causing the settings sync to silently fail on
every API startup. Now creates an admin user via pgcrypto bcrypt hashing
using GANCIO_ADMIN_USER/GANCIO_ADMIN_PASSWORD env vars, with
ON CONFLICT DO NOTHING for idempotency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:20:19 -07:00
a37d9910af Add nocodb-init container for automatic database registration
Follows the listmonk-init pattern: an alpine:3 container that runs once
after NocoDB is healthy, calls the REST API to register changemaker_v2
as a browsable data source, and exits. Idempotent — exits immediately
if the base already has tables, and guards against duplicate sources
during async table discovery.

Bunker Admin
2026-03-05 10:59:39 -07:00