workos 5.6.0 → 5.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +7 -1
- data/lib/workos/authentication_response.rb +25 -4
- data/lib/workos/refresh_authentication_response.rb +25 -2
- data/lib/workos/session.rb +183 -0
- data/lib/workos/user_management.rb +34 -4
- data/lib/workos/version.rb +1 -1
- data/lib/workos.rb +1 -0
- data/spec/lib/workos/session_spec.rb +214 -0
- data/spec/lib/workos/user_management_spec.rb +1 -0
- data/spec/support/fixtures/vcr_cassettes/user_management/authenticate_with_refresh_token/valid.yml +79 -78
- data/workos.gemspec +3 -0
- metadata +33 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9c1790feb170b50e199cef2297443aef2021978b3e1fe0c48c28e9b144e4120b
         | 
| 4 | 
            +
              data.tar.gz: 1b78e33b06c689280525ffb2f94847a86d17f2c9001ba229a16745511698442f
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: d4f641c69ad3f57def5fbb4533aef65a7fd294b59e169e4ef1d141cea58732643417727b1c84d510a3cf379e28f9b4fad931aea6d2c9cf50cf84765be29033d5
         | 
| 7 | 
            +
              data.tar.gz: 58f22a8a48d35941994f9877adbc02ed6ec2dd7cf9b6107401156cc8665912a54956e304aa64229ebd5515ddab14977ae293768700171ee7a1593efff272f59d
         | 
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,7 +1,9 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                workos (5. | 
| 4 | 
            +
                workos (5.7.0)
         | 
| 5 | 
            +
                  encryptor (~> 3.0)
         | 
| 6 | 
            +
                  jwt (~> 2.8)
         | 
| 5 7 |  | 
| 6 8 | 
             
            GEM
         | 
| 7 9 | 
             
              remote: https://rubygems.org/
         | 
| @@ -9,12 +11,16 @@ GEM | |
| 9 11 | 
             
                addressable (2.8.6)
         | 
| 10 12 | 
             
                  public_suffix (>= 2.0.2, < 6.0)
         | 
| 11 13 | 
             
                ast (2.4.2)
         | 
| 14 | 
            +
                base64 (0.2.0)
         | 
| 12 15 | 
             
                bigdecimal (3.1.7)
         | 
| 13 16 | 
             
                crack (1.0.0)
         | 
| 14 17 | 
             
                  bigdecimal
         | 
| 15 18 | 
             
                  rexml
         | 
| 16 19 | 
             
                diff-lcs (1.5.1)
         | 
| 20 | 
            +
                encryptor (3.0.0)
         | 
| 17 21 | 
             
                hashdiff (1.1.0)
         | 
| 22 | 
            +
                jwt (2.8.2)
         | 
| 23 | 
            +
                  base64
         | 
| 18 24 | 
             
                parallel (1.24.0)
         | 
| 19 25 | 
             
                parser (3.3.0.5)
         | 
| 20 26 | 
             
                  ast (~> 2.4.1)
         | 
| @@ -6,10 +6,19 @@ module WorkOS | |
| 6 6 | 
             
              class AuthenticationResponse
         | 
| 7 7 | 
             
                include HashProvider
         | 
| 8 8 |  | 
| 9 | 
            -
                attr_accessor :user, | 
| 9 | 
            +
                attr_accessor :user,
         | 
| 10 | 
            +
                              :organization_id,
         | 
| 11 | 
            +
                              :impersonator,
         | 
| 12 | 
            +
                              :access_token,
         | 
| 13 | 
            +
                              :refresh_token,
         | 
| 14 | 
            +
                              :authentication_method,
         | 
| 15 | 
            +
                              :sealed_session
         | 
| 10 16 |  | 
| 11 | 
            -
                 | 
| 17 | 
            +
                # rubocop:disable Metrics/AbcSize
         | 
| 18 | 
            +
                def initialize(authentication_response_json, session = nil)
         | 
| 12 19 | 
             
                  json = JSON.parse(authentication_response_json, symbolize_names: true)
         | 
| 20 | 
            +
                  @access_token = json[:access_token]
         | 
| 21 | 
            +
                  @refresh_token = json[:refresh_token]
         | 
| 13 22 | 
             
                  @user = WorkOS::User.new(json[:user].to_json)
         | 
| 14 23 | 
             
                  @organization_id = json[:organization_id]
         | 
| 15 24 | 
             
                  @impersonator =
         | 
| @@ -17,9 +26,19 @@ module WorkOS | |
| 17 26 | 
             
                      Impersonator.new(email: impersonator_json[:email],
         | 
| 18 27 | 
             
                                       reason: impersonator_json[:reason],)
         | 
| 19 28 | 
             
                    end
         | 
| 20 | 
            -
                  @ | 
| 21 | 
            -
                  @ | 
| 29 | 
            +
                  @authentication_method = json[:authentication_method]
         | 
| 30 | 
            +
                  @sealed_session =
         | 
| 31 | 
            +
                    if session && session[:seal_session]
         | 
| 32 | 
            +
                      WorkOS::Session.seal_data({
         | 
| 33 | 
            +
                                                  access_token: access_token,
         | 
| 34 | 
            +
                                                  refresh_token: refresh_token,
         | 
| 35 | 
            +
                                                  user: user.to_json,
         | 
| 36 | 
            +
                                                  organization_id: organization_id,
         | 
| 37 | 
            +
                                                  impersonator: impersonator.to_json,
         | 
| 38 | 
            +
                                                }, session[:cookie_password],)
         | 
| 39 | 
            +
                    end
         | 
| 22 40 | 
             
                end
         | 
| 41 | 
            +
                # rubocop:enable Metrics/AbcSize
         | 
| 23 42 |  | 
| 24 43 | 
             
                def to_json(*)
         | 
| 25 44 | 
             
                  {
         | 
| @@ -28,6 +47,8 @@ module WorkOS | |
| 28 47 | 
             
                    impersonator: impersonator.to_json,
         | 
| 29 48 | 
             
                    access_token: access_token,
         | 
| 30 49 | 
             
                    refresh_token: refresh_token,
         | 
| 50 | 
            +
                    authentication_method: authentication_method,
         | 
| 51 | 
            +
                    sealed_session: sealed_session,
         | 
| 31 52 | 
             
                  }
         | 
| 32 53 | 
             
                end
         | 
| 33 54 | 
             
              end
         | 
| @@ -6,18 +6,41 @@ module WorkOS | |
| 6 6 | 
             
              class RefreshAuthenticationResponse
         | 
| 7 7 | 
             
                include HashProvider
         | 
| 8 8 |  | 
| 9 | 
            -
                attr_accessor :access_token, :refresh_token
         | 
| 9 | 
            +
                attr_accessor :user, :organization_id, :impersonator, :access_token, :refresh_token, :sealed_session
         | 
| 10 10 |  | 
| 11 | 
            -
                 | 
| 11 | 
            +
                # rubocop:disable Metrics/AbcSize
         | 
| 12 | 
            +
                def initialize(authentication_response_json, session = nil)
         | 
| 12 13 | 
             
                  json = JSON.parse(authentication_response_json, symbolize_names: true)
         | 
| 13 14 | 
             
                  @access_token = json[:access_token]
         | 
| 14 15 | 
             
                  @refresh_token = json[:refresh_token]
         | 
| 16 | 
            +
                  @user = WorkOS::User.new(json[:user].to_json)
         | 
| 17 | 
            +
                  @organization_id = json[:organization_id]
         | 
| 18 | 
            +
                  @impersonator =
         | 
| 19 | 
            +
                    if (impersonator_json = json[:impersonator])
         | 
| 20 | 
            +
                      Impersonator.new(email: impersonator_json[:email],
         | 
| 21 | 
            +
                                       reason: impersonator_json[:reason],)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  @sealed_session =
         | 
| 24 | 
            +
                    if session && session[:seal_session]
         | 
| 25 | 
            +
                      WorkOS::Session.seal_data({
         | 
| 26 | 
            +
                                                  access_token: access_token,
         | 
| 27 | 
            +
                                                  refresh_token: refresh_token,
         | 
| 28 | 
            +
                                                  user: user.to_json,
         | 
| 29 | 
            +
                                                  organization_id: organization_id,
         | 
| 30 | 
            +
                                                  impersonator: impersonator.to_json,
         | 
| 31 | 
            +
                                                }, session[:cookie_password],)
         | 
| 32 | 
            +
                    end
         | 
| 15 33 | 
             
                end
         | 
| 34 | 
            +
                # rubocop:enable Metrics/AbcSize
         | 
| 16 35 |  | 
| 17 36 | 
             
                def to_json(*)
         | 
| 18 37 | 
             
                  {
         | 
| 38 | 
            +
                    user: user.to_json,
         | 
| 39 | 
            +
                    organization_id: organization_id,
         | 
| 40 | 
            +
                    impersonator: impersonator.to_json,
         | 
| 19 41 | 
             
                    access_token: access_token,
         | 
| 20 42 | 
             
                    refresh_token: refresh_token,
         | 
| 43 | 
            +
                    sealed_session: sealed_session,
         | 
| 21 44 | 
             
                  }
         | 
| 22 45 | 
             
                end
         | 
| 23 46 | 
             
              end
         | 
| @@ -0,0 +1,183 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require 'jwt'
         | 
| 4 | 
            +
            require 'uri'
         | 
| 5 | 
            +
            require 'net/http'
         | 
| 6 | 
            +
            require 'encryptor'
         | 
| 7 | 
            +
            require 'securerandom'
         | 
| 8 | 
            +
            require 'json'
         | 
| 9 | 
            +
            require 'uri'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            module WorkOS
         | 
| 12 | 
            +
              # The Session class provides helper methods for working with WorkOS sessions
         | 
| 13 | 
            +
              # This class is not meant to be instantiated in a user space, and is instantiated internally but exposed.
         | 
| 14 | 
            +
              # rubocop:disable Metrics/ClassLength
         | 
| 15 | 
            +
              class Session
         | 
| 16 | 
            +
                attr_accessor :jwks, :jwks_algorithms, :user_management, :cookie_password, :session_data, :client_id
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def initialize(user_management:, client_id:, session_data:, cookie_password:)
         | 
| 19 | 
            +
                  raise ArgumentError, 'cookiePassword is required' if cookie_password.nil? || cookie_password.empty?
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  @user_management = user_management
         | 
| 22 | 
            +
                  @cookie_password = cookie_password
         | 
| 23 | 
            +
                  @session_data = session_data
         | 
| 24 | 
            +
                  @client_id = client_id
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  @jwks = create_remote_jwk_set(URI(@user_management.get_jwks_url(client_id)))
         | 
| 27 | 
            +
                  @jwks_algorithms = @jwks.map { |key| key[:alg] }.compact.uniq
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                # Authenticates the user based on the session data
         | 
| 31 | 
            +
                # @return [Hash] A hash containing the authentication response and a reason if the authentication failed
         | 
| 32 | 
            +
                def authenticate
         | 
| 33 | 
            +
                  return { authenticated: false, reason: 'NO_SESSION_COOKIE_PROVIDED' } if @session_data.nil?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  begin
         | 
| 36 | 
            +
                    session = Session.unseal_data(@session_data, @cookie_password)
         | 
| 37 | 
            +
                  rescue StandardError
         | 
| 38 | 
            +
                    return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' }
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' } unless session[:access_token]
         | 
| 42 | 
            +
                  return { authenticated: false, reason: 'INVALID_JWT' } unless is_valid_jwt(session[:access_token])
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  decoded = JWT.decode(session[:access_token], nil, true, algorithms: @jwks_algorithms, jwks: @jwks).first
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  {
         | 
| 47 | 
            +
                    authenticated: true,
         | 
| 48 | 
            +
                    session_id: decoded['sid'],
         | 
| 49 | 
            +
                    organization_id: decoded['org_id'],
         | 
| 50 | 
            +
                    role: decoded['role'],
         | 
| 51 | 
            +
                    permissions: decoded['permissions'],
         | 
| 52 | 
            +
                    user: session[:user],
         | 
| 53 | 
            +
                    impersonator: session[:impersonator],
         | 
| 54 | 
            +
                    reason: nil,
         | 
| 55 | 
            +
                  }
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                # Refreshes the session data using the refresh token stored in the session data
         | 
| 59 | 
            +
                # @param options [Hash] Options for refreshing the session
         | 
| 60 | 
            +
                # @option options [String] :cookie_password The password to use for unsealing the session data
         | 
| 61 | 
            +
                # @option options [String] :organization_id The organization ID to use for refreshing the session
         | 
| 62 | 
            +
                # @return [Hash] A hash containing a new sealed session, the authentication response,
         | 
| 63 | 
            +
                # and a reason if the refresh failed
         | 
| 64 | 
            +
                # rubocop:disable Metrics/AbcSize
         | 
| 65 | 
            +
                # rubocop:disable Metrics/CyclomaticComplexity
         | 
| 66 | 
            +
                # rubocop:disable Metrics/PerceivedComplexity
         | 
| 67 | 
            +
                def refresh(options = nil)
         | 
| 68 | 
            +
                  cookie_password = options.nil? || options[:cookie_password].nil? ? @cookie_password : options[:cookie_password]
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  begin
         | 
| 71 | 
            +
                    session = Session.unseal_data(@session_data, cookie_password)
         | 
| 72 | 
            +
                  rescue StandardError
         | 
| 73 | 
            +
                    return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' }
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  return { authenticated: false, reason: 'INVALID_SESSION_COOKIE' } unless session[:refresh_token] && session[:user]
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  begin
         | 
| 79 | 
            +
                    auth_response = @user_management.authenticate_with_refresh_token(
         | 
| 80 | 
            +
                      client_id: @client_id,
         | 
| 81 | 
            +
                      refresh_token: session[:refresh_token],
         | 
| 82 | 
            +
                      organization_id: options.nil? || options[:organization_id].nil? ? nil : options[:organization_id],
         | 
| 83 | 
            +
                      session: { seal_session: true, cookie_password: cookie_password },
         | 
| 84 | 
            +
                    )
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                    @session_data = auth_response.sealed_session
         | 
| 87 | 
            +
                    @cookie_password = cookie_password
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    {
         | 
| 90 | 
            +
                      authenticated: true,
         | 
| 91 | 
            +
                      sealed_session: auth_response.sealed_session,
         | 
| 92 | 
            +
                      session: auth_response,
         | 
| 93 | 
            +
                      reason: nil,
         | 
| 94 | 
            +
                    }
         | 
| 95 | 
            +
                  rescue StandardError => e
         | 
| 96 | 
            +
                    { authenticated: false, reason: e.message }
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
                # rubocop:enable Metrics/AbcSize
         | 
| 100 | 
            +
                # rubocop:enable Metrics/CyclomaticComplexity
         | 
| 101 | 
            +
                # rubocop:enable Metrics/PerceivedComplexity
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                # Returns a URL to redirect the user to for logging out
         | 
| 104 | 
            +
                # @return [String] The URL to redirect the user to for logging out
         | 
| 105 | 
            +
                # rubocop:disable Naming/AccessorMethodName
         | 
| 106 | 
            +
                def get_logout_url
         | 
| 107 | 
            +
                  auth_response = authenticate
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  unless auth_response[:authenticated]
         | 
| 110 | 
            +
                    raise "Failed to extract session ID for logout URL: #{auth_response[:reason]}"
         | 
| 111 | 
            +
                  end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  @user_management.get_logout_url(session_id: auth_response[:session_id])
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
                # rubocop:enable Naming/AccessorMethodName
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                # Encrypts and seals data using AES-256-GCM
         | 
| 118 | 
            +
                # @param data [Hash] The data to seal
         | 
| 119 | 
            +
                # @param key [String] The key to use for encryption
         | 
| 120 | 
            +
                # @return [String] The sealed data
         | 
| 121 | 
            +
                def self.seal_data(data, key)
         | 
| 122 | 
            +
                  iv = SecureRandom.random_bytes(12)
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                  encrypted_data = Encryptor.encrypt(
         | 
| 125 | 
            +
                    value: JSON.generate(data),
         | 
| 126 | 
            +
                    key: key,
         | 
| 127 | 
            +
                    iv: iv,
         | 
| 128 | 
            +
                    algorithm: 'aes-256-gcm',
         | 
| 129 | 
            +
                  )
         | 
| 130 | 
            +
                  Base64.encode64(iv + encrypted_data) # Combine IV with encrypted data and encode as base64
         | 
| 131 | 
            +
                end
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                # Decrypts and unseals data using AES-256-GCM
         | 
| 134 | 
            +
                # @param sealed_data [String] The sealed data to unseal
         | 
| 135 | 
            +
                # @param key [String] The key to use for decryption
         | 
| 136 | 
            +
                # @return [Hash] The unsealed data
         | 
| 137 | 
            +
                def self.unseal_data(sealed_data, key)
         | 
| 138 | 
            +
                  decoded_data = Base64.decode64(sealed_data)
         | 
| 139 | 
            +
                  iv = decoded_data[0..11] # Extract the IV (first 12 bytes)
         | 
| 140 | 
            +
                  encrypted_data = decoded_data[12..-1] # Extract the encrypted data
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                  decrypted_data = Encryptor.decrypt(
         | 
| 143 | 
            +
                    value: encrypted_data,
         | 
| 144 | 
            +
                    key: key,
         | 
| 145 | 
            +
                    iv: iv,
         | 
| 146 | 
            +
                    algorithm: 'aes-256-gcm',
         | 
| 147 | 
            +
                  )
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  JSON.parse(decrypted_data, symbolize_names: true) # Parse the decrypted JSON string back to original data
         | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                private
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                # Creates a JWKS set from a remote JWKS URL
         | 
| 155 | 
            +
                # @param uri [URI] The URI of the JWKS
         | 
| 156 | 
            +
                # @return [JWT::JWK::Set] The JWKS set
         | 
| 157 | 
            +
                def create_remote_jwk_set(uri)
         | 
| 158 | 
            +
                  # Fetch the JWKS from the remote URL
         | 
| 159 | 
            +
                  response = Net::HTTP.get(uri)
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  jwks_hash = JSON.parse(response)
         | 
| 162 | 
            +
                  jwks = JWT::JWK::Set.new(jwks_hash)
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  # filter jwks so it only returns the keys where 'use' is equal to 'sig'
         | 
| 165 | 
            +
                  jwks.keys.select! { |key| key[:use] == 'sig' }
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                  jwks
         | 
| 168 | 
            +
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                # Validates a JWT token using the JWKS set
         | 
| 171 | 
            +
                # @param token [String] The JWT token to validate
         | 
| 172 | 
            +
                # @return [Boolean] True if the token is valid, false otherwise
         | 
| 173 | 
            +
                # rubocop:disable Naming/PredicateName
         | 
| 174 | 
            +
                def is_valid_jwt(token)
         | 
| 175 | 
            +
                  JWT.decode(token, nil, true, algorithms: @jwks_algorithms, jwks: @jwks)
         | 
| 176 | 
            +
                  true
         | 
| 177 | 
            +
                rescue StandardError
         | 
| 178 | 
            +
                  false
         | 
| 179 | 
            +
                end
         | 
| 180 | 
            +
                # rubocop:enable Naming/PredicateName
         | 
| 181 | 
            +
              end
         | 
| 182 | 
            +
              # rubocop:enable Metrics/ClassLength
         | 
| 183 | 
            +
            end
         | 
| @@ -37,6 +37,22 @@ module WorkOS | |
| 37 37 | 
             
                  PROVIDERS = WorkOS::UserManagement::Types::Provider::ALL
         | 
| 38 38 | 
             
                  AUTH_FACTOR_TYPES = WorkOS::UserManagement::Types::AuthFactorType::ALL
         | 
| 39 39 |  | 
| 40 | 
            +
                  # Load a sealed session
         | 
| 41 | 
            +
                  #
         | 
| 42 | 
            +
                  # @param [String] client_id The WorkOS client ID for the environment
         | 
| 43 | 
            +
                  # @param [String] session_data The sealed session data
         | 
| 44 | 
            +
                  # @param [String] cookie_password The password used to seal the session
         | 
| 45 | 
            +
                  #
         | 
| 46 | 
            +
                  # @return WorkOS::Session
         | 
| 47 | 
            +
                  def load_sealed_session(client_id:, session_data:, cookie_password:)
         | 
| 48 | 
            +
                    WorkOS::Session.new(
         | 
| 49 | 
            +
                      user_management: self,
         | 
| 50 | 
            +
                      client_id: client_id,
         | 
| 51 | 
            +
                      session_data: session_data,
         | 
| 52 | 
            +
                      cookie_password: cookie_password,
         | 
| 53 | 
            +
                    )
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 40 56 | 
             
                  # Generate an OAuth 2.0 authorization URL that automatically directs a user
         | 
| 41 57 | 
             
                  # to their Identity Provider.
         | 
| 42 58 | 
             
                  #
         | 
| @@ -289,14 +305,21 @@ module WorkOS | |
| 289 305 | 
             
                  # @param [String] client_id The WorkOS client ID for the environment
         | 
| 290 306 | 
             
                  # @param [String] ip_address The IP address of the request from the user who is attempting to authenticate.
         | 
| 291 307 | 
             
                  # @param [String] user_agent The user agent of the request from the user who is attempting to authenticate.
         | 
| 308 | 
            +
                  # @param [Hash] session An optional hash that determines whether the session should be sealed and
         | 
| 309 | 
            +
                  # the optional cookie password.
         | 
| 292 310 | 
             
                  #
         | 
| 293 311 | 
             
                  # @return WorkOS::AuthenticationResponse
         | 
| 294 312 | 
             
                  def authenticate_with_code(
         | 
| 295 313 | 
             
                    code:,
         | 
| 296 314 | 
             
                    client_id:,
         | 
| 297 315 | 
             
                    ip_address: nil,
         | 
| 298 | 
            -
                    user_agent: nil
         | 
| 316 | 
            +
                    user_agent: nil,
         | 
| 317 | 
            +
                    session: nil
         | 
| 299 318 | 
             
                  )
         | 
| 319 | 
            +
                    if session && (session[:seal_session] == true) && session[:cookie_password].nil?
         | 
| 320 | 
            +
                      raise ArgumentError, 'cookie_password is required when sealing session'
         | 
| 321 | 
            +
                    end
         | 
| 322 | 
            +
             | 
| 300 323 | 
             
                    response = execute_request(
         | 
| 301 324 | 
             
                      request: post_request(
         | 
| 302 325 | 
             
                        path: '/user_management/authenticate',
         | 
| @@ -311,7 +334,7 @@ module WorkOS | |
| 311 334 | 
             
                      ),
         | 
| 312 335 | 
             
                    )
         | 
| 313 336 |  | 
| 314 | 
            -
                    WorkOS::AuthenticationResponse.new(response.body)
         | 
| 337 | 
            +
                    WorkOS::AuthenticationResponse.new(response.body, session)
         | 
| 315 338 | 
             
                  end
         | 
| 316 339 |  | 
| 317 340 | 
             
                  # Authenticate a user using a refresh token.
         | 
| @@ -321,6 +344,8 @@ module WorkOS | |
| 321 344 | 
             
                  # @param [String] organization_id The organization to issue the new access token for. (Optional)
         | 
| 322 345 | 
             
                  # @param [String] ip_address The IP address of the request from the user who is attempting to authenticate.
         | 
| 323 346 | 
             
                  # @param [String] user_agent The user agent of the request from the user who is attempting to authenticate.
         | 
| 347 | 
            +
                  # @param [Hash] session An optional hash that determines whether the session should be sealed and
         | 
| 348 | 
            +
                  # the optional cookie password.
         | 
| 324 349 | 
             
                  #
         | 
| 325 350 | 
             
                  # @return WorkOS::RefreshAuthenticationResponse
         | 
| 326 351 | 
             
                  def authenticate_with_refresh_token(
         | 
| @@ -328,8 +353,13 @@ module WorkOS | |
| 328 353 | 
             
                    client_id:,
         | 
| 329 354 | 
             
                    organization_id: nil,
         | 
| 330 355 | 
             
                    ip_address: nil,
         | 
| 331 | 
            -
                    user_agent: nil
         | 
| 356 | 
            +
                    user_agent: nil,
         | 
| 357 | 
            +
                    session: nil
         | 
| 332 358 | 
             
                  )
         | 
| 359 | 
            +
                    if session && (session[:seal_session] == true) && session[:cookie_password].nil?
         | 
| 360 | 
            +
                      raise ArgumentError, 'cookie_password is required when sealing session'
         | 
| 361 | 
            +
                    end
         | 
| 362 | 
            +
             | 
| 333 363 | 
             
                    response = execute_request(
         | 
| 334 364 | 
             
                      request: post_request(
         | 
| 335 365 | 
             
                        path: '/user_management/authenticate',
         | 
| @@ -345,7 +375,7 @@ module WorkOS | |
| 345 375 | 
             
                      ),
         | 
| 346 376 | 
             
                    )
         | 
| 347 377 |  | 
| 348 | 
            -
                    WorkOS::RefreshAuthenticationResponse.new(response.body)
         | 
| 378 | 
            +
                    WorkOS::RefreshAuthenticationResponse.new(response.body, session)
         | 
| 349 379 | 
             
                  end
         | 
| 350 380 |  | 
| 351 381 | 
             
                  # Authenticate user by Magic Auth Code.
         | 
    
        data/lib/workos/version.rb
    CHANGED
    
    
    
        data/lib/workos.rb
    CHANGED
    
    | @@ -71,6 +71,7 @@ module WorkOS | |
| 71 71 | 
             
              autoload :Profile, 'workos/profile'
         | 
| 72 72 | 
             
              autoload :ProfileAndToken, 'workos/profile_and_token'
         | 
| 73 73 | 
             
              autoload :RefreshAuthenticationResponse, 'workos/refresh_authentication_response'
         | 
| 74 | 
            +
              autoload :Session, 'workos/session'
         | 
| 74 75 | 
             
              autoload :SSO, 'workos/sso'
         | 
| 75 76 | 
             
              autoload :Types, 'workos/types'
         | 
| 76 77 | 
             
              autoload :User, 'workos/user'
         | 
| @@ -0,0 +1,214 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe WorkOS::Session do
         | 
| 4 | 
            +
              let(:user_management) { instance_double('UserManagement') }
         | 
| 5 | 
            +
              let(:client_id) { 'test_client_id' }
         | 
| 6 | 
            +
              let(:cookie_password) { 'test_very_long_cookie_password__' }
         | 
| 7 | 
            +
              let(:session_data) { 'test_session_data' }
         | 
| 8 | 
            +
              let(:jwks_url) { 'https://api.workos.com/sso/jwks/client_123' }
         | 
| 9 | 
            +
              let(:jwks_hash) { '{"keys":[{"alg":"RS256","kty":"RSA","use":"sig","n":"test_n","e":"AQAB","kid":"sso_oidc_key_pair_123","x5c":["test"],"x5t#S256":"test"}]}' } # rubocop:disable all
         | 
| 10 | 
            +
              let(:jwk) { JWT::JWK.new(OpenSSL::PKey::RSA.new(2048), { kid: 'sso_oidc_key_pair_123', use: 'sig', alg: 'RS256' }) }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              before do
         | 
| 13 | 
            +
                allow(user_management).to receive(:get_jwks_url).with(client_id).and_return(jwks_url)
         | 
| 14 | 
            +
                allow(Net::HTTP).to receive(:get).and_return(jwks_hash)
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              describe 'initialize' do
         | 
| 18 | 
            +
                it 'raises an error if cookie_password is nil or empty' do
         | 
| 19 | 
            +
                  expect do
         | 
| 20 | 
            +
                    WorkOS::Session.new(
         | 
| 21 | 
            +
                      user_management: user_management,
         | 
| 22 | 
            +
                      client_id: client_id,
         | 
| 23 | 
            +
                      session_data: session_data,
         | 
| 24 | 
            +
                      cookie_password: nil,
         | 
| 25 | 
            +
                    )
         | 
| 26 | 
            +
                  end.to raise_error(ArgumentError, 'cookiePassword is required')
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  expect do
         | 
| 29 | 
            +
                    WorkOS::Session.new(
         | 
| 30 | 
            +
                      user_management: user_management,
         | 
| 31 | 
            +
                      client_id: client_id,
         | 
| 32 | 
            +
                      session_data: session_data,
         | 
| 33 | 
            +
                      cookie_password: '',
         | 
| 34 | 
            +
                    )
         | 
| 35 | 
            +
                  end.to raise_error(ArgumentError, 'cookiePassword is required')
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it 'initializes with valid parameters' do
         | 
| 39 | 
            +
                  session = WorkOS::Session.new(
         | 
| 40 | 
            +
                    user_management: user_management,
         | 
| 41 | 
            +
                    client_id: client_id,
         | 
| 42 | 
            +
                    session_data: session_data,
         | 
| 43 | 
            +
                    cookie_password: cookie_password,
         | 
| 44 | 
            +
                  )
         | 
| 45 | 
            +
                  expect(session.user_management).to eq(user_management)
         | 
| 46 | 
            +
                  expect(session.client_id).to eq(client_id)
         | 
| 47 | 
            +
                  expect(session.session_data).to eq(session_data)
         | 
| 48 | 
            +
                  expect(session.cookie_password).to eq(cookie_password)
         | 
| 49 | 
            +
                  expect(session.jwks.map(&:export)).to eq(JSON.parse(jwks_hash, symbolize_names: true)[:keys])
         | 
| 50 | 
            +
                  expect(session.jwks_algorithms).to eq(['RS256'])
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
              end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
              describe '.authenticate' do
         | 
| 55 | 
            +
                let(:valid_access_token) do
         | 
| 56 | 
            +
                  payload = {
         | 
| 57 | 
            +
                    sid: 'session_id',
         | 
| 58 | 
            +
                    org_id: 'org_id',
         | 
| 59 | 
            +
                    role: 'role',
         | 
| 60 | 
            +
                    permissions: ['read'],
         | 
| 61 | 
            +
                    exp: Time.now.to_i + 3600,
         | 
| 62 | 
            +
                  }
         | 
| 63 | 
            +
                  headers = { kid: jwk[:kid] }
         | 
| 64 | 
            +
                  JWT.encode(payload, jwk.signing_key, jwk[:alg], headers)
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
                let(:session_data) do
         | 
| 67 | 
            +
              WorkOS::Session.seal_data({
         | 
| 68 | 
            +
                                          access_token: valid_access_token,
         | 
| 69 | 
            +
                                          user: 'user',
         | 
| 70 | 
            +
                                          impersonator: 'impersonator',
         | 
| 71 | 
            +
                                        }, cookie_password,)
         | 
| 72 | 
            +
            end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                it 'returns NO_SESSION_COOKIE_PROVIDED if session_data is nil' do
         | 
| 75 | 
            +
                  session = WorkOS::Session.new(
         | 
| 76 | 
            +
                    user_management: user_management,
         | 
| 77 | 
            +
                    client_id: client_id,
         | 
| 78 | 
            +
                    session_data: nil,
         | 
| 79 | 
            +
                    cookie_password: cookie_password,
         | 
| 80 | 
            +
                  )
         | 
| 81 | 
            +
                  result = session.authenticate
         | 
| 82 | 
            +
                  expect(result).to eq({ authenticated: false, reason: 'NO_SESSION_COOKIE_PROVIDED' })
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                it 'returns INVALID_SESSION_COOKIE if session_data is invalid' do
         | 
| 86 | 
            +
                  session = WorkOS::Session.new(
         | 
| 87 | 
            +
                    user_management: user_management,
         | 
| 88 | 
            +
                    client_id: client_id,
         | 
| 89 | 
            +
                    session_data: 'invalid_data',
         | 
| 90 | 
            +
                    cookie_password: cookie_password,
         | 
| 91 | 
            +
                  )
         | 
| 92 | 
            +
                  result = session.authenticate
         | 
| 93 | 
            +
                  expect(result).to eq({ authenticated: false, reason: 'INVALID_SESSION_COOKIE' })
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                it 'returns INVALID_JWT if access_token is invalid' do
         | 
| 97 | 
            +
                  invalid_session_data = WorkOS::Session.seal_data({ access_token: 'invalid_token' }, cookie_password)
         | 
| 98 | 
            +
                  session = WorkOS::Session.new(
         | 
| 99 | 
            +
                    user_management: user_management,
         | 
| 100 | 
            +
                    client_id: client_id,
         | 
| 101 | 
            +
                    session_data: invalid_session_data,
         | 
| 102 | 
            +
                    cookie_password: cookie_password,
         | 
| 103 | 
            +
                  )
         | 
| 104 | 
            +
                  result = session.authenticate
         | 
| 105 | 
            +
                  expect(result).to eq({ authenticated: false, reason: 'INVALID_JWT' })
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                it 'authenticates successfully with valid session_data' do
         | 
| 109 | 
            +
                  session = WorkOS::Session.new(
         | 
| 110 | 
            +
                    user_management: user_management,
         | 
| 111 | 
            +
                    client_id: client_id,
         | 
| 112 | 
            +
                    session_data: session_data,
         | 
| 113 | 
            +
                    cookie_password: cookie_password,
         | 
| 114 | 
            +
                  )
         | 
| 115 | 
            +
                  allow(session).to receive(:is_valid_jwt).and_return(true)
         | 
| 116 | 
            +
                  allow(JWT).to receive(:decode).and_return([{
         | 
| 117 | 
            +
                                                              'sid' => 'session_id',
         | 
| 118 | 
            +
                                                              'org_id' => 'org_id',
         | 
| 119 | 
            +
                                                              'role' => 'role',
         | 
| 120 | 
            +
                                                              'permissions' => ['read'],
         | 
| 121 | 
            +
                                                            }])
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  result = session.authenticate
         | 
| 124 | 
            +
                  expect(result).to eq({
         | 
| 125 | 
            +
                                         authenticated: true,
         | 
| 126 | 
            +
                                         session_id: 'session_id',
         | 
| 127 | 
            +
                                         organization_id: 'org_id',
         | 
| 128 | 
            +
                                         role: 'role',
         | 
| 129 | 
            +
                                         permissions: ['read'],
         | 
| 130 | 
            +
                                         user: 'user',
         | 
| 131 | 
            +
                                         impersonator: 'impersonator',
         | 
| 132 | 
            +
                                         reason: nil,
         | 
| 133 | 
            +
                                       })
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
              end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
              describe '.refresh' do
         | 
| 138 | 
            +
                let(:refresh_token) { 'test_refresh_token' }
         | 
| 139 | 
            +
                let(:session_data) { WorkOS::Session.seal_data({ refresh_token: refresh_token, user: 'user' }, cookie_password) }
         | 
| 140 | 
            +
                let(:auth_response) { double('AuthResponse', sealed_session: 'new_sealed_session') }
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                before do
         | 
| 143 | 
            +
                  allow(user_management).to receive(:authenticate_with_refresh_token).and_return(auth_response)
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                it 'returns INVALID_SESSION_COOKIE if session_data is invalid' do
         | 
| 147 | 
            +
                  session = WorkOS::Session.new(
         | 
| 148 | 
            +
                    user_management: user_management,
         | 
| 149 | 
            +
                    client_id: client_id,
         | 
| 150 | 
            +
                    session_data: 'invalid_data',
         | 
| 151 | 
            +
                    cookie_password: cookie_password,
         | 
| 152 | 
            +
                  )
         | 
| 153 | 
            +
                  result = session.refresh
         | 
| 154 | 
            +
                  expect(result).to eq({ authenticated: false, reason: 'INVALID_SESSION_COOKIE' })
         | 
| 155 | 
            +
                end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                it 'refreshes the session successfully with valid session_data' do
         | 
| 158 | 
            +
                  session = WorkOS::Session.new(
         | 
| 159 | 
            +
                    user_management: user_management,
         | 
| 160 | 
            +
                    client_id: client_id,
         | 
| 161 | 
            +
                    session_data: session_data,
         | 
| 162 | 
            +
                    cookie_password: cookie_password,
         | 
| 163 | 
            +
                  )
         | 
| 164 | 
            +
                  result = session.refresh
         | 
| 165 | 
            +
                  expect(result).to eq({
         | 
| 166 | 
            +
                                         authenticated: true,
         | 
| 167 | 
            +
                                         sealed_session: 'new_sealed_session',
         | 
| 168 | 
            +
                                         session: auth_response,
         | 
| 169 | 
            +
                                         reason: nil,
         | 
| 170 | 
            +
                                       })
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
              end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
              describe '.get_logout_url' do
         | 
| 175 | 
            +
                let(:session) do
         | 
| 176 | 
            +
                WorkOS::Session.new(
         | 
| 177 | 
            +
                  user_management: user_management,
         | 
| 178 | 
            +
                  client_id: client_id,
         | 
| 179 | 
            +
                  session_data: session_data,
         | 
| 180 | 
            +
                  cookie_password: cookie_password,
         | 
| 181 | 
            +
                )
         | 
| 182 | 
            +
              end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                context 'when authentication is successful' do
         | 
| 185 | 
            +
                  before do
         | 
| 186 | 
            +
                    allow(session).to receive(:authenticate).and_return({
         | 
| 187 | 
            +
                                                                          authenticated: true,
         | 
| 188 | 
            +
                                                                          session_id: 'session_id',
         | 
| 189 | 
            +
                                                                          reason: nil,
         | 
| 190 | 
            +
                                                                        })
         | 
| 191 | 
            +
                    allow(user_management).to receive(:get_logout_url).with(session_id: 'session_id').and_return('https://example.com/logout')
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  it 'returns the logout URL' do
         | 
| 195 | 
            +
                    expect(session.get_logout_url).to eq('https://example.com/logout')
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
                end
         | 
| 198 | 
            +
             | 
| 199 | 
            +
                context 'when authentication fails' do
         | 
| 200 | 
            +
                  before do
         | 
| 201 | 
            +
                    allow(session).to receive(:authenticate).and_return({
         | 
| 202 | 
            +
                                                                          authenticated: false,
         | 
| 203 | 
            +
                                                                          reason: 'Invalid session',
         | 
| 204 | 
            +
                                                                        })
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                  it 'raises an error' do
         | 
| 208 | 
            +
                    expect { session.get_logout_url }.to raise_error(
         | 
| 209 | 
            +
                      RuntimeError, 'Failed to extract session ID for logout URL: Invalid session',
         | 
| 210 | 
            +
                    )
         | 
| 211 | 
            +
                  end
         | 
| 212 | 
            +
                end
         | 
| 213 | 
            +
              end
         | 
| 214 | 
            +
            end
         | 
| @@ -467,6 +467,7 @@ describe WorkOS::UserManagement do | |
| 467 467 | 
             
                      )
         | 
