@@ -12,11 +12,14 @@ module WorkOS
1212 # The Session class provides helper methods for working with WorkOS sessions
1313 # This class is not meant to be instantiated in a user space, and is instantiated internally but exposed.
1414 class Session
15- attr_accessor :jwks , :jwks_algorithms , :user_management , :cookie_password , :session_data , :client_id
15+ attr_accessor :jwks , :jwks_algorithms , :user_management , :cookie_password , :session_data , :client_id , :encryptor
1616
17- def initialize ( user_management :, client_id :, session_data :, cookie_password :)
17+ def initialize ( user_management :, client_id :, session_data :, cookie_password :, encryptor : nil )
1818 raise ArgumentError , 'cookiePassword is required' if cookie_password . nil? || cookie_password . empty?
1919
20+ @encryptor = encryptor || WorkOS ::Encryptors ::AesGcm . new
21+ validate_encryptor! ( @encryptor )
22+
2023 @user_management = user_management
2124 @cookie_password = cookie_password
2225 @session_data = session_data
@@ -37,7 +40,7 @@ def authenticate(include_expired: false, &claim_extractor)
3740 return { authenticated : false , reason : 'NO_SESSION_COOKIE_PROVIDED' } if @session_data . nil?
3841
3942 begin
40- session = Session . unseal_data ( @session_data , @cookie_password )
43+ session = Session . unseal_data ( @session_data , @cookie_password , encryptor : @encryptor )
4144 rescue StandardError
4245 return { authenticated : false , reason : 'INVALID_SESSION_COOKIE' }
4346 end
@@ -92,7 +95,7 @@ def refresh(options = nil)
9295 cookie_password = options . nil? || options [ :cookie_password ] . nil? ? @cookie_password : options [ :cookie_password ]
9396
9497 begin
95- session = Session . unseal_data ( @session_data , cookie_password )
98+ session = Session . unseal_data ( @session_data , cookie_password , encryptor : @encryptor )
9699 rescue StandardError
97100 return { authenticated : false , reason : 'INVALID_SESSION_COOKIE' }
98101 end
@@ -104,7 +107,7 @@ def refresh(options = nil)
104107 client_id : @client_id ,
105108 refresh_token : session [ :refresh_token ] ,
106109 organization_id : options . nil? || options [ :organization_id ] . nil? ? nil : options [ :organization_id ] ,
107- session : { seal_session : true , cookie_password : cookie_password } ,
110+ session : { seal_session : true , cookie_password : cookie_password , encryptor : @encryptor } ,
108111 )
109112
110113 @session_data = auth_response . sealed_session
@@ -137,43 +140,34 @@ def get_logout_url(return_to: nil)
137140 @user_management . get_logout_url ( session_id : auth_response [ :session_id ] , return_to : return_to )
138141 end
139142
140- # Encrypts and seals data using AES-256-GCM
143+ # Encrypts and seals data using the provided encryptor (defaults to AES-256-GCM)
141144 # @param data [Hash] The data to seal
142145 # @param key [String] The key to use for encryption
146+ # @param encryptor [Object] Optional encryptor that responds to #seal(data, key)
143147 # @return [String] The sealed data
144- def self . seal_data ( data , key )
145- iv = SecureRandom . random_bytes ( 12 )
146-
147- encrypted_data = Encryptor . encrypt (
148- value : JSON . generate ( data ) ,
149- key : key ,
150- iv : iv ,
151- algorithm : 'aes-256-gcm' ,
152- )
153- Base64 . encode64 ( iv + encrypted_data ) # Combine IV with encrypted data and encode as base64
148+ def self . seal_data ( data , key , encryptor : nil )
149+ enc = encryptor || WorkOS ::Encryptors ::AesGcm . new
150+ enc . seal ( data , key )
154151 end
155152
156- # Decrypts and unseals data using AES-256-GCM
153+ # Decrypts and unseals data using the provided encryptor (defaults to AES-256-GCM)
157154 # @param sealed_data [String] The sealed data to unseal
158155 # @param key [String] The key to use for decryption
156+ # @param encryptor [Object] Optional encryptor that responds to #unseal(sealed_data, key)
159157 # @return [Hash] The unsealed data
160- def self . unseal_data ( sealed_data , key )
161- decoded_data = Base64 . decode64 ( sealed_data )
162- iv = decoded_data [ 0 ..11 ] # Extract the IV (first 12 bytes)
163- encrypted_data = decoded_data [ 12 ..-1 ] # Extract the encrypted data
164-
165- decrypted_data = Encryptor . decrypt (
166- value : encrypted_data ,
167- key : key ,
168- iv : iv ,
169- algorithm : 'aes-256-gcm' ,
170- )
171-
172- JSON . parse ( decrypted_data , symbolize_names : true ) # Parse the decrypted JSON string back to original data
158+ def self . unseal_data ( sealed_data , key , encryptor : nil )
159+ enc = encryptor || WorkOS ::Encryptors ::AesGcm . new
160+ enc . unseal ( sealed_data , key )
173161 end
174162
175163 private
176164
165+ def validate_encryptor! ( enc )
166+ return if enc . respond_to? ( :seal ) && enc . respond_to? ( :unseal )
167+
168+ raise ArgumentError , 'encryptor must respond to #seal(data, key) and #unseal(sealed_data, key)'
169+ end
170+
177171 # Creates a JWKS set from a remote JWKS URL
178172 # @param uri [URI] The URI of the JWKS
179173 # @return [JWT::JWK::Set] The JWKS set
0 commit comments