Class: Zoom::OauthService

Inherits:
Object
  • Object
show all
Defined in:
app/services/zoom/oauth_service.rb

Overview

Handles Zoom OAuth 2.0 token storage and refresh via OauthCredential.

Zoom uses standard OAuth 2.0 Authorization Code flow:

  1. User authorizes via OmniAuth (Devise)
  2. Callback stores tokens in OauthCredential
  3. When token expires (1 hour), refresh_token is used to get a new pair
  4. Refresh tokens expire after 90 days of non-use

Reference: https://developers.zoom.us/docs/integrations/oauth/

Defined Under Namespace

Classes: TokenRefreshError

Constant Summary collapse

PROVIDER =
'zoom'
TOKEN_URL =
'https://zoom.us/oauth/token'
API_BASE =
'https://api.zoom.us/v2'

Instance Method Summary collapse

Constructor Details

#initialize(account:) ⇒ OauthService

Returns a new instance of OauthService.

Parameters:

  • account (Account)

    the CRM account whose Zoom credential to manage



22
23
24
25
26
# File 'app/services/zoom/oauth_service.rb', line 22

def initialize(account:)
  @account       = 
  @client_id     = Heatwave::Configuration.fetch(:zoom, :client_id)
  @client_secret = Heatwave::Configuration.fetch(:zoom, :client_secret)
end

Instance Method Details

#access_token!String

Get a valid access token, refreshing if expired.

Returns:

  • (String)

    the access_token

Raises:



75
76
77
78
79
80
81
# File 'app/services/zoom/oauth_service.rb', line 75

def access_token!
  credential = OauthCredential.for(PROVIDER, account: @account)
  raise TokenRefreshError, 'No Zoom credential found' unless credential

  refresh! if credential.token_expired?
  credential.reload.access_token
end

#connected?Boolean

Whether this account has a stored Zoom credential.

Returns:

  • (Boolean)


85
86
87
# File 'app/services/zoom/oauth_service.rb', line 85

def connected?
  OauthCredential.for(PROVIDER, account: @account).present?
end

#disconnect!Object

Remove the stored credential (disconnect).



90
91
92
# File 'app/services/zoom/oauth_service.rb', line 90

def disconnect!
  OauthCredential.where(provider: PROVIDER, account_id: @account.id).destroy_all
end

#healthy?Boolean

Verify the current token is valid by calling Zoom API.
Attempts refresh first if token is expired.

Returns:

  • (Boolean)


97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'app/services/zoom/oauth_service.rb', line 97

def healthy?
  credential = OauthCredential.for(PROVIDER, account: @account)
  return false unless credential&.access_token.present?

  refresh! if credential.token_expired?
  credential.reload

  response = connection.get("#{API_BASE}/users/me") do |req|
    req.headers['Authorization'] = "Bearer #{credential.access_token}"
  end
  response.status == 200
rescue TokenRefreshError, StandardError => e
  Rails.logger.error("[Zoom::OauthService] Health check failed for account #{@account.id}: #{e.message}")
  false
end

#refresh!OauthCredential

Refresh the stored access token.

Returns:

Raises:



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'app/services/zoom/oauth_service.rb', line 44

def refresh!
  credential = OauthCredential.for(PROVIDER, account: @account)
  raise TokenRefreshError, 'No Zoom credential found' unless credential
  raise TokenRefreshError, 'No refresh token available' if credential.refresh_token.blank?

  encoded = Base64.strict_encode64("#{@client_id}:#{@client_secret}")

  response = connection.post(TOKEN_URL) do |req|
    req.headers['Authorization'] = "Basic #{encoded}"
    req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
    req.body = URI.encode_www_form(
      grant_type: 'refresh_token',
      refresh_token: credential.refresh_token
    )
  end

  unless response.status == 200
    raise TokenRefreshError, "Token refresh failed (#{response.status}): #{response.body}"
  end

  data = JSON.parse(response.body)
  credential.update!(
    access_token: data['access_token'],
    refresh_token: data['refresh_token'].presence || credential.refresh_token,
    expires_at: data['expires_in'] ? data['expires_in'].to_i.seconds.from_now : 1.hour.from_now
  )
  credential
end

#store_from_omniauth!(credentials) ⇒ OauthCredential

Store tokens from OmniAuth callback.

Parameters:

  • credentials (Hash)

    omniauth['credentials'] hash

Returns:



31
32
33
34
35
36
37
38
39
# File 'app/services/zoom/oauth_service.rb', line 31

def store_from_omniauth!(credentials)
  cred = OauthCredential.find_or_initialize_by(provider: PROVIDER, account_id: @account.id)
  cred.access_token  = credentials['token']
  cred.refresh_token = credentials['refresh_token'] if credentials['refresh_token'].present?
  cred.expires_at    = credentials['expires_at'] ? Time.at(credentials['expires_at']) : 1.hour.from_now
  cred.token_type    = 'Bearer'
  cred.save!
  cred
end