| 468 468 | 
             
                      expect(authentication_response.access_token).to eq('<ACCESS_TOKEN>')
         | 
| 469 469 | 
             
                      expect(authentication_response.refresh_token).to eq('<REFRESH_TOKEN>')
         | 
| 470 | 
            +
                      expect(authentication_response.user.id).to eq('user_01H93WD0R0KWF8Q7BK02C0RPYJ')
         | 
| 470 471 | 
             
                    end
         | 
| 471 472 | 
             
                  end
         | 
| 472 473 | 
             
                end
         | 
    
        data/spec/support/fixtures/vcr_cassettes/user_management/authenticate_with_refresh_token/valid.yml
    CHANGED
    
    | @@ -1,81 +1,82 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            http_interactions:
         | 
| 3 | 
            -
            - request:
         | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
                   | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
                 | 
| 21 | 
            -
                   | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                   | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
             | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
             | 
| 72 | 
            -
             | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 77 | 
            -
                   | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 3 | 
            +
              - request:
         | 
| 4 | 
            +
                  method: post
         | 
| 5 | 
            +
                  uri: https://api.workos.com/user_management/authenticate
         | 
| 6 | 
            +
                  body:
         | 
| 7 | 
            +
                    encoding: UTF-8
         | 
| 8 | 
            +
                    string:
         | 
