Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/workos.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def self.key
autoload :Invitation, 'workos/invitation'
autoload :MagicAuth, 'workos/magic_auth'
autoload :MFA, 'workos/mfa'
autoload :OAuthTokens, 'workos/oauth_tokens'
autoload :Organization, 'workos/organization'
autoload :Organizations, 'workos/organizations'
autoload :OrganizationMembership, 'workos/organization_membership'
Expand Down
5 changes: 4 additions & 1 deletion lib/workos/authentication_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ class AuthenticationResponse
:access_token,
:refresh_token,
:authentication_method,
:sealed_session
:sealed_session,
:oauth_tokens

# rubocop:disable Metrics/AbcSize
def initialize(authentication_response_json, session = nil)
Expand All @@ -27,6 +28,7 @@ def initialize(authentication_response_json, session = nil)
reason: impersonator_json[:reason],)
end
@authentication_method = json[:authentication_method]
@oauth_tokens = json[:oauth_tokens] ? WorkOS::OAuthTokens.new(json[:oauth_tokens].to_json) : nil
@sealed_session =
if session && session[:seal_session]
WorkOS::Session.seal_data({
Expand All @@ -49,6 +51,7 @@ def to_json(*)
refresh_token: refresh_token,
authentication_method: authentication_method,
sealed_session: sealed_session,
oauth_tokens: oauth_tokens&.to_json,
}
end
end
Expand Down
29 changes: 29 additions & 0 deletions lib/workos/oauth_tokens.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module WorkOS
# The OAuthTokens class represents the third party providerOAuth tokens returned in the authentication response.
# This class is not meant to be instantiated in user space, and is instantiated internally but exposed.
class OAuthTokens
include HashProvider

attr_accessor :access_token, :refresh_token, :scopes, :expires_at

def initialize(json)
hash = JSON.parse(json, symbolize_names: true)

@access_token = hash[:access_token]
@refresh_token = hash[:refresh_token]
@scopes = hash[:scopes]
@expires_at = hash[:expires_at]
end

def to_json(*)
{
access_token: access_token,
refresh_token: refresh_token,
scopes: scopes,
expires_at: expires_at,
}
end
end
end
5 changes: 4 additions & 1 deletion lib/workos/user_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def load_sealed_session(client_id:, session_data:, cookie_password:)
# field of the IdP sign-in page for the user, if you know their username ahead of time.
# @param [String] domain_hint Can be used to pre-fill the domain field when
# initiating authentication with Microsoft OAuth, or with a GoogleSAML connection type.
# @param [Array<String>] provider_scopes An array of additional OAuth scopes to request from the provider.
# @example
# WorkOS::UserManagement.authorization_url(
# connection_id: 'conn_123',
Expand All @@ -96,7 +97,8 @@ def authorization_url(
provider: nil,
connection_id: nil,
organization_id: nil,
state: ''
state: '',
provider_scopes: nil
)

validate_authorization_url_arguments(
Expand All @@ -115,6 +117,7 @@ def authorization_url(
provider: provider,
connection_id: connection_id,
organization_id: organization_id,
provider_scopes: provider_scopes,
}.compact)

"https://#{WorkOS.config.api_hostname}/user_management/authorize?#{query}"
Expand Down
60 changes: 60 additions & 0 deletions spec/lib/workos/user_management_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,32 @@
'edit%22%7D&provider=authkit',
)
end

context 'with provider_scopes' do
it 'returns a valid authorization URL that includes provider_scopes' do
url = WorkOS::UserManagement.authorization_url(
provider: 'GoogleOAuth',
provider_scopes: ['custom-scope-1', 'custom-scope-2'],
client_id: 'workos-proj-123',
redirect_uri: 'foo.com/auth/callback',
state: {
next_page: '/dashboard/edit',
}.to_s,
)

expect(url).to eq(
'https://api.workos.com/user_management/authorize?' \
'client_id=workos-proj-123' \
'&redirect_uri=foo.com%2Fauth%2Fcallback' \
'&response_type=code' \
'&state=%7B%3Anext_page%3D%3E%22%2Fdashboard%2F' \
'edit%22%7D' \
'&provider=GoogleOAuth' \
'&provider_scopes=custom-scope-1' \
'&provider_scopes=custom-scope-2',
)
end
end
end

context 'with a connection selector' do
Expand Down Expand Up @@ -453,6 +479,40 @@
end
end

context 'when oauth_tokens is present in the api response' do
it 'returns an oauth_tokens object' do
VCR.use_cassette('user_management/authenticate_with_code/valid_with_oauth_tokens') do
authentication_response = WorkOS::UserManagement.authenticate_with_code(
code: '01H93ZZHA0JBHFJH9RR11S83YN',
client_id: 'client_123',
ip_address: '200.240.210.16',
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/108.0.0.0 Safari/537.36',
)

expect(authentication_response.oauth_tokens).to be_a(WorkOS::OAuthTokens)
expect(authentication_response.oauth_tokens.access_token).to eq('oauth_access_token')
expect(authentication_response.oauth_tokens.refresh_token).to eq('oauth_refresh_token')
expect(authentication_response.oauth_tokens.scopes).to eq(['read', 'write'])
expect(authentication_response.oauth_tokens.expires_at).to eq(1234567890)
end
end
end

context 'when oauth_tokens is not present in the api response' do
it 'returns nil oauth_tokens' do
VCR.use_cassette('user_management/authenticate_with_code/valid') do
authentication_response = WorkOS::UserManagement.authenticate_with_code(
code: '01H93ZZHA0JBHFJH9RR11S83YN',
client_id: 'client_123',
ip_address: '200.240.210.16',
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/108.0.0.0 Safari/537.36',
)

expect(authentication_response.oauth_tokens).to be_nil
end
end
end

context 'when the user is being impersonated' do
it 'contains the impersonator metadata' do
VCR.use_cassette('user_management/authenticate_with_code/valid_with_impersonator') do
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading