Commit Graph

1651 Commits

Author SHA1 Message Date
Jorge Manrubia ffb9847a4e Merge pull request #2287 from italomatos/refactor/use-ids-method-instead-of-pluck
Refactor: Replace pluck(:id) with ids method
2026-01-05 16:03:41 +01:00
Jorge Manrubia 045ddf78f3 Revert "Make sure new card drafts are refreshed when reused"
This reverts commit 3008011175.
2026-01-05 15:58:16 +01:00
Jorge Manrubia 3008011175 Make sure new card drafts are refreshed when reused
To deal with old timestamps messing with card ordering and so.

https://app.fizzy.do/5986089/cards/3495
2026-01-05 15:57:48 +01:00
Italo Matos 4189261cd0 Refactor: Replace pluck(:id) with ids method
Use the more idiomatic ActiveRecord ids method instead of pluck(:id)
across controllers and models. The ids method is more readable and
explicitly conveys the intent to retrieve primary key values.

Changes:
- BoardsController#edit: Use @board.users.ids
- Board::Storage: Use cards.ids, Comment.where().ids, and ActionText::RichText.where().ids
- User::Accessor: Use account.boards.all_access.ids

This change improves code clarity while maintaining the same functionality.
2026-01-04 10:02:58 -03:00
Jeremy Daer 0eefd67b68 Block IPv4-compatible IPv6 addresses in SSRF protection (#2273)
The SSRF filter checked ipv4_mapped? but not ipv4_compat?, allowing
addresses like ::169.254.169.254 to bypass the link-local check and
reach cloud metadata endpoints.

Changes:
- Add ipv4_compat? check to block deprecated IPv4-compatible format
- Rename private_address? to blocked_address? (more accurate - method
  blocks more than just RFC 1918 private ranges)
- Add IPv6 test coverage for both mapped and compat formats

Both ipv4_mapped and ipv4_compat formats are blocked entirely as
defense-in-depth: DNS never returns these formats, so they only
appear in attack scenarios.

HackerOne: #3481701
2025-12-29 21:45:06 -08:00
Rosa Gutierrez f66a2dc1ba Consider user avatars always public
Avatars are purposely accessible without authentication
(5e3b5b6d7c) because they can be in public
collections. Trying to restrict this by checking whether they're in fact
present in some public collection is rather expensive, so let's keep
them public.
2025-12-26 19:33:26 +01:00
Rosa Gutierrez 196d685f8d Implement authorization for Active Storage endpoints
Consider blobs attached to any public records accessible to anyone with
the URL.
2025-12-25 21:22:36 +01:00
Stanko K.R. 0df667f4fb Fix HTML injection in webhooks through card titles 2025-12-22 12:20:32 +01:00
Jeremy Daer bd6c1cf34f Storage: harden reconcile for concurrent writes and fix board transfer (#2096)
* Storage: harden reconcile for concurrent writes and fix board transfer

Reconcile now uses two-cursor approach: captures cursor before and after
the storage scan, aborting if they differ (entries added during scan).
Job retries 3x with 1-minute waits and limits concurrency to 1 per owner.

Board transfer now correctly moves storage for card description embeds
and comment embeds, not just direct attachments. Uses batched queries
to handle cards with thousands of comments efficiently.

Also fixes N+1 queries in attachment grouping via lookup maps.

* Storage: fix per-attachment reconcile and enforce no blob reuse

The storage ledger tracks per-attachment (not per-blob) as a business
abstraction for quotas. This fixes reconcile to match that model and
adds enforcement to prevent blob reuse in tracked contexts.

* Reconcile now uses joins(:blob).sum() for per-attachment counting
* New validation prevents reusing blobs across tracked attachments
* Race-safe storage_total creation with create_or_find_by
* Job efficiency: skip find_by when object already available
* Backfill script checks per-attachment, not just per-blob
2025-12-19 13:07:32 -08:00
Jorge Manrubia 2cbbdfbd19 Merge pull request #2138 from italomatos/refactor/replace-reverse-merge-with-with-defaults
Refactor: Replace reverse_merge with with_defaults for improved readability
2025-12-19 11:24:46 +01:00
Rosa Gutierrez cb01966439 Remove unused Card.published_or_drafted_by scope
As the tests for it could lead to confusion where it seems drafted cards
are not accessible to someone with access to the board and with the
direct drafted card URL.
2025-12-19 09:46:21 +01:00
Samuel Péchèr 32a0acea11 Merge pull request #2107 from basecamp/adjust-paragraph-spacing
Adjusting text content spacing between paragraphs
2025-12-19 07:59:25 +01:00
Adrien Maston 972f86b603 Merge pull request #2177 from basecamp/mobile-app/scoped-styles
Mobile app / Scoped styles
2025-12-18 17:57:46 +01:00
Stanko Krtalić 42185a8d6d Merge pull request #2153 from basecamp/limit-the-number-of-asignees-on-a-card
Limit the number of asignees on a card
2025-12-18 12:08:45 +01:00
Samuel Péchèr d250c256c6 Add paragraph space to seed Card #8 2025-12-18 09:27:56 +00:00
Adrien Maston 5b7aa67bb3 Add platform attribute 2025-12-17 08:56:08 +01:00
Jorge Manrubia 0278fe6ae4 Revert "Mobile app / Scoped stylesheets" (#1698)
This reverts commit 39c1906e67.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 17:20:18 +01:00
Mike Dalessio 2b388dd241 Merge pull request #2164 from basecamp/flavorjones/user-validate-name
Validate User name presence and handle blank names gracefully
2025-12-16 10:49:13 -05:00
Jorge Manrubia ee80b87c8c Add billing system with Stripe
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Jason Zimdars <jz@37signals.com>
2025-12-16 16:44:20 +01:00
Mike Dalessio b75d2ae6d8 Validate User name presence and handle blank names gracefully
Addresses an issue where User#familiar_name assumed `name` was always
present, potentially raising an exception during view rendering. Now
User validates name presence, and User#familiar_name handles blank
strings without error, in case any existing invalid records exist.
2025-12-16 10:44:10 -05:00
Adrien Maston 04332bc526 Merge branch 'main' into mobile-app/scoped-stylesheets
* main:
  Use decimals for ordered lists
  Update cached fragments
  Revert "Merge pull request #1865 from basecamp/public-avatar-caching"
  Show only public cards on public boards
  Swap order of avatar links
  Limit length of full name during signup
  Make layout bulletproof
  Serve own avatar from its own endpoint
2025-12-16 15:30:31 +01:00
Stanko K.R. b0f9960b8a Raise the limit to 100 2025-12-16 13:36:40 +01:00
Stanko K.R. 15daa2bf05 Limit asignees to 10 per card 2025-12-15 16:40:52 +01:00
Kevin McConnell 87c6557747 Limit length of full name during signup
If we don't validate for length, then signups that overflow the database
columns will unnecessarily create and cancel a tenant. Adding a
validation means we can avoid this.
2025-12-15 09:56:35 +00:00
Adrien Maston 1f346085b9 Merge branch 'main' into mobile-app/scoped-stylesheets
* main: (119 commits)
  Bust comment view cache
  Update lexxy to bring fixes from https://github.com/basecamp/lexxy/releases/tag/v0.1.24.beta
  Fix unexpected remove empty line from README
  Lightbox uses Stimulus target callbacks instead of data-action
  Add test coverage for the image lightbox
  Add tests for tenancy middleware and timezone cookie
  Refactor: Simplify TimeWindowParser using Rails convenience methods
  SMTP: support SMTPS on port 465 (#2132)
  Bump fizzy-saas to retain fewer docker images (#2134)
  Add test coverage for with_golden_first scope (#2130)
  Refactor: improve query scope composition with merge syntax (#2131)
  Validate avatar sizes
  Introduce Vips configuration
  Tailscale serve support (#2126)
  Update tests with final method naming: record! -> record
  CSP config to allow Minio in development
  Update fizzy-saas to get employee restriction in staging
  Drop staff restriction in beta and staging
  Unused
  Use new FIZZY_GH_TOKEN with limited access
  ...
2025-12-15 10:43:32 +01:00
Italo Matos cd4fbc011c Refactor: Replace reverse_merge with with_defaults for improved readability
Replace all occurrences of reverse_merge with with_defaults across the codebase.
The with_defaults method provides clearer intent and better readability when
setting default values for hash parameters.

Changes:
- app/helpers/columns_helper.rb: Update column_frame_tag method
- app/models/user/email_address_changeable.rb: Update generate_email_address_change_token method
- app/models/account.rb: Update create_with_owner method
- app/controllers/concerns/filter_scoped.rb: Update filter_params method

This refactoring maintains the same functionality while improving code clarity.
2025-12-14 14:22:06 -03:00
Jorge Manrubia 01a5d194ba Merge pull request #2135 from italomatos/refactor/improve-time-window-parser-readability
Refactor: Simplify TimeWindowParser using Rails convenience methods
2025-12-14 09:42:07 +01:00
Jorge Manrubia d7e5d4218f Merge pull request #2032 from tomycostantino/update-columns-on-actions
Fix: board columns actions are stale when moving a column moves
2025-12-14 09:28:57 +01:00
Italo Matos 5ade7e1a52 Refactor: Simplify TimeWindowParser using Rails convenience methods
- Replace `beginning_of_day..end_of_day` with `all_day`
- Replace `beginning_of_week..end_of_week` with `all_week`
- Replace `beginning_of_month..end_of_month` with `all_month`
- Replace `beginning_of_year..end_of_year` with `all_year`

These changes improve code readability by using idiomatic Rails
methods that accomplish the same thing in a more concise and
expressive way.
2025-12-13 16:31:48 -03:00
Mike Dalessio 3584df6816 Merge pull request #2133 from basecamp/flavorjones/handle-image-uploads-better
Improve avatar image handling
2025-12-13 13:25:54 -05:00
Ítalo Matos fbc586646f Refactor: improve query scope composition with merge syntax (#2131)
* Refactor: improve query scope composition with merge syntax

Replace manual WHERE clause concatenation with Rails' merge method
for more elegant and maintainable scope composition across Card,
Comment, and Filter models. This approach better follows Rails
conventions and improves code readability.

* Extend scope composition improvements to Card::Closeable

Apply the same nested hash syntax pattern to closures table references
in order and where clauses.

* Remove unnecessary outer braces from where clause

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2025-12-13 10:10:13 -08:00
Mike Dalessio 139bf3cf81 Validate avatar sizes 2025-12-13 13:05:30 -05:00
Stanko K.R. c8a5d01771 Wrap join code redemption in a lock 2025-12-12 09:59:42 +01:00
Stanko K.R. a4d11e169f Replace custom code generator with Base32 2025-12-12 07:45:23 +01:00
Ítalo Matos 2e33262960 Refactor: Use Rails range syntax in ActivitySpike query (#2080)
Replace SQL string syntax with Rails range syntax for date filtering
in the ActivitySpike::Detector. This improves code readability and
follows Rails idioms.

Changed from:
  .where("created_at >= ?", recent_period.seconds.ago)

To:
  .where(created_at: recent_period.seconds.ago..)

This modernizes the codebase while maintaining the same functionality.
2025-12-11 20:40:31 -08:00
Ítalo Matos 0833c52aa0 Refactor: use idiomatic .last instead of .order(:desc).first (#2098)
Simplifies the last_event method in ActivitySpike::Detector by using
the more idiomatic Rails pattern .order(:created_at).last instead of
.order(created_at: :desc).first. Both generate the same SQL query but
.last is more readable and conventional in Rails codebases.
2025-12-11 20:35:38 -08:00
Jankees van Woezik 78fc80cf35 API: Allow updates to last_active_at (#2076)
* Allow Card#last_updated_at to be set

This is useful when doing an import from another system. I'm currently
working on a script to import our Github issues into Fizzy.

This is discussed in
https://github.com/basecamp/fizzy/pull/2056#discussion_r2609560246

* Add nil fallback and expand test coverage for last_active_at

Adds a safety fallback to Time.current if created_at is unexpectedly nil
during card creation.

Test coverage to verify:
* last_active_at defaults to created_at when not provided
* last_active_at can be updated via API on existing cards
* import workflow where last_active_at is restored after comments
* publishing doesn't overwrite explicit last_active_at values

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2025-12-11 19:06:03 -08:00
Adrien Maston 865fbdcf66 Merge branch 'main' into mobile-app/scoped-stylesheets
* main: (117 commits)
  Explain that the upload URL is account-scope
  Allow direct uploads via API
  Storage: ignore jobs for now-deleted targets
  API: Support `created_at` for API card and comment creation (#2056)
  Enforce CSP (#2070)
  CSP: full config with env vars per source (#2069)
  Speedy, auditable, deadlock-resistant storage tracking (#2026)
  Gitleaks: ignore legit non-sensitive API keys and tokens in docs/ and test/ (#2068)
  Get gitleaks-audit green again
  Bump actions/checkout from 4 to 6 (#2047)
  Bump docker/login-action from 3.5.0 to 3.6.0 (#2046)
  Bump docker/metadata-action from 5.8.0 to 5.10.0 (#2045)
  Bump sigstore/cosign-installer from 3.9.2 to 4.0.0 (#2044)
  make MySQL SSL mode configurable via env var (#2036)
  Update tip text for turning a card into a Golden Ticket
  Revert "Fix Lexxy prompt list padding by lowering rich-text specificity"
  Fix Lexxy prompt list padding by lowering rich-text specificity
  Improve phrasing
  Fix crash due to missing partial
  Fix status and filter mistakes
  ...
2025-12-11 13:42:48 +01:00
Kevin McConnell dece2d08d1 Update namespacing & naming 2025-12-11 11:21:09 +00:00
Kevin McConnell 0e443d3602 Use initializer to configure tenant mode 2025-12-11 11:20:43 +00:00
Kevin McConnell a5580666a7 Single tenant by default & update tests 2025-12-11 11:20:43 +00:00
Tomas Costantino 52fcca9438 remove unnecessary callback 2025-12-11 10:25:34 +01:00
Tomas Costantino fb0b787f10 rename surroundings to adjacent columns 2025-12-11 10:25:17 +01:00
Tomas Costantino df7e597066 Merge branch 'main' into update-columns-on-actions 2025-12-11 10:17:15 +01:00
Adam Haris f76309d116 Merge branch 'main' into single_tenant 2025-12-11 06:08:01 +01:00
Jankees van Woezik 477b4d65f2 API: Support created_at for API card and comment creation (#2056)
* Add support to set `created_at`

Discussed in
https://github.com/basecamp/fizzy/pull/1766#issuecomment-3637846074,
this allows users to import cards from another system with entries from
the past. For instance I'm importing all our Github issues (including
the closed onces) to Fizzy.

* Iron out published state tracking

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2025-12-10 19:57:38 -08:00
Jeremy Daer 761d0b4a76 Speedy, auditable, deadlock-resistant storage tracking (#2026)
An append-only storage ledger replaces deadlock-prone synchronous
counter updates and drift-prone async updates.

All content storage (card images, card/comment/board description embeds)
is tracked. Account exports (which expire) and avatars are not tracked.

Storage is accounted for by Account and Board. Both consume the same
event stream independently: no bubble-up storage bumps triggering
deadlocks due to lock sequencing. Each calculates its own total from
the underlying ledger with independent cursors and materialization.

* Storage::Entry: Append-only ledger recording attach/detach/transfer
  events with delta bytes. Single event stream indexed for both
  Account and Board cursor queries.

* Storage::Total: Polymorphic snapshot cache with cursor (last_entry_id)
  tracking which entries have been materialized.

* Storage::Totaled concern: Provides bytes_used (fast snapshot) and
  bytes_used_exact (snapshot + pending) query modes, plus
  materialize_storage! to roll up pending entries.

* Storage::Tracked concern: For models owning attachments (Card,
  Comment, Board). Provides board_for_storage_tracking for models
  where board is determined differently (Board returns self).
  Handles board transfers by recording transfer_out/transfer_in entries.

* Storage::AttachmentTracking: Hooks ActiveStorage::Attachment lifecycle
  to record attach/detach entries. Handles ActionText::RichText embeds
  by traversing to the actual model. Snapshots context in before_destroy
  to handle cascading deletes where parent record may be gone by
  after_destroy_commit.

* MaterializeJob: Rolls up pending entries into snapshot. Concurrency
  limited per owner to prevent duplicate work.

* ReconcileJob: On-demand reconciliation against actual attachment
  storage for support/debugging. Compares ledger total to real bytes
  from card images, card embeds, comment embeds, and board embeds.

Usage:
* account.bytes_used / board.bytes_used: fast, slightly stale bytesize
* account.bytes_used_exact / board.bytes_used_exact: real-time bytesize
* Storage::Entry: audit trail for debugging and point-in-time queries
2025-12-10 16:04:30 -08:00
Mike Dalessio 6c73049261 Merge pull request #2053 from basecamp/flavorjones/fix-system-comment-injection
Prevent HTML injection into system comments (card activity feed)
2025-12-10 11:01:34 -05:00
Mike Dalessio 83360ec6f3 Escape the names used to generate system comments
including user names, board titles, column names, and card titles.
2025-12-10 10:38:42 -05:00
Mike Dalessio fa118a7d83 Add validation for the join code usage limit
which is preferred to generating a database error
2025-12-10 10:10:08 -05:00