| 9 | 
            +
                      '{"refresh_token":"some_refresh_token","client_id":"client_123","client_secret":"<API_KEY>","ip_address":"200.240.210.16","user_agent":"Mozilla/5.0
         | 
| 10 | 
            +
                      (Macintosh; Intel Mac OS X 10_15_7) Chrome/108.0.0.0 Safari/537.36","grant_type":"refresh_token"}'
         | 
| 11 | 
            +
                  headers:
         | 
| 12 | 
            +
                    Content-Type:
         | 
| 13 | 
            +
                      - application/json
         | 
| 14 | 
            +
                    Accept-Encoding:
         | 
| 15 | 
            +
                      - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
         | 
| 16 | 
            +
                    Accept:
         | 
| 17 | 
            +
                      - '*/*'
         | 
| 18 | 
            +
                    User-Agent:
         | 
| 19 | 
            +
                      - WorkOS; ruby/3.2.2; arm64-darwin22; v4.0.0
         | 
| 20 | 
            +
                response:
         | 
| 21 | 
            +
                  status:
         | 
| 22 | 
            +
                    code: 200
         | 
| 23 | 
            +
                    message: OK
         | 
| 24 | 
            +
                  headers:
         | 
| 25 | 
            +
                    Date:
         | 
| 26 | 
            +
                      - Mon, 18 Mar 2024 19:00:53 GMT
         | 
