From 80d855703aec1d4d6bd5ab394c8bef78d7edbae0 Mon Sep 17 00:00:00 2001 From: Sam Liu Date: Wed, 26 Nov 2025 22:22:31 -0500 Subject: [PATCH] Add Sentry to capture exceptions --- app/controllers/admin/sessions_controller.rb | 13 +++++++++++-- app/controllers/api/verify_controller.rb | 5 +++++ app/controllers/identity_controller.rb | 4 ++++ app/controllers/popup/authorize_controller.rb | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin/sessions_controller.rb b/app/controllers/admin/sessions_controller.rb index 06ace7c..25d6ca2 100644 --- a/app/controllers/admin/sessions_controller.rb +++ b/app/controllers/admin/sessions_controller.rb @@ -44,10 +44,14 @@ def callback begin res = http.request(req) rescue => e + Sentry.capture_exception(e) Rails.logger.error("Admin OAuth token exchange error: #{e.class}: #{e.message}") return redirect_to root_path, alert: 'OAuth failed' end - return redirect_to root_path, alert: 'OAuth failed' unless res.is_a?(Net::HTTPSuccess) + unless res.is_a?(Net::HTTPSuccess) + Sentry.capture_message("OAuth token exchange failed: #{res.code} - #{res.body}") + return redirect_to root_path, alert: 'OAuth failed' + end token_data = JSON.parse(res.body) @@ -61,16 +65,21 @@ def callback begin me_res = http.request(req) rescue => e + Sentry.capture_exception(e) Rails.logger.error("Admin profile fetch error: #{e.class}: #{e.message}") return redirect_to root_path, alert: 'Profile fetch failed' end - return redirect_to root_path, alert: 'Profile fetch failed' unless me_res.is_a?(Net::HTTPSuccess) + unless me_res.is_a?(Net::HTTPSuccess) + Sentry.capture_message("Profile fetch failed: #{me_res.code} - #{me_res.body}") + return redirect_to root_path, alert: 'Profile fetch failed' + end user_data = JSON.parse(me_res.body)['identity'] user_data = IdentityNormalizer.normalize(user_data) # Verify and consume state state_data = StateToken.verify(state) if session[:admin_state_nonce].blank? || state_data.nil? || state_data['nonce'] != session[:admin_state_nonce] + Sentry.capture_message('OAuth state verification failed') return redirect_to root_path, alert: 'OAuth failed' end session.delete(:admin_state_nonce) diff --git a/app/controllers/api/verify_controller.rb b/app/controllers/api/verify_controller.rb index e04c522..fe86239 100644 --- a/app/controllers/api/verify_controller.rb +++ b/app/controllers/api/verify_controller.rb @@ -82,6 +82,7 @@ def index end unless ENV['IDENTITY_URL'].present? && ENV['IDENTITY_PROGRAM_KEY'].present? + Sentry.capture_message('Missing Identity Vault configuration') Rails.logger.error('Missing Identity Vault configuration') attempt = create_attempt_safely!( idv_rec: idv_rec, @@ -120,6 +121,7 @@ def index begin res = http.request(req) rescue => e + Sentry.capture_exception(e) Rails.logger.error("Identity API error: #{e.class}: #{e.message}") attempt = create_attempt_safely!(idv_rec: idv_rec, first_name: first_name, last_name: last_name, email: email, program: program_slug, submit_id: submit_id, verified: false, identity_response: nil, ip: request.remote_ip) UserJourneyEvent.create!(event_type: 'verification_attempt', program: program_slug, idv_rec: idv_rec, email: email, request_ip: request.remote_ip, verification_attempt_id: attempt&.id, metadata: { error: 'fetch_timeout', submit_id: submit_id }) rescue nil @@ -132,6 +134,7 @@ def index UserJourneyEvent.create!(event_type: 'verification_attempt', program: program_slug, idv_rec: idv_rec, email: email, request_ip: request.remote_ip, verification_attempt_id: attempt&.id, metadata: { error: '404', submit_id: submit_id }) rescue nil return render json: { verified: false, identity_response: nil } else + Sentry.capture_message("User fetch failed: #{res.code} - #{res.body}") Rails.logger.error("User fetch failed: #{res.code} - #{res.body}") attempt = create_attempt_safely!(idv_rec: idv_rec, first_name: first_name, last_name: last_name, email: email, program: program_slug, submit_id: submit_id, verified: false, identity_response: nil, ip: request.remote_ip) UserJourneyEvent.create!(event_type: 'verification_attempt', program: program_slug, idv_rec: idv_rec, email: email, request_ip: request.remote_ip, verification_attempt_id: attempt&.id, metadata: { error: 'fetch_failed', code: res.code, submit_id: submit_id }) rescue nil @@ -144,6 +147,7 @@ def index user_data = body['identity'] user_data = IdentityNormalizer.normalize(user_data) rescue => e + Sentry.capture_exception(e) Rails.logger.error("Invalid JSON from identity API: #{e.message}") attempt = create_attempt_safely!( idv_rec: idv_rec, @@ -277,6 +281,7 @@ def index ) rescue nil render json: { verified: false, error: 'Submit token already used', identity_response: nil }, status: :gone rescue => e + Sentry.capture_exception(e) Rails.logger.error("Verification error: #{e.message}") attempt = create_attempt_safely!(idv_rec: idv_rec, first_name: first_name, last_name: last_name, email: email, program: (program_rec&.slug || program_slug), submit_id: submit_id, verified: false, identity_response: nil, ip: request.remote_ip) UserJourneyEvent.create!(event_type: 'verification_attempt', program: (program_rec&.slug || program_slug), idv_rec: idv_rec, email: email, request_ip: request.remote_ip, verification_attempt_id: attempt&.id, metadata: { error: 'exception', message: e.message, submit_id: submit_id }) rescue nil diff --git a/app/controllers/identity_controller.rb b/app/controllers/identity_controller.rb index 2ed51e3..c4f767e 100644 --- a/app/controllers/identity_controller.rb +++ b/app/controllers/identity_controller.rb @@ -27,6 +27,7 @@ def url # Base URL for redirect_uri; prefer request.base_url if NEXTAUTH_URL missing nextauth_url = ENV['NEXTAUTH_URL'].presence || request.base_url unless nextauth_url.present? + Sentry.capture_message('NEXTAUTH_URL environment variable is not set') Rails.logger.error('NEXTAUTH_URL environment variable is not set') return render json: { error: 'Server configuration error' }, status: :internal_server_error end @@ -245,6 +246,7 @@ def callback rescue ActiveRecord::RecordNotUnique # If it already exists, continue (idempotent on refresh) rescue => e + Sentry.capture_exception(e) Rails.logger.error("Failed to issue AuthorizedSubmitToken: #{e.class}: #{e.message}") end @@ -286,6 +288,7 @@ def callback # Centralized failure handler for OAuth flow def oauth_fail(reason:, alert_message: 'Identity verification failed', program: nil, idv_rec: nil, email: nil, extra_metadata: {}) + Sentry.capture_message("OAuth failure: #{reason} - #{alert_message}") if alert_message.to_s.downcase.include?('failed') base_metadata = { reason: reason }.merge(extra_metadata || {}) Rails.logger.warn("OAuth failure: #{reason} metadata=#{base_metadata.inspect}") safe_create_journey_event( @@ -309,6 +312,7 @@ def safe_create_journey_event(event_type:, program: nil, idv_rec: nil, email: ni metadata: metadata.presence ) rescue => e + Sentry.capture_exception(e) Rails.logger.error("UserJourneyEvent create failed (#{event_type}): #{e.class}: #{e.message}") end diff --git a/app/controllers/popup/authorize_controller.rb b/app/controllers/popup/authorize_controller.rb index c390364..40e1451 100644 --- a/app/controllers/popup/authorize_controller.rb +++ b/app/controllers/popup/authorize_controller.rb @@ -149,6 +149,7 @@ def callback render :success, layout: 'application' rescue => e + Sentry.capture_exception(e) Rails.logger.error("Popup OAuth error: #{e.message}") UserJourneyEvent.create!( event_type: 'popup_oauth_error',