Class: YouTube::OauthService
- Inherits:
-
Object
- Object
- YouTube::OauthService
- Defined in:
- app/services/youtube/oauth_service.rb
Overview
Handles Google/YouTube OAuth 2.0 token exchange and refresh.
YouTube uses standard OAuth 2.0 Authorization Code flow:
- Redirect user to authorization_url
- Google redirects back with ?code=...
- Exchange code for access_token + refresh_token
- When token expires (~1 hour), use refresh_token to get a new pair
Reference: https://developers.google.com/youtube/v3/guides/authentication
Defined Under Namespace
Classes: TokenRefreshError
Constant Summary collapse
- AUTHORIZE_URL =
URL for authorize.
'https://accounts.google.com/o/oauth2/v2/auth'- TOKEN_URL =
URL for token.
'https://oauth2.googleapis.com/token'- PROVIDER =
Provider.
'youtube'- SCOPES =
captions.list / captions.insert / captions.delete only accept youtube.force-ssl or
youtubepartner per API docs — the broaderyoutubescope is not sufficient for those endpoints.
After changing scopes, users must disconnect and complete OAuth again so the refresh token includes them. [ 'https://www.googleapis.com/auth/youtube.force-ssl', 'https://www.googleapis.com/auth/youtube', 'https://www.googleapis.com/auth/youtube.upload', 'https://www.googleapis.com/auth/youtube.readonly' ].freeze
Instance Method Summary collapse
-
#access_token! ⇒ Object
Get a valid access token, refreshing if expired.
-
#authorization_url(state: nil) ⇒ Object
URL to redirect the user to for authorization.
- #connected? ⇒ Boolean
-
#connection_status ⇒ Object
Lightweight status check (DB only, no HTTP calls).
- #disconnect! ⇒ Object
-
#exchange_code!(code) ⇒ Object
Exchange the authorization code for tokens and persist them.
-
#healthy? ⇒ Boolean
Full health check — attempts token refresh if expired.
-
#initialize(account: nil) ⇒ OauthService
constructor
A new instance of OauthService.
-
#refresh! ⇒ Object
Refresh the stored access token.
Constructor Details
#initialize(account: nil) ⇒ OauthService
Returns a new instance of OauthService.
35 36 37 38 39 |
# File 'app/services/youtube/oauth_service.rb', line 35 def initialize(account: nil) @account = account @client_id = Heatwave::Configuration.fetch(:omniauth, :google_oauth2_id) @client_secret = Heatwave::Configuration.fetch(:omniauth, :google_oauth2_secret) end |
Instance Method Details
#access_token! ⇒ Object
Get a valid access token, refreshing if expired.
93 94 95 96 97 98 99 |
# File 'app/services/youtube/oauth_service.rb', line 93 def access_token! credential = OauthCredential.for(PROVIDER, account: @account) raise TokenRefreshError, 'No YouTube credential found' unless credential refresh! if credential.token_expired? credential.reload.access_token end |
#authorization_url(state: nil) ⇒ Object
URL to redirect the user to for authorization.
access_type=offline ensures we get a refresh_token.
prompt=consent forces re-consent to always get a refresh_token.
44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'app/services/youtube/oauth_service.rb', line 44 def (state: nil) params = { response_type: 'code', client_id: @client_id, redirect_uri: redirect_uri, scope: SCOPES.join(' '), access_type: 'offline', prompt: 'consent' } params[:state] = state if state.present? "#{AUTHORIZE_URL}?#{params.to_query}" end |
#connected? ⇒ Boolean
101 102 103 |
# File 'app/services/youtube/oauth_service.rb', line 101 def connected? OauthCredential.for(PROVIDER, account: @account).present? end |
#connection_status ⇒ Object
Lightweight status check (DB only, no HTTP calls). Returns a hash:
{ connected: true/false, healthy: true/false, credential: OauthCredential or nil }
healthy = connected AND token not expired (refresh worker keeps it fresh)
109 110 111 112 113 114 |
# File 'app/services/youtube/oauth_service.rb', line 109 def connection_status credential = OauthCredential.for(PROVIDER, account: @account) return { connected: false, healthy: false, credential: nil } unless credential { connected: true, healthy: credential.token_fresh?, credential: credential } end |
#disconnect! ⇒ Object
116 117 118 |
# File 'app/services/youtube/oauth_service.rb', line 116 def disconnect! OauthCredential.destroy_by(provider: PROVIDER, account_id: @account&.id) end |
#exchange_code!(code) ⇒ Object
Exchange the authorization code for tokens and persist them.
58 59 60 61 62 63 64 65 66 67 68 |
# File 'app/services/youtube/oauth_service.rb', line 58 def exchange_code!(code) response = token_request( grant_type: 'authorization_code', code: code, redirect_uri: redirect_uri, client_id: @client_id, client_secret: @client_secret ) persist_tokens!(response) end |
#healthy? ⇒ Boolean
Full health check — attempts token refresh if expired. Use on admin pages,
not on every video page load (makes HTTP call to Google).
122 123 124 125 126 127 128 129 130 |
# File 'app/services/youtube/oauth_service.rb', line 122 def healthy? credential = OauthCredential.for(PROVIDER, account: @account) return false if credential&.access_token.blank? refresh! if credential.token_expired? true rescue TokenRefreshError, StandardError false end |
#refresh! ⇒ Object
Refresh the stored access token.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'app/services/youtube/oauth_service.rb', line 71 def refresh! credential = OauthCredential.for(PROVIDER, account: @account) raise TokenRefreshError, 'No YouTube credential found' unless credential raise TokenRefreshError, 'No refresh token available' if credential.refresh_token.blank? response = token_request( grant_type: 'refresh_token', refresh_token: credential.refresh_token, client_id: @client_id, client_secret: @client_secret ) attrs = { access_token: response['access_token'], expires_at: response['expires_in'] ? response['expires_in'].to_i.seconds.from_now : nil } attrs[:refresh_token] = response['refresh_token'] if response['refresh_token'].present? credential.update!(attrs) credential end |