| 27 | 
            +
                    Content-Type:
         | 
| 28 | 
            +
                      - application/json; charset=utf-8
         | 
| 29 | 
            +
                    Transfer-Encoding:
         | 
| 30 | 
            +
                      - chunked
         | 
| 31 | 
            +
                    Connection:
         | 
| 32 | 
            +
                      - keep-alive
         | 
| 33 | 
            +
                    Cf-Ray:
         | 
| 34 | 
            +
                      - 866777d63b4627e8-SLC
         | 
| 35 | 
            +
                    Cf-Cache-Status:
         | 
| 36 | 
            +
                      - DYNAMIC
         | 
| 37 | 
            +
                    Etag:
         | 
| 38 | 
            +
                      - W/"335-M3MDQYhs5724SayBHHCwnBDn3qA"
         | 
| 39 | 
            +
                    Strict-Transport-Security:
         | 
| 40 | 
            +
                      - max-age=15552000; includeSubDomains
         | 
| 41 | 
            +
                    Vary:
         | 
| 42 | 
            +
                      - Origin, Accept-Encoding
         | 
| 43 | 
            +
                    Via:
         | 
| 44 | 
            +
                      - 1.1 spaces-router (devel)
         | 
| 45 | 
            +
                    Access-Control-Allow-Credentials:
         | 
