Commit Graph

105 Commits

Author SHA1 Message Date
Stanko K.R. f5ae2f8076 Rename extract_passkey_component_options to partition_passkey_options 2026-03-25 17:26:50 +01:00
Stanko K.R. 693415bc71 Rename request options to authentication options 2026-03-25 17:26:49 +01:00
Stanko K.R. df7c2678f3 Rename create_options to registration_options 2026-03-25 17:26:49 +01:00
Stanko K.R. 8e4f9ce5e7 Cleanup form helpers 2026-03-25 17:26:49 +01:00
Stanko K.R. 1983014be6 Rely solely on the challange signature for verification
The cookie approach seems like the more secure aproach because it ties the authentication or registration attempt to the user's browser session, but it doesn't work reliably on Chrome for Windows. Also, a simila problem pops up on Chrome for Linux if the session is used instead of a separate cookie. It looks like the browser doesn't propagate the state change through fast enough which results in some requests contaiining the new/updated cookie, and others don't, which results in sporadic failures. Since we use a signed and expiring challange we still get protection from replay attacks and tampering which enables us to omit the cookie entierly and rely on the challange's signature to prove expiration and authenticity. The only thing we lose is the ability to tie and attemp to a single browser session.

See: https://github.com/w3c/webauthn/wiki/Explainer:-WebAuthn-challengeURL which proposes to add the same challange fetching logic as part of the standard

See: https://github.com/w3c/webauthn/issues/1856 which discusses issues that arise from having expiring challanges (which the spec recommends)

See: https://github.com/OneUptime/oneuptime/security/advisories/GHSA-gjjc-pcwp-c74m which is an explot that can happen if the server isn't able to verify the authenticity of challanges that are sent outside of a cookie
2026-03-25 17:26:49 +01:00
Stanko K.R. 4fb1cddb4c Use WebComponents for buttons
This simplifies the loading and cleanup logic, while providing ubiquitous support across JS frameworks. Buttons can now be added in any way imaginable and still work without requiring additional initialization. The upside of this aproach is that it doesn't require a mutation observer nor a global click listener, and is supported by all browsers that also support passkeys.
2026-03-25 17:26:49 +01:00
Stanko K.R. c24d4fd7fd Use Same-site: Lax
This is a guess based on a few articles I read where Windows Hello doesn't play nice with Same-Site: Strict. I have no credible evidence to back this up.
2026-03-24 15:17:05 +01:00
Mike Dalessio 8c3461a1d6 Remove @regexp_timeout_reported from AutoLinkScrubber
No longer needed since the scrubber is not memoized across calls and the
text node length guard prevents the performance issue.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 19:53:02 -04:00
Mike Dalessio 87c659cd67 Skip auto-linking in very large text nodes
A large comment body was causing Regexp::TimeoutError in production.
The URI regexp isn't catastrophically backtracking — it's linear — but
with a long enough string it exceeds the 1s Regexp.timeout set by
Rails.

Skip scanning text nodes over 10KB, which is well beyond any
reasonable content for auto-linking and still keeps us many orders of
magnitued under the timeout on an unloaded machine.

