Class: TimeOffRequests::GoogleAuthService
- Inherits:
-
Object
- Object
- TimeOffRequests::GoogleAuthService
- Defined in:
- app/services/time_off_requests/google_auth_service.rb
Overview
Service object: google auth service.
Defined Under Namespace
Classes: RefreshExhaustedError
Class Method Summary collapse
- .authorize(employee) ⇒ Object
-
.check_status(employee, skip_api_check: false) ⇒ Object
Check if employee has valid Google Calendar authorization Returns a hash with :connected (boolean) and :needs_reconnect (boolean).
- .refresh_token(employee, client = nil) ⇒ Object
-
.report_refresh_exhausted(employee, context = {}) ⇒ nil
Report a grant that no amount of retrying will fix — revoked, or consent that never covered the scope.
-
.update_cached_status(auth, status) ⇒ Object
Update the cached Google Calendar status on the authentication record.
Class Method Details
.authorize(employee) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'app/services/time_off_requests/google_auth_service.rb', line 58 def self.(employee) auth = employee.account&.authentications&.google_auth&.first return unless auth client = Signet::OAuth2::Client.new( client_id: Heatwave::Configuration.fetch(:omniauth, :google_oauth2_id), client_secret: Heatwave::Configuration.fetch(:omniauth, :google_oauth2_secret), access_token: auth.google_auth_access_token, refresh_token: auth.google_auth_refresh_token, expires_at: auth.google_auth_expires_at, token_credential_uri: 'https://oauth2.googleapis.com/token' ) # Refresh ahead of expiry rather than waiting for Google to 401. We ask the # record, not Signet: Signet#expired? is false whenever expires_at is nil, # which is every row written before that column existed. if auth.google_auth_needs_refresh? refreshed_client = refresh_token(employee, client) return nil unless refreshed_client return refreshed_client end client end |
.check_status(employee, skip_api_check: false) ⇒ Object
Check if employee has valid Google Calendar authorization
Returns a hash with :connected (boolean) and :needs_reconnect (boolean)
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
# File 'app/services/time_off_requests/google_auth_service.rb', line 11 def self.check_status(employee, skip_api_check: false) return { connected: false, needs_reconnect: false, message: 'No account linked' } unless employee&.account auth = employee.account.authentications.google_auth&.first return { connected: false, needs_reconnect: false, message: 'Google account not connected' } unless auth # Check if we have refresh token (required for calendar access) if auth.google_auth_refresh_token.blank? update_cached_status(auth, 'needs_reconnect') return { connected: false, needs_reconnect: true, message: 'Google account needs to be reconnected' } end # If skip_api_check, use cached status if available and recent (within 24 hours) if skip_api_check && auth.respond_to?(:google_calendar_status) && auth.google_calendar_status.present? && auth.google_calendar_status_checked_at&.>(24.hours.ago) case auth.google_calendar_status when 'connected' return { connected: true, needs_reconnect: false, message: 'Connected' } when 'needs_reconnect' return { connected: false, needs_reconnect: true, message: 'Google authorization expired - please reconnect' } end end # Actually try to refresh the token to verify it works # This is the only reliable way to know if the credentials are still valid refreshed_client = refresh_token(employee) if refreshed_client update_cached_status(auth, 'connected') { connected: true, needs_reconnect: false, message: 'Connected' } else update_cached_status(auth, 'needs_reconnect') { connected: false, needs_reconnect: true, message: 'Google authorization expired - please reconnect' } end end |
.refresh_token(employee, client = nil) ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'app/services/time_off_requests/google_auth_service.rb', line 113 def self.refresh_token(employee, client = nil) auth = employee.account.authentications.google_auth&.first return unless auth client ||= Signet::OAuth2::Client.new( client_id: Heatwave::Configuration.fetch(:omniauth, :google_oauth2_id), client_secret: Heatwave::Configuration.fetch(:omniauth, :google_oauth2_secret), refresh_token: auth.google_auth_refresh_token, token_credential_uri: 'https://oauth2.googleapis.com/token' ) begin client.refresh! auth.update!(google_auth_access_token: client.access_token, google_auth_expires_at: client.expires_at) Rails.logger.info("Google OAuth token refreshed successfully for #{employee.email}") client rescue StandardError => e Rails.logger.error("Failed to refresh Google OAuth token: #{e.}") nil end end |
.report_refresh_exhausted(employee, context = {}) ⇒ nil
Report a grant that no amount of retrying will fix — revoked, or consent
that never covered the scope. Nobody sees a log line, and the symptom is
silent: calendars read as empty and PTO stops syncing for that employee
until they reconnect. That needs to page someone, so it goes to AppSignal.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'app/services/time_off_requests/google_auth_service.rb', line 96 def self.report_refresh_exhausted(employee, context = {}) # Employee id, not email: the message is the AppSignal grouping key and ends # up in incident titles, so it stays free of PII. party_id below carries the # correlation, and the id keeps one broken grant from merging with the next. ErrorReporting.error( RefreshExhaustedError.new( "Google token refresh limit reached for employee #{employee&.id} — the account must be reconnected" ), { party_id: employee&.id, reason: 'google_refresh_exhausted', source: defined?(Sidekiq) && Sidekiq.server? ? :background : :web }.merge(context) ) nil end |
.update_cached_status(auth, status) ⇒ Object
Update the cached Google Calendar status on the authentication record
47 48 49 50 51 52 53 54 55 56 |
# File 'app/services/time_off_requests/google_auth_service.rb', line 47 def self.update_cached_status(auth, status) return unless auth.respond_to?(:google_calendar_status) auth.update_columns( google_calendar_status: status, google_calendar_status_checked_at: Time.current ) rescue StandardError => e Rails.logger.error("Failed to update cached Google Calendar status: #{e.}") end |