| 46 | 
            +
                      - 'true'
         | 
| 47 | 
            +
                    Content-Security-Policy:
         | 
| 48 | 
            +
                      - "default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self'
         | 
| 49 | 
            +
                        https: data:;frame-ancestors 'self';img-src 'self' data:;object-src 'none';script-src
         | 
| 50 | 
            +
                        'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests"
         | 
| 51 | 
            +
                    Expect-Ct:
         | 
| 52 | 
            +
                      - max-age=0
         | 
| 53 | 
            +
                    Referrer-Policy:
         | 
| 54 | 
            +
                      - no-referrer
         | 
| 55 | 
            +
                    X-Content-Type-Options:
         | 
| 56 | 
            +
                      - nosniff
         | 
| 57 | 
            +
                    X-Dns-Prefetch-Control:
         | 
| 58 | 
            +
                      - 'off'
         | 
| 59 | 
            +
                    X-Download-Options:
         | 
| 60 | 
            +
                      - noopen
         | 
| 61 | 
            +
                    X-Frame-Options:
         | 
| 62 | 
            +
                      - SAMEORIGIN
         | 
| 63 | 
            +
                    X-Permitted-Cross-Domain-Policies:
         | 
| 64 | 
            +
                      - none
         | 
| 65 | 
            +
                    X-Request-Id:
         | 
