Commit Graph

660 Commits

Author SHA1 Message Date
Jorge Manrubia db804b8450 Merge pull request #2299 from Maklu/fix-cjk-search
Add CJK (Chinese, Japanese, Korean) search support
2026-01-08 12:00:46 +01:00
Jorge Manrubia e34716a537 Merge pull request #2221 from pinzonjulian/patch-1
Fix test state pollution caused by filter/search_test
2026-01-07 16:40:18 +01:00
Jorge Manrubia 8efb778f53 Merge pull request #2237 from dilberryhoundog/fix-closed-cards-from-not_now
Fix: destroy "not now" state when closing cards, enables "indexed_by=closed" expected behaviour
2026-01-07 16:15:14 +01:00
LU 366588e263 Add SQLite FTS5 support for CJK search
- Split each CJK character into individual tokens for FTS5 indexing
- Add stem_content callback to SQLite adapter to pre-process content
- Stem search queries before matching against FTS5 index
- Update stemmer tests to reflect character-splitting behavior

This enables CJK search on SQLite installations where the FTS5
tokenizer cannot natively segment CJK text.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 17:51:20 +09:00
LU 2a4fbecbda Add CJK (Chinese, Japanese, Korean) search support
The search functionality was silently dropping CJK characters because:

1. Query sanitization used `\w` which only matches ASCII word characters
2. Stemmer split by whitespace, which doesn't work for CJK languages
3. Highlighter used `\b` word boundaries that don't apply to CJK

This commit fixes all three issues:

- Query: Use `\p{L}\p{N}` (Unicode letters/numbers) instead of `\w`
- Stemmer: Preserve CJK characters as-is without stemming, since CJK
  languages don't have stemming rules like English
- Highlighter: Skip word boundary matching for CJK terms

Also extracts `CJK_PATTERN` to `Search` module to avoid duplication.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-06 17:22:42 +09:00
Jorge Manrubia 85bfa7aa48 Rename method 2026-01-05 16:01:25 +01:00
Jorge Manrubia 5f38e8957e Reapply "Make sure new card drafts are refreshed when reused"
This reverts commit 045ddf78f3.
2026-01-05 15:58:29 +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
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
Dylan a005391724 Fix: allow destroying "not now" state when closing cards
- Update `Card::Closeable` to destroy associated "not now" state during close.
- Add new tests to verify closure behavior for different column types, including "not now".
2025-12-23 13:43:03 +08:00
Stanko K.R. 0df667f4fb Fix HTML injection in webhooks through card titles 2025-12-22 12:20:32 +01:00
Julián Pinzón Eslava 12928f530b Refactor search test to use instance variables
The included SearchTestHelper disables transactional tests and has a specific setup that does not use fixtures.

This test however, ignores the setup and uses fixtures directly by creating comments and publishing cards making test state unpredictable. 