Fixes FIZZY-Q4

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 16:18:19 -04:00
Zacharias Dyna Knudsen b0fa6525fe Stop scrubbing after first regexp timeout 2026-03-20 12:13:17 +01:00
Zacharias Dyna Knudsen 7399ec5bd9 Rescue regexp timeout 2026-03-20 11:55:00 +01:00
Stanko K.R. 1cf1f598e1 Require resident keys by default 2026-03-18 12:14:17 +01:00
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. da69039476 Implement Passkey authentication 2026-03-18 11:49:23 +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
Stanko K.R. 4bb0a2929c Implement WebAuthn protocol
- Add a CBOR decoder
- Add public key credential options
- Add COSE key decoder
- Add authenticator attestation and assertion
- Add credentials
- Add exclude_credentials
2026-03-18 11:47:48 +01:00
Mike Dalessio 4496a203e6 Quash the new Rails warnings about premature class loading (#2653) 2026-03-03 10:44:51 -05:00
Joseph Hale 451920adc7 Include concrete example motivating this module file 2026-03-02 15:25:11 -07:00
Joseph Hale 7611236a67 Simplify comment in skip-detached override
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:40:21 -07:00
Joseph Hale c61f49cce9 Extract detached-blob guard into its own file
Separate the attachment existence check from the broadcast suppression
override so each file has a single responsibility. The guard now lives
in ActiveStorageAnalyzeJobSkipDetached with its own documentation
explaining the upload-then-delete race condition.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:38:19 -07:00
Joseph Hale 143508fc22 Skip AnalyzeJob when blob has no attachments
Upload-then-delete races cause AnalyzeJob to hit S3 after PurgeJob has
already removed the object, producing Aws::S3::Errors::NoSuchKey noise
in solid_queue_failed_executions. Since PurgeOnLastAttachment destroys
the attachment row before enqueueing PurgeJob, checking
blob.attachments.exists? catches this — a fast DB query that avoids the
S3 round-trip entirely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:13:00 -07:00
Rosa Gutierrez fc0e477141 Include path in web push payload for backwards compatibility
Old service workers cached in browsers still read `data.path` from the
push notification payload. Include it alongside `data.url` so notification
clicks keep working until those service workers update.

Currently we only register the service worker in the notification
settings screen, so this won't happen very often. Once we introduce
offline mode, we'll register the service worker in more places, so we
should be able to remove this.
2026-02-25 20:17:16 +01:00
Fernando Olivares 7e0470a692 Send the URL instead of path in notifications 2026-02-25 19:31:13 +01:00
Mike Dalessio 97d8d6a395 Add "noopener" to autolinks' rel attribute 2025-12-29 12:21:02 -05:00
Mike Dalessio 4579f7cd61 Avoid string manipulation when autolinking.
Instead, let's use a Loofah scrubber which will create DOM nodes
directly. This should be faster and is a tiny bit simpler, as well as
removing a potential HTML injection vector.

Also, add "noreferrer" to all `mailto:` links (already present on URLs).
2025-12-29 12:21:00 -05: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
Mike Dalessio 18b3319a94 Extract hex_to_base36 and base36_to_hex methods in Uuid type
replacing normalize_base36. This should make it easier to convert
UUIDs between hex and base36 formats, useful when debugging production
logs that contain hex UUIDs.

For example, if in a log you see:

    User Load (0.3ms) SELECT `users`.* FROM `users` WHERE
    `users`.`account_id` = x'019afab815897a4f920f3a24fab75400' AND
    `users`.`id` = x'019afab815a2790688c58c7f8326e700' LIMIT 1

and you want to find that account or that user in the console, you can
now do:

    id = ActiveRecord::Type::Uuid.hex_to_base36("019afab815897a4f920f3a24fab75400")
    => "03f6bilrvt3oghzhurll4pp8g"
2025-12-16 14:16:55 -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
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 c4a8996562 Retry mailer jobs on common networking and SMTP errors
This concern is lifted nearly verbatim from Basecamp.

ref: https://app.fizzy.do/5986089/cards/3300
2025-12-04 10:26:24 -05:00
Jorge Manrubia 6ccf655597 Move yabeda/prometheus stuff to the gem 2025-11-28 15:53:58 +01:00
Jorge Manrubia f03fbba5ac Restore local CI, sensible setup for open source users 2025-11-28 15:53:58 +01:00
Jorge Manrubia 0328149ba2 Initial github action setup 2025-11-28 15:53:58 +01:00
Jorge Manrubia a5b02755b2 Add rake tasks for enabling/disabling saas mode 2025-11-28 15:53:58 +01:00
Jorge Manrubia 9132f6b835 Fix path to saas. 2025-11-28 15:53:58 +01:00
Jorge Manrubia ad6f3a9ff7 Instead of a dedicated group, use a completely separate Gemfile for the saas version
The group-based approach won't work if you don't have access to the gems!
2025-11-28 15:53:58 +01:00
Jorge Manrubia 03483a0526 Format 2025-11-28 15:53:58 +01:00
Jorge Manrubia fb9aa5dfac Extract common method to configure bundle 2025-11-28 15:53:48 +01:00
Jorge Manrubia bab914d4e0 Move db check to the Fizzy module 2025-11-28 15:53:48 +01:00
Jorge Manrubia 6134407f5e Make sass property depend on txt file or env var 2025-11-28 15:53:48 +01:00
Jorge Manrubia e1e9a01720 Make open source the default, and check for sass instead 2025-11-28 15:53:48 +01: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 b571303385 Don't use Current.account for search records
It doesn't actually work, and even if we could make it work reliably
we are better off if the records always know to go to the right shard.

It does make the interface a bit more complicated as we need to select
the right shard class with `for(account_id)`.
2025-11-25 11:34:41 +00:00
Mike Dalessio 9f117159a6 Restore structured logging of queenbee_id
and clean up some other small remaining "plan b" to-dos.
2025-11-24 15:50:45 -05:00
Mike Dalessio b2e3c7b1c7 Drop the "max previewable file size" limit
which was originally introduced to work around timing issues causing
us to try (and fail) to generate previews on sqlite read replicas.
2025-11-21 10:55:20 -05:00
Donal McBreen 01d16f96d4 Remove Search::Record.for_account
Instead we'll compute the table name dynamically based on
Current.account where needed. Also we'll prevent searchable records
from being saved if Current.account is not set, otherwise the after
commit callbacks will fail.
2025-11-21 12:09:21 +00:00
Donal McBreen 19b0e25eac Split sqlite/mysql record stuff 2025-11-21 09:15:19 +00:00
Donal McBreen a682a807ed Handle SQLite date subtraction 2025-11-21 09:15:19 +00:00
Donal McBreen cc7e091508 Disable multi-db for SQLite 2025-11-21 09:15:19 +00:00