| 66 | 
            +
                      - 995ed1ed-e892-4049-86c9-0e07baa6cc4b
         | 
| 67 | 
            +
                    X-Xss-Protection:
         | 
| 68 | 
            +
                      - '0'
         | 
| 69 | 
            +
                    Set-Cookie:
         | 
| 70 | 
            +
                      - __cf_bm=2NHqv1cd1BisOc8KKcQ0oNzFxZZT4OHQd6c2QDuGnUU-1710788453-1.0.1.1-4BxBRzVrhL7rCH895PcfORXr_6Rnj3Oh5w1YG4xi7X1st62LMzb5dHZO7u7P.V1P8nBDAAt3Wbz7xsDTWrfWJg;
         | 
| 71 | 
            +
                        path=/; expires=Mon, 18-Mar-24 19:30:53 GMT; domain=.workos.com; HttpOnly;
         | 
| 72 | 
            +
                        Secure; SameSite=None
         | 
| 73 | 
            +
                      - __cfruid=06035c17e9b60a1d7a42a5b568146a0bb71a06dc-1710788453; path=/; domain=.workos.com;
         | 
| 74 | 
            +
                        HttpOnly; Secure; SameSite=None
         | 
| 75 | 
            +
                    Server:
         | 
| 76 | 
            +
                      - cloudflare
         | 
