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 =
'https://accounts.google.com/o/oauth2/v2/auth'- TOKEN_URL =
'https://oauth2.googleapis.com/token'- 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.
31 32 33 34 35 |
# File 'app/services/youtube/oauth_service.rb', line 31 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.
89 90 91 92 93 94 95 |
# File 'app/services/youtube/oauth_service.rb', line 89 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.
40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'app/services/youtube/oauth_service.rb', line 40 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
97 98 99 |
# File 'app/services/youtube/oauth_service.rb', line 97 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)
105 106 107 108 109 110 |
# File 'app/services/youtube/oauth_service.rb', line 105 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
112 113 114 |
# File 'app/services/youtube/oauth_service.rb', line 112 def disconnect! OauthCredential.where(provider: PROVIDER, account_id: @account&.id).destroy_all end |
#exchange_code!(code) ⇒ Object
Exchange the authorization code for tokens and persist them.
54 55 56 57 58 59 60 61 62 63 64 |
# File 'app/services/youtube/oauth_service.rb', line 54 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).
118 119 120 121 122 123 124 125 126 |
# File 'app/services/youtube/oauth_service.rb', line 118 def healthy? credential = OauthCredential.for(PROVIDER, account: @account) return false unless credential&.access_token.present? refresh! if credential.token_expired? true rescue TokenRefreshError, StandardError false end |
#refresh! ⇒ Object
Refresh the stored access token.
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'app/services/youtube/oauth_service.rb', line 67 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 |