Class: Rubino::OAuth::Provider

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/oauth/provider.rb,
lib/rubino/oauth/provider/github.rb,
lib/rubino/oauth/provider/google.rb

Overview

Abstract OAuth 2.0 provider. Subclasses declare endpoints + default scopes and implement #fetch_account_info to populate account_id/account_email after a successful token exchange.

Configured per-provider with client_id, client_secret, scopes from rubino.yml. PKCE (S256) is enabled by default for the auth_code flow. The agent is stateless across the redirect: the client persists the returned state and code_verifier between connect and callback.

Direct Known Subclasses

Github, Google

Defined Under Namespace

Classes: Github, Google

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(client_id:, client_secret:, scopes: nil, metadata: {}) ⇒ Provider

Returns a new instance of Provider.



45
46
47
48
49
50
# File 'lib/rubino/oauth/provider.rb', line 45

def initialize(client_id:, client_secret:, scopes: nil, metadata: {})
  @client_id = client_id
  @client_secret = client_secret
  @scopes = (scopes || self.class.default_scopes).map(&:to_s)
  @metadata = 
end

Instance Attribute Details

#client_idObject (readonly)

Returns the value of attribute client_id.



19
20
21
# File 'lib/rubino/oauth/provider.rb', line 19

def client_id
  @client_id
end

#client_secretObject (readonly)

Returns the value of attribute client_secret.



19
20
21
# File 'lib/rubino/oauth/provider.rb', line 19

def client_secret
  @client_secret
end

#metadataObject (readonly)

Returns the value of attribute metadata.



19
20
21
# File 'lib/rubino/oauth/provider.rb', line 19

def 
  @metadata
end

#scopesObject (readonly)

Returns the value of attribute scopes.



19
20
21
# File 'lib/rubino/oauth/provider.rb', line 19

def scopes
  @scopes
end

Class Method Details

.authorize_pathObject

Raises:

  • (NotImplementedError)


33
34
35
# File 'lib/rubino/oauth/provider.rb', line 33

def self.authorize_path
  raise NotImplementedError
end

.default_scopesObject



41
42
43
# File 'lib/rubino/oauth/provider.rb', line 41

def self.default_scopes
  []
end

.display_nameObject



25
26
27
# File 'lib/rubino/oauth/provider.rb', line 25

def self.display_name
  id.to_s.capitalize
end

.idObject

Raises:

  • (NotImplementedError)


21
22
23
# File 'lib/rubino/oauth/provider.rb', line 21

def self.id
  raise NotImplementedError
end

.siteObject

Raises:

  • (NotImplementedError)


29
30
31
# File 'lib/rubino/oauth/provider.rb', line 29

def self.site
  raise NotImplementedError
end

.token_pathObject

Raises:

  • (NotImplementedError)


37
38
39
# File 'lib/rubino/oauth/provider.rb', line 37

def self.token_path
  raise NotImplementedError
end

Instance Method Details

#build_authorize_request(redirect_uri:, scopes: nil, extra: {}) ⇒ Hash

Build the authorize URL the client must redirect the user to.

The returned state and code_verifier MUST be persisted by the caller and replayed on the callback — rubino keeps no per-flow session.

Parameters:

  • redirect_uri (String)

    absolute callback URL registered with the provider

  • scopes (Array<String>, nil) (defaults to: nil)

    overrides the instance default scopes when present

  • extra (Hash) (defaults to: {})

    additional query parameters appended to the authorize URL

Returns:

  • (Hash)

    with keys :authorize_url (String), :state (String, urlsafe base64), :code_verifier (String, PKCE verifier)



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/rubino/oauth/provider.rb', line 67

def build_authorize_request(redirect_uri:, scopes: nil, extra: {})
  state = SecureRandom.urlsafe_base64(32)
  code_verifier = SecureRandom.urlsafe_base64(64)
  code_challenge = pkce_challenge(code_verifier)

  url = oauth2_client.auth_code.authorize_url(
    redirect_uri: redirect_uri,
    scope: Array(scopes || @scopes).join(scope_separator),
    state: state,
    code_challenge: code_challenge,
    code_challenge_method: "S256",
    **extra
  )

  { authorize_url: url, state: state, code_verifier: code_verifier }
end

#exchange_code(code:, redirect_uri:, code_verifier:) ⇒ Hash

Exchange the authorization code for tokens.

Parameters:

  • code (String)

    authorization code returned by the provider

  • redirect_uri (String)

    same redirect_uri used in #build_authorize_request

  • code_verifier (String)

    PKCE verifier paired with the original challenge

Returns:

  • (Hash)

    with keys :access_token (String), :refresh_token (String, nil), :expires_at (String ISO8601 UTC, nil), :scopes (Array<String>)



92
93
94
95
96
97
98
99
# File 'lib/rubino/oauth/provider.rb', line 92

def exchange_code(code:, redirect_uri:, code_verifier:)
  token = oauth2_client.auth_code.get_token(
    code,
    redirect_uri: redirect_uri,
    code_verifier: code_verifier
  )
  normalize(token)
end

#fetch_account_info(_access_token) ⇒ Hash

Provider-specific call to /userinfo (or equivalent) using the access token.

Parameters:

  • _access_token (String)

Returns:

  • (Hash)

    with keys :account_id (String), :account_email (String, nil), :metadata (Hash)

Raises:

  • (NotImplementedError)


112
113
114
# File 'lib/rubino/oauth/provider.rb', line 112

def (_access_token)
  raise NotImplementedError
end

#idObject



52
53
54
# File 'lib/rubino/oauth/provider.rb', line 52

def id
  self.class.id
end

#refresh(refresh_token) ⇒ Object



101
102
103
104
# File 'lib/rubino/oauth/provider.rb', line 101

def refresh(refresh_token)
  token = OAuth2::AccessToken.new(oauth2_client, "", refresh_token: refresh_token)
  normalize(token.refresh!)
end