| 77 | 
            +
                  body:
         | 
| 78 | 
            +
                    encoding: UTF-8
         | 
| 79 | 
            +
                    string: '{"user":{"object":"user","id":"user_01H93WD0R0KWF8Q7BK02C0RPYJ","email":"test@workos.app","email_verified":true,"first_name":"Lucille","last_name":"Bluth","created_at":"2023-08-30T18:48:26.517Z","updated_at":"2023-08-30T18:58:00.821Z","user_type":"unmanaged","email_verified_at":"2023-08-30T18:58:00.915Z","google_oauth_profile_id":null,"microsoft_oauth_profile_id":null},"access_token":"<ACCESS_TOKEN>","refresh_token":"<REFRESH_TOKEN>"}'
         | 
| 80 | 
            +
                  http_version:
         | 
| 81 | 
            +
                recorded_at: Mon, 18 Mar 2024 19:00:53 GMT
         | 
| 81 82 | 
             
            recorded_with: VCR 5.0.0
         | 
    
        data/workos.gemspec
    CHANGED
    
    | @@ -21,6 +21,9 @@ Gem::Specification.new do |spec| | |
| 21 21 | 
             
              spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
         | 
| 22 22 | 
             
              spec.require_paths = ['lib']
         | 
| 23 23 |  | 
| 24 | 
            +
              spec.add_dependency 'encryptor', '~> 3.0'
         | 
