2e9bd540c97001d274c6288800a86a164174ae9c
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0deef2584f |
fix(security): one settings decoder, and the last creation path
Round-four review follow-ups. Every reader of app_settings now shares decodeSettingValue. The previous commit taught the GET handler to decode, which on a legacy SQLite install made the tab show devtools protection as disabled while readBooleanSetting — parsing once, getting the string 'false', rejecting it — left new galleries with it enabled. A decoder used by only some readers is worse than none, because the UI and the behaviour disagree. readBooleanSetting, getImageSecurityDefaults, the v1 devtools fallback and the settings GET all use it now. Standalone contract conversion covered. contract/conversions.js takes Path B and inserts its own event row when the contract has no source quote, so signed standalone contracts were the last path still landing on the migration-038 column defaults. Refs #1296 |
||
|
|
0e560ebb19 |
fix(security): decode settings at the API boundary and honour the transaction
Round-three review follow-ups. getImageSecurityDefaults now accepts a transaction, the way getAppSetting two lines above it already does. quoteService.convertToEvent called it from inside db.transaction() through the global db; sqlite3 runs a single-connection pool, so that read would have waited on the connection its own transaction was holding until the acquire timeout, and the helper's catch would then have swallowed the error and dropped the defaults silently. The double-encoding is fixed where it starts. GET /admin/image-security/settings returned setting_value undecoded, so it shipped "true" to a tab that types the field as boolean — and since the tab PUTs the whole object back through JSON.stringify, every save wrapped another layer around values nobody edited. It decodes now, so a round trip is idempotent. The tab is the only consumer of that endpoint. The reader unwraps to any depth instead of four. The depth on an existing install is however many times someone opened that tab, which is not a number to cap. It terminates because each parse of a string is strictly shorter than its input. Refs #1296 |
||
|
|
9143997f8e |
style(backend): clear the eslint backlog to zero
929 problems (928 errors, 1 warning) -> 0, exit 0.
Rule breakdown, which corrects the report's premise -- `indent` dominated, not
`quotes`: indent 719, quotes 68, no-unused-vars 54, no-empty 36,
no-useless-escape 22, no-case-declarations 17, no-inner-declarations 6,
no-control-regex 5, no-useless-catch 1, no-console 1 (warn).
--fix handled only indent + quotes (719+68 = exactly the "fixable" count).
no-useless-escape was NOT auto-fixable in this eslint version, so the one
genuinely risky class never went through the autofixer -- all 22 were done by
hand. Two mechanical proofs on the autofix diff: a token-level AST diff
(espree, before vs after) shows exactly 68 differing tokens, all quotes, with
the 719 indent fixes producing zero token changes; and a cooked-value diff of
every string/template/regex literal shows 0 differences.
Regex escapes: eslint was correctly conservative and did not flag the
load-bearing ones -- \- in [^a-zA-Z0-9_\-\.] (unescaping makes an invalid
reversed _ -> . range) or in [!@#$%^&*()_+\-=...] (would become a + -> = range
silently matching ",-."). Every removal was a \/ \[ or \. inside a character
class; all 11 old/new pairs were brute-forced over 794 inputs with 0
mismatches.
Manual fixes: no-empty were all deliberate best-effort catches around activity
logging, annotated rather than restructured; no-case-declarations braced in
two adminBackup switches; no-inner-declarations converted to const arrows
after checking no call precedes the declaration and no this/arguments use;
no-control-regex and no-console got targeted disables with stated reasons;
one `catch (e) { throw e; }` wrapper removed.
Two unused bindings were near-misses worth noting: secureStatic.js's
`fullPath` is a path-traversal guard (safePathJoin throws on escape) and
restoreService.js's `backupManifest` is the throw-on-corrupt-manifest gate
before a rollback -- deleting either would have silently removed a check. Only
the bindings were dropped; the calls stay.
Two real bugs found and deliberately preserved with a comment plus a narrow
disable rather than deleted, since deleting would erase the evidence:
_workflowSeedBoot.js's `booted` is written but never read, so the intended
once-per-process guard is missing its early return and workflows re-seed on
every call; and quoteService.js's VALID_QUOTE_TRANSITIONS is a full state
machine nothing consults, so quote status changes are unvalidated.
Backend test suite: 253 suites / 2552 tests passing, 0 failures, before and
after.
Refs testplan REPORT.md #22 (Part 1.2.02).
|
||
|
|
b118695474 |
feat(permissions): granular permission gating + role editor & presets (#747, phase 1 of #743) (#1045)
* feat(permissions): granular permission gating + role editor & presets Make every admin feature permission-gateable so multi-user studios can split capability across roles (#747, and phase 1 of #743). - Split the catch-all settings.edit into dedicated dangerous-config perms (banking / domains / security / integrations / features): a team member can no longer change IBAN, domains, SSO, webhooks, API tokens or feature flags. Reads keep an OR with settings.view so existing roles keep visibility. The site-URL write inside /general is change-gated on settings.domains. - Add dedicated perms for admin surfaces miscategorised under settings.* (whatsapp, event_types, image_security, notifications, system) plus roles.manage and vat_codes.view; gate the previously-ungated VAT read. - Boot self-heal (_permissionsBoot.js): super_admin always holds every permission (tracks-all) so new perms never need a compensation migration; all other roles stay frozen (no silent escalation on upgrade). - Seed two presets: Solo Photographer (full operator) and Team Photographer (contributor — view events + manage photos + read-only CRM; no settings/users/billing edits, no events.edit). - Role editor: adminRoles CRUD (create/edit/clone/delete + permission matrix; system roles protected, super_admin immutable) and a Roles tab with a category-grouped matrix and preset cloning. - Settings page tabs are permission-gated with snap-back; i18n en/de. Migration 174. Backward-compatible: admin/editor/viewer unchanged. * feat(permissions): hide in-page action buttons a role can't use Wrap mutating controls on the surfaces restricted roles actually reach (Events list, Archives, gallery photo grid, event detail) in PermissionGate so they are HIDDEN when the user lacks the permission, rather than shown-then-403: - Events list: create / bulk archive / bulk delete / row archive / row delete / download-archive. - Archives: restore / download / delete. - Photo grid: single + bulk delete (photos.delete), per-photo download (photos.download), bulk move/hide/show (photos.edit). - Event detail: edit / rename / publish (events.edit), duplicate (events.create), archive (events.archive), create-invoice (bills.manage); the Actions card is hidden entirely for view-only roles. - Photos tab: upload / external import (photos.upload), export menu (photos.download). Backend already enforces these with 403; this is the matching UX so a Team Photographer never sees delete/settings controls. * fix(permissions): close settings-split bypass via generic settings writers Security review found the settings.edit split was bypassable: the generic settings writers (/general, /analytics, /seo, /security) upsert arbitrary setting_keys, so a role holding only settings.edit (or settings.security) could write keys owned by a narrower permission — repointing the public site URL (settings.domains), security policy (settings.security) or VAT/accounting config (settings.banking) via the wrong endpoint. Add stripUnauthorizedProtectedKeys(): before every generic upsert, drop any protected key the caller isn't permitted to write (general_site_url → settings.domains, security_* → settings.security, accounting_* → settings.banking). Dedicated routes still work because their caller holds the matching perm. Replaces the narrower in-handler site-URL guard. Also fix two tests affected by the RBAC changes: - authzPermissionGaps: API-token management moved to settings.integrations, so grant that (not settings.edit) to exercise the ownership 404. - AdminPhotoGrid.viewToggle: stub PermissionGate (its buttons are now gated and the test renders without a PermissionsProvider). * fix(permissions): address upstream review (#1045) - Renumber migration 174 -> 175 (174 now taken by 174_sqlite_nullable_event_dates from #1035; the collision made picpeakImportService's forward-only restore guard treat both as order 174 and accept a newer .picpeak onto an older schema). - Contain the roles.manage blast radius (delegation, not root escalation): a non-super_admin can no longer edit their own role, nor grant any permission their own role doesn't already hold (createRole + updateRole). - Protected-key denial now 403s (naming the keys + required perms) instead of silently stripping and reporting "saved" (adminSettings generic writers). - Reserve team_photographer so a custom role can't squat the preset name. - Boot self-heal: per-step try/catch so a role_permissions insert race on one replica doesn't skip preset seeding. - Forward-project the feature .manage perms that also replaced settings.edit gates (whatsapp/event_types/image_security/notifications/system), matching the settings.* split projection so the pattern is symmetric for phase-2. - Guard exports.down's roles/admin_users queries with hasTable. * fix(permissions): change-detection on protected-key 403 + commit guard tests (#1045) Round-2 review: - The protected-key 403 fired on key PRESENCE. The General tab re-posts general_site_url on every save, so a settings.edit-only role (the office manager this PR enables) got 403'd on every General save even when the URL was unchanged. Restore change-detection: compare the incoming value against the stored one and 403 only on an actual change; unchanged protected keys are dropped so the rest of the save proceeds. Only /general is affected. - Commit the self-amplification guard test (was run locally, never staged): adminRolesGuards.test.js — non-super can't grant perms it lacks, can't edit its own role, can't escalate another role; super_admin bypasses; team_photographer name reserved. - Add adminSettingsProtectedKeys.test.js pinning the change-detection: an unchanged general_site_url saves, an actual change 403s, super_admin changes it. |
||
|
|
8f91c2ca99 |
fix(security): neutralize spreadsheet formulas in all CSV/export cell-writers (CSV injection cluster) (#948)
Co-authored-by: Paul Nothaft <[email protected]> |
||
|
|
892e47d017 |
feat: add multi-administrator support with RBAC and fix backup/restore for S3
## Multi-Administrator System
- Add role-based access control (RBAC) with predefined roles (Super Admin, Admin, Editor, Viewer)
- Add granular permissions system for all admin operations
- Add admin user management page with invite functionality
- Add email invitation system for new administrators
- Add permission middleware protecting all admin routes
- Add PermissionGate component for frontend permission checks
- Track event creator (created_by) for audit purposes
## Backup & Restore Fixes
- Fix S3 backup: endpoint URL handling, manifest loading, field name compatibility
- Fix S3 restore: add list-backups endpoint, transform S3 config from frontend format
- Fix PostgreSQL compatibility: add .returning('id') for insert operations
- Fix disk space check: use df command, handle unknown space gracefully
- Fix dry-run validation to not block on warnings
- Fix req.user → req.admin in restore routes
## Database Migrations
- 054: Add roles table with predefined roles
- 055: Add permissions table
- 056: Add role_permissions junction table
- 057: Add role_id to admin_users
- 058: Add admin_invitations table
- 059: Add admin email templates
- 060: Add created_by to events table
## Other Improvements
- Update .gitignore to exclude planning docs and local backup directory
- Remove SQLite database file from tracking
- Add i18n translations for user management (EN/DE)
|
||
|
|
6033461be1 |
feat: add Apple Liquid Glass templates, image security settings, and automated releases
## New Features - Apple Liquid Glass CSS template with iOS 26-inspired design - Liquid Glass Dark theme with neon accents - Image Security settings tab with per-event protection levels - Release Please automation for versioning and changelog ## Improvements - Update CSS template migration with final working templates - Add search placeholder visibility fix for glass themes - Update README roadmap (Download Protection, Gallery Templates, Filtering & Export now implemented) ## Infrastructure - Add release-please.yml workflow for automated releases - Add release-please-config.json and manifest - Update docker-build.yml with Release Please integration comments - Add comprehensive CHANGELOG.md ## Cleanup - Add working/planning docs to .gitignore (CLAUDE.md, test-*.md, feature-*.md, etc.) - Remove internal planning documents from git tracking (kept locally) ## Files Added - .github/workflows/release-please.yml - .release-please-manifest.json - release-please-config.json - CHANGELOG.md - frontend/src/features/settings/tabs/ImageSecurityTab.tsx |
||
|
|
1b4b497fdf |
chore: clean up codebase for production readiness
Mirror to GitHub / mirror (push) Successful in 44s
Test and Lint / backend-test (push) Successful in 1m42s
Test and Lint / frontend-test (push) Has been cancelled
Version and Release / version-bump (push) Has been cancelled
Version and Release / trigger-drone (push) Has been cancelled
- Remove all console.log/debug statements from production code - Add NODE_ENV checks for development-only logging - Remove test scripts (test-feedback, test-image-security, test-backup-*, test-restore) - Remove one-time fix scripts (fix-temp-photos, fix-migration-state, mark-migration-applied) - Remove sensitive files (.env.backup, ADMIN_CREDENTIALS.txt) - Update package.json to remove references to deleted scripts - Replace console statements with logger utility in backend - Secure error boundaries to not expose stack traces in production This makes the codebase production-ready with no debug output or test scripts. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]> |