Commit Graph

1598 Commits

Author SHA1 Message Date
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
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
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
Adam Haris eb8655dc8e refactor: use MULTI_TENANT env variable, model concern instead of controller (#accepting_signups?) 2025-12-10 15:14:22 +01:00
Stanko K.R. 75f273c6fa Fix Current not setting a session in some contexts 2025-12-10 09:23:52 +01:00
Stanko K.R. 259707bf70 Fix identity tests 2025-12-10 09:23:52 +01:00
David Heinemeier Hansson 79fc57a82a Use built-in authenticate_or_request_with_http_token
Hat tip to @adrienpoly
2025-12-10 09:23:52 +01:00
David Heinemeier Hansson 0ce3a85778 Only allow writing when the access token has permission 2025-12-10 09:23:52 +01:00
David Heinemeier Hansson caa4cd491e Smooth out the finder API 2025-12-10 09:23:52 +01: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 660fcff558 Authenticate api requests without needing 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
Adam Haris bec6036533 refactor 2025-12-10 07:36:03 +01:00
Robin Brandt d60643f5ef Allow chromium unstable endpoint 2025-12-09 20:24:09 -05:00
Adam Haris 0780b5168f add env variable to disable multi-tenancy 2025-12-09 15:40:01 +01: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 bec46e15a9 Drop defunct Account upload attachments (#2030)
Controller and routes removed in 6a62df470c
2025-12-09 00:36:39 -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
Jorge Manrubia 31a41e66c2 Make column have a proper ID instead of inferring it from the title 2025-12-07 11:34:54 +01:00
Mike Dalessio 95ba2c01b8 Add email validation to Signup 2025-12-06 16:50:39 -05:00
Stanko K.R. 73c2b00211 Rever to using Ssrf instead of SSRF
Ref: https://github.com/basecamp/fizzy/pull/1967#discussion_r2593750027
2025-12-06 11:04:55 +01: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
Stanko K.R. cd3751f4c1 Fix Resolv::DNS always returning no results 2025-12-05 20:09:39 +01:00
Jorge Manrubia 59ff50c1cb We can remove ad-hoc handling now that we rely on page refreshes in the card perma 2025-12-05 10:11:24 +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
Andy Smith bcad6b913c Update pinned cards when the title changes 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
Jeremy Daer 496851b255 Security: Web Push SSRF and IP range bypass
Add SSRF protection for web push endpoints:
- Resolve endpoint IP once and pin it for connection
- Validate endpoints resolve to public IPs
- Whitelist permitted push service hosts

Add missing IP ranges to SsrfProtection:
- 100.64.0.0/10 (Carrier-grade NAT, RFC6598)
- 198.18.0.0/15 (Benchmark testing, RFC2544)

Note: link-local (169.254.0.0/16) is already covered by ip.link_local?
2025-12-04 21:35:55 -08:00
Mike Dalessio 00eee29837 Validate Identity email address
using the "standard" email regexp URI::MailTo::EMAIL_REGEXP. The form
field will validate this in the browser, but if bots are creating
identities, they can put whatever they want in here. So let's add some
protection against that.

The HtmlHelper regex was renamed here to avoid confusing Brakeman,
which does imprecise constant lookup and was confusing the two
constants, one of which uses `\A` and `\z` and the other does
not (intentionally).

ref: https://app.fizzy.do/5986089/cards/3276
2025-12-04 14:01:15 -05:00
Kevin McConnell 2e47749739 Don't allow SVG avatar uploads in the first place 2025-12-04 14:27:28 +00:00
Kevin McConnell 6475ad3425 Don't try to resize non-variable avatars 2025-12-04 13:36:21 +00:00