| 25 | 
            +
              spec.add_dependency 'jwt', '~> 2.8'
         | 
| 26 | 
            +
             | 
| 24 27 | 
             
              spec.add_development_dependency 'bundler', '>= 2.0.1'
         | 
| 25 28 | 
             
              spec.add_development_dependency 'rspec', '~> 3.9.0'
         | 
| 26 29 | 
             
              spec.add_development_dependency 'rubocop', '~> 0.77'
         | 
    
        metadata
    CHANGED
    
    | @@ -1,15 +1,43 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: workos
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 5. | 
| 4 | 
            +
              version: 5.7.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - WorkOS
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024-08- | 
| 11 | 
            +
            date: 2024-08-29 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 14 | 
            +
              name: encryptor
         | 
| 15 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 16 | 
            +
                requirements:
         | 
| 17 | 
            +
                - - "~>"
         | 
| 18 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 19 | 
            +
                    version: '3.0'
         | 
| 20 | 
            +
              type: :runtime
         | 
| 21 | 
            +
              prerelease: false
         | 
| 22 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 23 | 
            +
                requirements:
         | 
| 24 | 
            +
                - - "~>"
         | 
| 25 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 26 | 
            +
                    version: '3.0'
         | 
| 27 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 28 | 
            +
              name: jwt
         | 
| 29 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 30 | 
            +
                requirements:
         | 
| 31 | 
            +
                - - "~>"
         | 
| 32 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 33 | 
            +
                    version: '2.8'
         | 
| 34 | 
            +
              type: :runtime
         | 
| 35 | 
            +
              prerelease: false
         | 
| 36 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 37 | 
            +
                requirements:
         | 
| 38 | 
            +
                - - "~>"
         | 
| 39 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 40 | 
            +
                    version: '2.8'
         | 
| 13 41 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 42 | 
             
              name: bundler
         | 
| 15 43 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -137,6 +165,7 @@ files: | |
| 137 165 | 
             
            - lib/workos/profile.rb
         | 
| 138 166 | 
             
            - lib/workos/profile_and_token.rb
         | 
| 139 167 | 
             
            - lib/workos/refresh_authentication_response.rb
         | 
| 168 | 
            +
            - lib/workos/session.rb
         | 
| 140 169 | 
             
            - lib/workos/sso.rb
         | 
| 141 170 | 
             
            - lib/workos/types.rb
         | 
| 142 171 | 
             
            - lib/workos/types/intent.rb
         | 
| @@ -161,6 +190,7 @@ files: | |
| 161 190 | 
             
            - spec/lib/workos/organizations_spec.rb
         | 
| 162 191 | 
             
            - spec/lib/workos/passwordless_spec.rb
         | 
| 163 192 | 
             
            - spec/lib/workos/portal_spec.rb
         | 
| 193 | 
            +
            - spec/lib/workos/session_spec.rb
         | 
| 164 194 | 
             
            - spec/lib/workos/sso_spec.rb
         | 
| 165 195 | 
             
            - spec/lib/workos/user_management_spec.rb
         | 
| 166 196 | 
             
            - spec/lib/workos/webhooks_spec.rb
         | 
| @@ -373,6 +403,7 @@ test_files: | |
| 373 403 | 
             
            - spec/lib/workos/organizations_spec.rb
         | 
| 374 404 | 
             
            - spec/lib/workos/passwordless_spec.rb
         | 
| 375 405 | 
             
            - spec/lib/workos/portal_spec.rb
         | 
| 406 | 
            +
            - spec/lib/workos/session_spec.rb
         | 
| 376 407 | 
             
            - spec/lib/workos/sso_spec.rb
         | 
| 377 408 | 
             
            - spec/lib/workos/user_management_spec.rb
         | 
| 378 409 | 
             
            - spec/lib/workos/webhooks_spec.rb
         |