Skip to content

Commit

Permalink
Add session-related endpoints (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmatheson authored Mar 19, 2024
1 parent 6434928 commit b75d2ef
Show file tree
Hide file tree
Showing 15 changed files with 351 additions and 17 deletions.
1 change: 1 addition & 0 deletions lib/workos.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def self.key
autoload :Portal, 'workos/portal'
autoload :Profile, 'workos/profile'
autoload :ProfileAndToken, 'workos/profile_and_token'
autoload :RefreshAuthenticationResponse, 'workos/refresh_authentication_response'
autoload :SSO, 'workos/sso'
autoload :Types, 'workos/types'
autoload :User, 'workos/user'
Expand Down
6 changes: 5 additions & 1 deletion lib/workos/authentication_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class AuthenticationResponse
include HashProvider
extend T::Sig

attr_accessor :user, :organization_id, :impersonator
attr_accessor :user, :organization_id, :impersonator, :access_token, :refresh_token

sig { params(authentication_response_json: String).void }
def initialize(authentication_response_json)
Expand All @@ -20,13 +20,17 @@ def initialize(authentication_response_json)
Impersonator.new(email: impersonator_json[:email],
reason: impersonator_json[:reason],)
end
@access_token = T.let(json[:access_token], String)
@refresh_token = T.let(json[:refresh_token], String)
end

def to_json(*)
{
user: user.to_json,
organization_id: organization_id,
impersonator: impersonator.to_json,
access_token: access_token,
refresh_token: refresh_token,
}
end
end
Expand Down
27 changes: 27 additions & 0 deletions lib/workos/refresh_authentication_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true
# typed: true

module WorkOS
# The RefreshAuthenticationResponse contains response data from a successful
# `UserManagement.authenticate_with_refresh_token` call
class RefreshAuthenticationResponse
include HashProvider
extend T::Sig

attr_accessor :access_token, :refresh_token

sig { params(authentication_response_json: String).void }
def initialize(authentication_response_json)
json = JSON.parse(authentication_response_json, symbolize_names: true)
@access_token = T.let(json[:access_token], String)
@refresh_token = T.let(json[:refresh_token], String)
end

def to_json(*)
{
access_token: access_token,
refresh_token: refresh_token,
}
end
end
end
100 changes: 100 additions & 0 deletions lib/workos/user_management.rb
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,46 @@ def authenticate_with_code(
WorkOS::AuthenticationResponse.new(response.body)
end

# Authenticate a user using a refresh token.
#
# @param [String] refresh_token The refresh token previously obtained from a successful authentication call
# @param [String] client_id The WorkOS client ID for the environment
# @param [String] ip_address The IP address of the request from the user who is attempting to authenticate.
# @param [String] user_agent The user agent of the request from the user who is attempting to authenticate.
#
# @return WorkOS::RefreshAuthenticationResponse

sig do
params(
refresh_token: String,
client_id: String,
ip_address: T.nilable(String),
user_agent: T.nilable(String),
).returns(WorkOS::RefreshAuthenticationResponse)
end
def authenticate_with_refresh_token(
refresh_token:,
client_id:,
ip_address: nil,
user_agent: nil
)
response = execute_request(
request: post_request(
path: '/user_management/authenticate',
body: {
refresh_token: refresh_token,
client_id: client_id,
client_secret: WorkOS.config.key!,
ip_address: ip_address,
user_agent: user_agent,
grant_type: 'refresh_token',
},
),
)

WorkOS::RefreshAuthenticationResponse.new(response.body)
end

# Authenticate user by Magic Auth Code.
#
# @param [String] code The one-time code that was emailed to the user.
Expand Down Expand Up @@ -554,6 +594,66 @@ def authenticate_with_email_verification(
WorkOS::AuthenticationResponse.new(response.body)
end

# Get the logout URL for a session
#
# The user's browser should be navigated to this URL
#
# @param [String] session_id The session ID can be found in the `sid`
# claim of the access token
#
# @return String
sig do
params(
session_id: String,
).returns(String)
end
def get_logout_url(session_id:)
URI::HTTPS.build(
host: WorkOS.config.api_hostname,
path: '/user_management/sessions/logout',
query: "session_id=#{session_id}",
).to_s
end

# Revokes a session
#
# @param [String] session_id The session ID can be found in the `sid`
# claim of the access token
sig do
params(
session_id: String,
).void
end
def revoke_session(session_id:)
execute_request(
request: post_request(
path: '/user_management/sessions/revoke',
body: {
session_id: session_id,
},
),
)
end

# Get the JWKS URL
#
# The JWKS can be used to validate the access token returned upon successful authentication
#
# @param [String] client_id The WorkOS client ID for the environment
#
# @return String
sig do
params(
client_id: String,
).returns(String)
end
def get_jwks_url(client_id)
URI::HTTPS.build(
host: WorkOS.config.api_hostname,
path: "/sso/jwks/#{client_id}",
).to_s
end

# Create a one-time Magic Auth code and emails it to the user.
#
# @param [String] email The email address the one-time code will be sent to.
Expand Down
52 changes: 43 additions & 9 deletions spec/lib/workos/user_management_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@
describe '.authenticate_with_password' do
context 'with a valid password' do
it 'returns user' do
VCR.use_cassette('user_management/authenticate_with_password/valid') do
VCR.use_cassette('user_management/authenticate_with_password/valid', tag: :token) do
authentication_response = WorkOS::UserManagement.authenticate_with_password(
email: 'test@workos.app',
password: '7YtYic00VWcXatPb',
Expand Down Expand Up @@ -418,6 +418,8 @@
user_agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) Chrome/108.0.0.0 Safari/537.36',
)
expect(authentication_response.user.id).to eq('user_01H93ZY4F80YZRRS6N59Z2HFVS')
expect(authentication_response.access_token).to eq('<ACCESS_TOKEN>')
expect(authentication_response.refresh_token).to eq('<REFRESH_TOKEN>')
end
end

Expand Down Expand Up @@ -454,10 +456,42 @@
end
end

describe '.authenticate_with_refresh_token' do
context 'with a valid refresh_token' do
it 'returns user' do
VCR.use_cassette('user_management/authenticate_with_refresh_token/valid', tag: :token) do
authentication_response = WorkOS::UserManagement.authenticate_with_refresh_token(
refresh_token: 'some_refresh_token',
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.access_token).to eq('<ACCESS_TOKEN>')
expect(authentication_response.refresh_token).to eq('<REFRESH_TOKEN>')
end
end
end

context 'with an invalid refresh_token' do
it 'raises an error' do
VCR.use_cassette('user_management/authenticate_with_refresh_code/invalid', tag: :token) do
expect do
WorkOS::UserManagement.authenticate_with_refresh_token(
refresh_token: 'invalid',
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',
)
end.to raise_error(WorkOS::InvalidRequestError, /Status 400/)
end
end
end
end

describe '.authenticate_with_magic_auth' do
context 'with a valid code' do
it 'returns user' do
VCR.use_cassette('user_management/authenticate_with_magic_auth/valid') do
VCR.use_cassette('user_management/authenticate_with_magic_auth/valid', tag: :token) do
authentication_response = WorkOS::UserManagement.authenticate_with_magic_auth(
code: '452079',
client_id: 'project_01EGKAEB7G5N88E83MF99J785F',
Expand All @@ -472,7 +506,7 @@

context 'with an invalid code' do
it 'returns an error' do
VCR.use_cassette('user_management/authenticate_with_magic_auth/invalid') do
VCR.use_cassette('user_management/authenticate_with_magic_auth/invalid', tag: :token) do
expect do
WorkOS::UserManagement.authenticate_with_magic_auth(
code: 'invalid',
Expand All @@ -488,7 +522,7 @@
describe '.authenticate_with_organization_selection' do
context 'with a valid code' do
it 'returns user' do
VCR.use_cassette('user_management/authenticate_with_organization_selection/valid') do
VCR.use_cassette('user_management/authenticate_with_organization_selection/valid', tag: :token) do
authentication_response = WorkOS::UserManagement.authenticate_with_organization_selection(
client_id: 'project_01EGKAEB7G5N88E83MF99J785F',
organization_id: 'org_01H5JQDV7R7ATEYZDEG0W5PRYS',
Expand All @@ -504,7 +538,7 @@

context 'with an invalid token' do
it 'returns an error' do
VCR.use_cassette('user_management/authenticate_with_organization_selection/invalid') do
VCR.use_cassette('user_management/authenticate_with_organization_selection/invalid', tag: :token) do
expect do
WorkOS::UserManagement.authenticate_with_organization_selection(
organization_id: 'invalid_org_id',
Expand All @@ -520,7 +554,7 @@
describe '.authenticate_with_totp' do
context 'with a valid code' do
it 'returns user' do
VCR.use_cassette('user_management/authenticate_with_totp/valid') do
VCR.use_cassette('user_management/authenticate_with_totp/valid', tag: :token) do
authentication_response = WorkOS::UserManagement.authenticate_with_totp(
code: '01H93ZZHA0JBHFJH9RR11S83YN',
client_id: 'client_123',
Expand All @@ -536,7 +570,7 @@

context 'with an invalid code' do
it 'raises an error' do
VCR.use_cassette('user_management/authenticate_with_totp/invalid') do
VCR.use_cassette('user_management/authenticate_with_totp/invalid', tag: :token) do
expect do
WorkOS::UserManagement.authenticate_with_totp(
code: 'invalid',
Expand All @@ -555,7 +589,7 @@
describe '.authenticate_with_email_verification' do
context 'with a valid code' do
it 'returns user' do
VCR.use_cassette('user_management/authenticate_with_email_verification/valid') do
VCR.use_cassette('user_management/authenticate_with_email_verification/valid', tag: :token) do
authentication_response = WorkOS::UserManagement.authenticate_with_email_verification(
code: '01H93ZZHA0JBHFJH9RR11S83YN',
client_id: 'client_123',
Expand All @@ -570,7 +604,7 @@

context 'with an invalid code' do
it 'raises an error' do
VCR.use_cassette('user_management/authenticate_with_email_verification/invalid') do
VCR.use_cassette('user_management/authenticate_with_email_verification/invalid', tag: :token) do
expect do
WorkOS::UserManagement.authenticate_with_email_verification(
code: 'invalid',
Expand Down
6 changes: 6 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
VCR.configure do |config|
config.cassette_library_dir = 'spec/support/fixtures/vcr_cassettes'
config.filter_sensitive_data('<API_KEY>') { WorkOS.config.key }
config.filter_sensitive_data('<ACCESS_TOKEN>', :token) do |interaction|
JSON.parse(interaction.response.body)['access_token']
end
config.filter_sensitive_data('<REFRESH_TOKEN>', :token) do |interaction|
JSON.parse(interaction.response.body)['refresh_token']
end
config.hook_into :webmock
end

Expand Down

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

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

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

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

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

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

Loading

0 comments on commit b75d2ef

Please sign in to comment.