Commit Graph

373 Commits

Author SHA1 Message Date
Stanko K.R. 017bcc9ce1 Polish up Passkey interface
- Fix indentation
- Split awkward initializer into separate methods
- Rename credentials to passkeys
- Simplify authenticator registry loading
- Flatten nested errors under ActionPack::WebAuthn
- Set passkey current params only requests that use it
- Inline anemic methods
- Replace custom validation with ActiveModel::Validation
- Rename identity to holder in Passkey
- Rename credentials to passkeys in JS
- Extract framework library out of controllers
- Pass params hashes down to ActionPack
- Attempt to simplify public interface
- Push data decoding down to the classes representing the data
- Introduce has_passkeys
- Add CBOR bigint support
- Rename ActionPack::WebAuthn::Passkey to ActionPack::Passkey
- Add create_passkey_button helper
- Rename public-key to creation-options
- Add sign_in_with_passkey_button helper
- Dispatch events for the whole Passkey lifecycle
- Add ED25519 support
- Prevent crash on missing meta tag
- Validate resident key options
- Don't clobber existing ActionPack config options
- Validate cryptographic params
- Move CurrentWebAuthnRequest into ActionPack::Passkey
- Use ActiveModel::Attributes for Options objects
- Implement expiring challanges
- Add lifecycle events
- Extract param helpers into Request
- Add passkey_creation_options and passkey_request_options helpers
- Add create_passkey_challenge to make it easier to override the create method if needed
- Prefix all view helpers with passkey_
- Auto-include ActionPack::Passkey::Holder
- Make the passkey challange url configurable
- Add a reminder about Passkeys to the magic link email
2026-03-18 11:51:10 +01:00
Stanko K.R. fbe9252db1 Auto-suggest name for new Passkeys
- Use plain client data json everywhere
- Expose AAGUID and backed_up on Credentials
- Make attestation verifiers configurable
- Auto-suggest names for passkeys and show icons
2026-03-18 11:49:59 +01:00
Stanko K.R. 6c58fd9bdd Implement Passkey registration
- Create Identity credentials
- Add management UI and registration flow
2026-03-18 11:48:52 +01:00
Jeremy Daer ed81ca760f Restore unique index on board_publications.key (#2568)
* Restore unique index on board_publications.key

The account_id rollout (fe6df7085) replaced the unique index on
board_publications.key with a non-unique composite (account_id, key)
index. This was unintentional — other tables in the same migration
preserved uniqueness (account_join_codes, tags) but this one did not.

Without global uniqueness on key, a crafted data import can insert a
duplicate publication key via insert_all! (which bypasses model
validations). Since find_by_published_key queries globally without
account scoping, this enables public board URL hijacking after the
legitimate owner unpublishes.

Restore the original unique index on key and keep a plain account_id
index for account-scoped queries.

* Reorder index operations: add unique index before dropping composite

On MySQL, DDL is non-transactional. If the unique index creation
fails (e.g. duplicate keys exist), the previous ordering would leave
the composite index already dropped. Adding the unique index first
means a failure leaves the database unchanged.

* Add schema dumps for both SQLite and MySQL

Dump both schema files after running the migration against each
adapter to keep the PR complete for CI.
2026-02-18 11:21:25 -08:00
Jeremy Daer 2d7950879d Normalize schema_sqlite.rb to reflect TableDefinitionColumnLimits initializer (#2569)
The table_definition_column_limits initializer (PR #1669) applies a
default limit of 255 to all string columns during schema:load, but
schema_sqlite.rb was never re-dumped after it landed. This caused
drift on every subsequent migration PR that dumped the schema.

Re-dump from schema:load → schema:dump to make the file idempotent.
2026-02-18 11:13:13 -08:00
Jeremy Daer bc76633f77 Reduce webhook delivery cleanup impact (#2550)
* Add index on webhook_deliveries.created_at

The stale scope (WHERE created_at < ?) and ordered scope
(ORDER BY created_at DESC) both benefit from this index.
Without it, Webhook::Delivery.cleanup does a full table scan.

* Run webhook delivery cleanup every 15 minutes

Each run now deletes ~15 minutes of newly-stale records instead
of ~4 hours worth, a 16x reduction in per-run write volume.
2026-02-16 14:28:05 -08:00
Stanko K.R. 22276a25c8 Index notifications on updated_at
As we now update notifications with unread counts we sort them by the
time they were updated instead of when they were created. To keep that
performant we also have to change the previous indices that used
created_at to use updated_at.
2026-02-12 11:36:46 +01:00
Stanko K.R. 36ee253a1a Stack notifications everywhere
We had client-side notification stacking in the tray since launch, but now we want to stack notifications in the notifications page, in API responses and in email bundles.
2026-02-12 10:29:50 +01:00
Stanko K.R. c84d783b2a Add card_id and unread_count to notifications 2026-02-12 08:07:51 +01:00
Stanko K.R. 37d7f5c569 Give more detail about why the import failed 2026-02-11 13:47:21 +01:00
Stanko K.R. 3f2a91922a Regenerate schemas 2026-02-02 12:36:48 +01:00
Stanko K.R. 19ae555c2e Convert Account::SingleUserExport to User::DataExport 2026-02-02 12:36:48 +01:00
Stanko K.R. 8953920652 Implement full account exports 2026-02-02 12:36:48 +01:00
Stanko K.R. 6f17e3867d Split Export model into user and whole-account export 2026-02-02 12:36:31 +01:00
Mike Dalessio 1570c16f67 Make Reaction polymorphic
Add migration to replace comment_id with polymorphic reactable
association (reactable_type, reactable_id), enabling reactions on both
comments and cards.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 12:29:54 -05:00
Stanko Krtalić eceb6c9b43 Add self-service account deletion (#2246)
* Add self-service account deletion

* Disable access to cancelled accounts & implement Stripe interactions

* Add tests

* Fix failing tests

* Remove cancelled accounts from lists

* Fix incorrect redirect

* Use _path instead of _url for consistency

* Don't track how far the inicieration job got

We still want the step tracking so that the job can be interrupted. But, since the scope is idempotent, we don't need to track how far it got.

* Specify the exact time when the data will be deleted

* Fix crash due to unadvancable cursor

* Rename up_for_incineration to due_for_incineration

* Regenrate the schema

* Fix incorrect path check

* Migrate the SQLite schema

* Only show the cancel button on cancellable accounts

* Check that a subscirption method exists before calling it

* Ignore job failures due to missing records when an account gets deleted

* Skip sending notifications on cancelled accounts

* Use collbacks to integrate

* Add a blank line between queue_as and discard_on

* Break checks into methods

* Inline methods

* Rename Account::IncinerateJob

* Run migrations

* Migrate SQLite
2026-01-12 14:21:05 +01:00
Jorge Manrubia 6d05ab428e Remove engagements
We are not using this anymore!
2026-01-07 16:43:33 +01:00
Mike Dalessio 27093e4e78 Remove unnecessary recurring execution cleanup task
This task is unnecessary because the
`solid_queue_recurring_executions` table has a foreign key to
`solid_queue_jobs` with `on_delete: :cascade`.

This config was cargo-culted from HEY (in 44429644), which *does* need
this task to run because we've omitted the FK constraints there.
2026-01-04 11:12:01 -05: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
David Heinemeier Hansson 0ba43de61a This had gotten stripped 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 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
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
Mike Dalessio 6345aa4787 Update seeds to use @example.com
See also https://www.rfc-editor.org/rfc/rfc2606.html
2025-12-03 15:40:58 -05:00
Mike Dalessio 8064e017dc db:seeds create staff users for testing in development 2025-12-03 15:29:36 -05:00
Kevin McConnell e16cc21b0a Add "data export" feature
- Adds a button in Account Settings where you can request a ZIP export of your
  Fizzy data
- Export files are created in the background. When ready, a link to
  download them is sent to the requester.
- Exports expire after 24 hours. And are limited to 10 per day.
2025-12-01 15:23:26 +00:00
Mike Dalessio edf6b53469 Introduce an "owner" role, and prevent it from being administered
to prevent the owner from being demoted or kicked out.

ref: https://app.fizzy.do/5986089/cards/3213
2025-11-29 14:13:29 -05:00
Stanko K.R. fd06e5d076 Migrate sqlite schema 2025-11-29 15:29:52 +01:00
Stanko K.R. 2311efd03e Make sign up Magic Links work across devices
Previously if someone started signing in on their phone, but finished it on their laptop, they'd end up on the menu screen with no account. Bu tracking the purpose of a Magic Link we can always direct the user to sign up if they requested a magic link through sign up. This also makes the logic for changing the copy in the email more robust.
2025-11-29 12:36:00 +01:00
Jorge Manrubia 4e09352c09 Bring simple signup flow from the fizzy-saas gem
We skip the QB code and we fill external account ids automatically on creation with a sequence

See:
https://github.com/basecamp/fizzy-saas/pull/7
2025-11-28 15:53:58 +01:00
Jorge Manrubia 2061ab4ab2 Use dedicated sequence record instead of deal with issues with ids and uuids when using the previous approach 2025-11-28 15:53:58 +01:00
Jorge Manrubia 07b4c55185 Auto-increment external_account_id automatically by default to allow upcoming simple signups 2025-11-28 15:53:58 +01:00
Mike Dalessio b78977f563 Make db/seeds.rb rerunnable when I want to add more cards. 2025-11-27 10:46:48 -05:00
Donal McBreen a5fc6eeaec Update SQLite schema 2025-11-26 10:18:55 +00:00
Donal McBreen c4498212dc Merge branch 'main' into sqlite
* main: (116 commits)
  Ensure avatar thumbnails are square
  Update useragent to recognize twitterbot/facebot
  Add defensive styles for non-square avatar images
  Update test for copy changes
  Missed commit
  AI: standardize on https://agents.md
  Make it clear this is just notifications, not comprehensive activity
  AI: configure MCP servers for Chrome, Grafana, and Sentry (#1727)
  Allow requests from Google Image Proxy
  Update to basecamp's useragent fork
  Clean up a little bit the CSRF reporting code
  Claude: production observability guidance (#1725)
  Prevent autoscroll to the root columns container to prevent jump on page load
  Include full name string so you can type your name to filter
  Prioritize current user and assigned users in assignment dropdown
  Check and report on Sec-Fetch-Site header for forgery protection
  bundle update
  Bump bootsnap from 1.18.6 to 1.19.0
  Bump rails from `077c3ad` to `17f6e00`
  Fix cards getting stuck in edit mode
  ...
2025-11-26 10:07:41 +00:00
Donal McBreen 97bcdf1853 Enforce column limits via concern
SQLite columns lengths are purely informational, so we'll enforce the
limits via a concern that checks the lengths from the schema.
2025-11-25 15:32:34 +00:00
Donal McBreen 67f648f356 Enforce MySQL string/text limits in SQLite
To ensure consistent column lengths, we'll add limits to string and text
columns which match MySQL defaults.
2025-11-25 14:50:40 +00:00
Stanko K.R. f64a49dcc0 Determine staff members based on flag
This replaces the old system based on email addresses, the aim is to make it more generic and thus appropriate for OSS/self-hosting

See: https://3.basecamp.com/2914079/buckets/37331921/todos/9302705265#__recording_9320051455
2025-11-25 14:09:31 +01:00
Stanko K.R. cc49a1b772 Allow long user agent strings
Copied this from BC and HEY which also use a string with a 4096 character length

See: https://app.fizzy.do/5986089/cards/3066
2025-11-25 12:09:02 +01:00
Donal McBreen b92aba383e account_key is MySQL specific 2025-11-21 11:47:28 +00:00
Donal McBreen e76f159f05 Gate search_record shard migrations on adapter type 2025-11-21 11:44:37 +00:00
Donal McBreen 366dbd393c Merge branch 'main' into sqlite 2025-11-21 11:39:47 +00:00
Donal McBreen cb71af8231 Remove the old fulltext indexes
We only search by account_key, content, and title now.
2025-11-21 11:25:16 +00:00
Donal McBreen 74d1cf735b Only add and fill account_key 2025-11-21 10:20:27 +00:00
Donal McBreen 70dd754cf7 Account key for search records
Add an account key field to improve search performance. This field
allows us to filter the records by account directly in the fulltext
index, so we only need to examine rows belonging to the relevant account.
2025-11-21 09:38:35 +00:00
Donal McBreen b4e3d08689 Add long back to dumped schema, need to check why it wants to change it though 2025-11-21 09:15:19 +00:00
Donal McBreen 5edb0a2075 Update schema 2025-11-21 09:15:19 +00:00
Donal McBreen bb36b4846f Support SQLite searching 2025-11-21 09:15:19 +00:00
Mike Dalessio 1bbb6d0c03 Fix race condition in Card::ActivitySpike::DetectionJob
The check-then-act pattern in `register_activity_spike` has been
replaced with `find_or_create_by!` to eliminate the race condition
that could lead to creating multiple activity spikes for a card. To
support this change, the `card_id` index on `cards_activity_spikes`
has been made unique.

ref: https://app.fizzy.do/5986089/cards/3063
2025-11-20 15:40:42 -05:00