mirror of
https://github.com/we-promise/sure.git
synced 2026-04-22 21:44:11 +00:00
* fix(security): sanitize exception messages in API responses (FIX-11)
Replace raw e.message/error.message interpolations in response bodies
with generic error strings, and log class+message server-side. Prevents
leaking internal exception details (stack traces, SQL fragments, record
data) to API clients.
Covers:
- API v1 accounts, categories (index/show), holdings, sync, trades,
transactions (index/show/create/update/destroy), valuations
(show/create/update): replace "Error: #{e.message}" with
"An unexpected error occurred".
- API v1 auth: device-registration rescue paths now log
"[Auth] Device registration failed: ..." and respond with
"Failed to register device".
- WebhooksController#plaid and #plaid_eu: log full error and respond
with "Invalid webhook".
- Settings::ProvidersController: generic user-facing flash alert,
detailed log line with error class + message.
Updates providers_controller_test assertion to match sanitized flash.
* fix(security): address CodeRabbit review
Major — partial-commit on device registration failure:
- Strengthened valid_device_info? to also run MobileDevice's model
validations up-front (device_type inclusion, attribute presence), not
just a flat "are the keys present?" check. A client that sends a bad
device_type ("windows", etc.) is now rejected at the API boundary
BEFORE signup commits any user/family/invite state.
- Wrapped the signup path (user.save + InviteCode.claim + MobileDevice
upsert + token issuance) in ActiveRecord::Base.transaction. A
post-save RecordInvalid from device registration (e.g., racing
uniqueness on device_id) now rolls back the user/invite/family so
clients don't see a partial-account state.
- Rescue branch logs the exception class + message ("#{e.class} - #{e.message}")
for better postmortem debugging, matching the providers controller
pattern.
Nit:
- Tightened providers_controller_test log expectation regex to assert on
both the exception class name AND the message ("StandardError - Database
error"), so a regression that drops either still fails the test.
Tests:
- New: "should reject signup with invalid device_type before committing
any state" — POST /api/v1/auth/signup with device_type="windows"
returns 400 AND asserts no User, MobileDevice, or Doorkeeper::AccessToken
row was created.
Note on SSO path (sso_exchange → issue_mobile_tokens, lines 173/225): the
device_info in those flows comes from Rails.cache (populated by an earlier
request that already passed valid_device_info?), so the pre-validation
covers it indirectly. Wrapping the full SSO account creation (user +
invitation + OidcIdentity + issue_mobile_tokens) in one transaction would
be a meaningful architectural cleanup but is out of scope for this
error-hygiene PR — filed it as a mental note for a follow-up.
60 lines
1.9 KiB
Ruby
60 lines
1.9 KiB
Ruby
class WebhooksController < ApplicationController
|
|
skip_before_action :verify_authenticity_token
|
|
skip_authentication
|
|
|
|
def plaid
|
|
webhook_body = request.body.read
|
|
plaid_verification_header = request.headers["Plaid-Verification"]
|
|
|
|
client = Provider::Registry.plaid_provider_for_region(:us)
|
|
|
|
client.validate_webhook!(plaid_verification_header, webhook_body)
|
|
|
|
PlaidItem::WebhookProcessor.new(webhook_body).process
|
|
|
|
render json: { received: true }, status: :ok
|
|
rescue => error
|
|
Sentry.capture_exception(error)
|
|
Rails.logger.error("Webhook error: #{error.class} - #{error.message}")
|
|
render json: { error: "Invalid webhook" }, status: :bad_request
|
|
end
|
|
|
|
def plaid_eu
|
|
webhook_body = request.body.read
|
|
plaid_verification_header = request.headers["Plaid-Verification"]
|
|
|
|
client = Provider::Registry.plaid_provider_for_region(:eu)
|
|
|
|
client.validate_webhook!(plaid_verification_header, webhook_body)
|
|
|
|
PlaidItem::WebhookProcessor.new(webhook_body).process
|
|
|
|
render json: { received: true }, status: :ok
|
|
rescue => error
|
|
Sentry.capture_exception(error)
|
|
Rails.logger.error("Webhook error: #{error.class} - #{error.message}")
|
|
render json: { error: "Invalid webhook" }, status: :bad_request
|
|
end
|
|
|
|
def stripe
|
|
stripe_provider = Provider::Registry.get_provider(:stripe)
|
|
|
|
begin
|
|
webhook_body = request.body.read
|
|
sig_header = request.env["HTTP_STRIPE_SIGNATURE"]
|
|
|
|
stripe_provider.process_webhook_later(webhook_body, sig_header)
|
|
|
|
head :ok
|
|
rescue JSON::ParserError => error
|
|
Sentry.capture_exception(error)
|
|
Rails.logger.error "JSON parser error: #{error.message}"
|
|
head :bad_request
|
|
rescue Stripe::SignatureVerificationError => error
|
|
Sentry.capture_exception(error)
|
|
Rails.logger.error "Stripe signature verification error: #{error.message}"
|
|
head :bad_request
|
|
end
|
|
end
|
|
end
|