By using the records set by the helper instead of fixtures, the state of the system is not polluted.
2025-12-21 00:23:21 +11: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
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
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
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
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
Ítalo Matos 2066109003 Add test coverage for with_golden_first scope (#2130)
* Add test coverage for with_golden_first scope

Test verifies that the with_golden_first scope correctly orders
golden cards before non-golden cards in query results.

* Refactor golden test: use instance variables, add subordering coverage

Extract @golden and @non_golden fixtures into setup for reuse across
all tests. Simplify with_golden_first test to verify both primary
ordering (golden before non-golden) and subordering preservation.

---------

Co-authored-by: Jeremy Daer <jeremy@37signals.com>
2025-12-13 10:34:35 -08:00
Mike Dalessio 139bf3cf81 Validate avatar sizes 2025-12-13 13:05:30 -05:00
Jeremy Daer 43ab17df2f Update tests with final method naming: record! -> record 2025-12-12 21:47:10 -08:00
Stanko K.R. a4d11e169f Replace custom code generator with Base32 2025-12-12 07:45:23 +01: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
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
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 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
David Heinemeier Hansson 895b0e13b8 Drop the need for access tokens to have a session 2025-12-10 09:23:52 +01:00
David Heinemeier Hansson db29562c4c Tie access token directly to session
We need to present them differently in the session list and prevent them
from being deleted
2025-12-10 09:23:52 +01:00
David Heinemeier Hansson a81c681a8d Add access token authentication via HTTP AUTHORIZATION bearer header 2025-12-10 09:23:52 +01:00
Robin Brandt d60643f5ef Allow chromium unstable endpoint 2025-12-09 20:24:09 -05:00
Jorge Manrubia 94c59a2399 Merge pull request #2033 from basecamp/limit-webhook-response-size
Stream response in webhook request manually to check size
2025-12-09 12:16:42 +01:00
Rosa Gutierrez 29c7926307 Stream response in webhook request manually to check size
This addresses a DoS vulnerability where the response might be massive
leading to OOM errors, as the response is read in full in memory by
default.

To prevent this, we need to read the body in chunks, checking the
size of the chunks we've read and raising if we go over a certain limit.

I've set the limit to 100 KB because the responses to these requests
should be fairly small or even empty, and we only care about the status
code in the end.
2025-12-09 11:25:12 +01:00
Jeremy Daer 49c4f2adc6 Fix ActiveStorage FileNotFoundError with immediate variant processing (#2022)
When ActiveStorage::Record uses `connects_to` for read replica support,
it creates a separate connection pool from ApplicationRecord. This causes
`after_commit` callbacks to fire in non-deterministic order - the
Attachment's `create_variants` callback can fire before the User model's
upload callback completes, resulting in FileNotFoundError.

The fix removes replica connection configuration from ActiveStorage::Record
so it shares the same connection pool as application models, ensuring
proper callback ordering.

Also reverts test workarounds that were added to work around this issue,
since the root cause is now fixed.

See: https://github.com/rails/rails/issues/53694
2025-12-08 14:44:49 -08:00
Jorge Manrubia cb0e9b9962 Immediate avatar and embed variants (#2002)
Reverts #2001
Restores #1955
2025-12-08 14:17:06 -08:00
Mike Dalessio 3c4b838b25 Fix Identity destruction callback ordering
ref: #2003

Co-authored-by: Dylan <dylan@restaurantcare.com.au>
2025-12-08 15:12:38 -05:00
Jorge Manrubia c8c91259c7 Revert "Immediate avatar and embed variants" 2025-12-07 12:06:03 +01:00
Jorge Manrubia 568783efd1 Merge pull request #1905 from basecamp/web-push-ssrf
Security: Web Push SSRF
2025-12-07 11:58:14 +01:00
Jorge Manrubia 91017c9208 Merge pull request #1955 from basecamp/immediate-variants
Immediate avatar and embed variants
2025-12-07 11:50:02 +01:00
Mike Dalessio 95ba2c01b8 Add email validation to Signup 2025-12-06 16:50:39 -05:00
Mike Dalessio 3ff45d77bb Ensure test coverage for invalid Signup#complete 2025-12-06 16:46:51 -05:00
Mike Dalessio d747d1bfa3 Ensure we have good test coverage on Signup.create_identity
because we're relying on it returning a MagicLink
2025-12-06 16:40:17 -05:00
Mike Dalessio 1ad52d2590 Only send notification emails to verified users
Adds verified? check to bundling_emails? to prevent notification emails
from being sent to users who have never authenticated. This closes the
spam vector where bad actors could create users for known email
addresses and trigger unwanted notifications by mentioning them.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 21:51:44 -05:00
Mike Dalessio 4602cd3cdd Add verified_at timestamp to use for spam prevention
User are marked as verified after a join code is redeemed. The user is
redirected to Users::VerificationsController, either:

- after submitting a valid magic link code,
- or immediately after redeeming the join code (if they're already
  authenticated with the correct identity)

Account owners are automatically verified when the account is
created (because they have already provided a magic link code at that
point).

This sets up for later commits that will backfill existing users and
require verification before sending notification emails.

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 21:51:44 -05:00
Stanko K.R. 1b3d7d4276 Fix ipaddr setter 2025-12-05 20:43:56 +01:00
Jorge Manrubia c89db89f2a Only broadcast when the preview changes, use the _later variant for the broadcast, add tests 2025-12-05 10:09:20 +01:00
Jeremy Daer 21f3f72647 Immediate avatar and embed variants
Process variants synchronously on attachment to close the window between
image upload and variant availability, guaranteeing that we won't have
lazy variant processing attempts in GET requests.

Tradeoff is that we do variant processing in upload requests, which is
actually desirable. We're working with images that should take
milliseconds to resize given that we'll already have the file on hand.

References https://github.com/rails/rails/pull/51951
2025-12-04 23:54:37 -08:00