workos 5.11.0 → 5.11.1
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 +1 -1
- data/lib/workos/cache.rb +94 -0
- data/lib/workos/session.rb +3 -1
- data/lib/workos/version.rb +1 -1
- data/lib/workos.rb +1 -0
- data/spec/lib/workos/cache_spec.rb +94 -0
- data/spec/lib/workos/session_spec.rb +46 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2792dbbd0e3dac4d2a9eac0920a135b0db63c723a85fbbec049327ab2cffd547
|
4
|
+
data.tar.gz: 356cb856f6e2df599daf2affc94eb74d4ee885ab71d77e6002ab176718dbc0ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4a1406d88ac981d0d0653027cf2afe17b64d7c577d6cfe0d4ccc9ffb0a6c6be29c0f3ed140a03bb8ce5a8876990e41bea60a892f5675dc6578ce1526a4504f6
|
7
|
+
data.tar.gz: 55abb8c32270ce7b4b5e4e7142f9f6c390e9cca88c3f3eb1242840193f4155da9bf4f018d85e5b34f51ebd3cc85f0cac176c8cd00abffd077133962100083566
|
data/Gemfile.lock
CHANGED
data/lib/workos/cache.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WorkOS
|
4
|
+
# The Cache module provides a simple in-memory cache for storing values
|
5
|
+
# This module is not meant to be instantiated in a user space, and is used internally by the SDK
|
6
|
+
module Cache
|
7
|
+
# The Entry class represents a cache entry with a value and an expiration time
|
8
|
+
class Entry
|
9
|
+
attr_reader :value, :expires_at
|
10
|
+
|
11
|
+
# Initializes a new cache entry
|
12
|
+
# @param value [Object] The value to store in the cache
|
13
|
+
# @param expires_in_seconds [Integer, nil] The expiration time for the value in seconds, or nil for no expiration
|
14
|
+
def initialize(value, expires_in_seconds)
|
15
|
+
@value = value
|
16
|
+
@expires_at = expires_in_seconds ? Time.now + expires_in_seconds : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Checks if the entry has expired
|
20
|
+
# @return [Boolean] True if the entry has expired, false otherwise
|
21
|
+
def expired?
|
22
|
+
return false if expires_at.nil?
|
23
|
+
|
24
|
+
Time.now > @expires_at
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# Fetches a value from the cache, or calls the block to fetch the value if it is not present
|
30
|
+
# @param key [String] The key to fetch the value for
|
31
|
+
# @param expires_in [Integer] The expiration time for the value in seconds
|
32
|
+
# @param force [Boolean] If true, the value will be fetched from the block even if it is present in the cache
|
33
|
+
# @param block [Proc] The block to call to fetch the value if it is not present in the cache
|
34
|
+
# @return [Object] The value fetched from the cache or the block
|
35
|
+
def fetch(key, expires_in: nil, force: false, &block)
|
36
|
+
entry = store[key]
|
37
|
+
|
38
|
+
if force || entry.nil? || entry.expired?
|
39
|
+
value = block.call
|
40
|
+
store[key] = Entry.new(value, expires_in)
|
41
|
+
return value
|
42
|
+
end
|
43
|
+
|
44
|
+
entry.value
|
45
|
+
end
|
46
|
+
|
47
|
+
# Reads a value from the cache
|
48
|
+
# @param key [String] The key to read the value for
|
49
|
+
# @return [Object] The value read from the cache, or nil if the value is not present or has expired
|
50
|
+
def read(key)
|
51
|
+
entry = store[key]
|
52
|
+
return nil if entry.nil? || entry.expired?
|
53
|
+
|
54
|
+
entry.value
|
55
|
+
end
|
56
|
+
|
57
|
+
# Writes a value to the cache
|
58
|
+
# @param key [String] The key to write the value for
|
59
|
+
# @param value [Object] The value to write to the cache
|
60
|
+
# @param expires_in [Integer] The expiration time for the value in seconds
|
61
|
+
# @return [Object] The value written to the cache
|
62
|
+
def write(key, value, expires_in: nil)
|
63
|
+
store[key] = Entry.new(value, expires_in)
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
# Deletes a value from the cache
|
68
|
+
# @param key [String] The key to delete the value for
|
69
|
+
def delete(key)
|
70
|
+
store.delete(key)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Clears all values from the cache
|
74
|
+
def clear
|
75
|
+
store.clear
|
76
|
+
end
|
77
|
+
|
78
|
+
# Checks if a value exists in the cache
|
79
|
+
# @param key [String] The key to check for
|
80
|
+
# @return [Boolean] True if the value exists and has not expired, false otherwise
|
81
|
+
def exist?(key)
|
82
|
+
entry = store[key]
|
83
|
+
!(entry.nil? || entry.expired?)
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# The in-memory store for the cache
|
89
|
+
def store
|
90
|
+
@store ||= {}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/workos/session.rb
CHANGED
@@ -23,7 +23,9 @@ module WorkOS
|
|
23
23
|
@session_data = session_data
|
24
24
|
@client_id = client_id
|
25
25
|
|
26
|
-
@jwks =
|
26
|
+
@jwks = Cache.fetch("jwks_#{client_id}", expires_in: 5 * 60) do
|
27
|
+
create_remote_jwk_set(URI(@user_management.get_jwks_url(client_id)))
|
28
|
+
end
|
27
29
|
@jwks_algorithms = @jwks.map { |key| key[:alg] }.compact.uniq
|
28
30
|
end
|
29
31
|
|
data/lib/workos/version.rb
CHANGED
data/lib/workos.rb
CHANGED
@@ -45,6 +45,7 @@ module WorkOS
|
|
45
45
|
autoload :AuthenticationFactorAndChallenge, 'workos/authentication_factor_and_challenge'
|
46
46
|
autoload :AuthenticationResponse, 'workos/authentication_response'
|
47
47
|
autoload :AuditLogs, 'workos/audit_logs'
|
48
|
+
autoload :Cache, 'workos/cache'
|
48
49
|
autoload :Challenge, 'workos/challenge'
|
49
50
|
autoload :Client, 'workos/client'
|
50
51
|
autoload :Connection, 'workos/connection'
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe WorkOS::Cache do
|
4
|
+
before { described_class.clear }
|
5
|
+
|
6
|
+
describe '.write and .read' do
|
7
|
+
it 'stores and retrieves data' do
|
8
|
+
described_class.write('key', 'value')
|
9
|
+
expect(described_class.read('key')).to eq('value')
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns nil if key does not exist' do
|
13
|
+
expect(described_class.read('missing')).to be_nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '.fetch' do
|
18
|
+
it 'returns cached value when present and not expired' do
|
19
|
+
described_class.write('key', 'value')
|
20
|
+
fetch_value = described_class.fetch('key') { 'new_value' }
|
21
|
+
expect(fetch_value).to eq('value')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'executes block and caches value when not present' do
|
25
|
+
fetch_value = described_class.fetch('key') { 'new_value' }
|
26
|
+
expect(fetch_value).to eq('new_value')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'executes block and caches value when force is true' do
|
30
|
+
described_class.write('key', 'value')
|
31
|
+
fetch_value = described_class.fetch('key', force: true) { 'new_value' }
|
32
|
+
expect(fetch_value).to eq('new_value')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'expiration' do
|
37
|
+
it 'expires values after specified time' do
|
38
|
+
described_class.write('key', 'value', expires_in: 0.1)
|
39
|
+
expect(described_class.read('key')).to eq('value')
|
40
|
+
sleep 0.2
|
41
|
+
expect(described_class.read('key')).to be_nil
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'executes block and caches new value when expired' do
|
45
|
+
described_class.write('key', 'old_value', expires_in: 0.1)
|
46
|
+
sleep 0.2
|
47
|
+
fetch_value = described_class.fetch('key') { 'new_value' }
|
48
|
+
expect(fetch_value).to eq('new_value')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'does not expire values when expires_in is nil' do
|
52
|
+
described_class.write('key', 'value', expires_in: nil)
|
53
|
+
sleep 0.2
|
54
|
+
expect(described_class.read('key')).to eq('value')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '.exist?' do
|
59
|
+
it 'returns true if key exists' do
|
60
|
+
described_class.write('key', 'value')
|
61
|
+
expect(described_class.exist?('key')).to be true
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns false if expired' do
|
65
|
+
described_class.write('key', 'value', expires_in: 0.1)
|
66
|
+
sleep 0.2
|
67
|
+
expect(described_class.exist?('key')).to be false
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'returns false if key does not exist' do
|
71
|
+
expect(described_class.exist?('missing')).to be false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '.delete' do
|
76
|
+
it 'deletes key' do
|
77
|
+
described_class.write('key', 'value')
|
78
|
+
described_class.delete('key')
|
79
|
+
expect(described_class.read('key')).to be_nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '.clear' do
|
84
|
+
it 'removes all keys from the cache' do
|
85
|
+
described_class.write('key1', 'value1')
|
86
|
+
described_class.write('key2', 'value2')
|
87
|
+
|
88
|
+
described_class.clear
|
89
|
+
|
90
|
+
expect(described_class.read('key1')).to be_nil
|
91
|
+
expect(described_class.read('key2')).to be_nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -19,6 +19,52 @@ describe WorkOS::Session do
|
|
19
19
|
allow(user_management).to receive(:get_jwks_url).with(client_id).and_return(jwks_url)
|
20
20
|
end
|
21
21
|
|
22
|
+
describe 'JWKS caching' do
|
23
|
+
before do
|
24
|
+
WorkOS::Cache.clear
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'caches and returns JWKS' do
|
28
|
+
expect(Net::HTTP).to receive(:get).once
|
29
|
+
session1 = WorkOS::Session.new(
|
30
|
+
user_management: user_management,
|
31
|
+
client_id: client_id,
|
32
|
+
session_data: session_data,
|
33
|
+
cookie_password: cookie_password,
|
34
|
+
)
|
35
|
+
|
36
|
+
session2 = WorkOS::Session.new(
|
37
|
+
user_management: user_management,
|
38
|
+
client_id: client_id,
|
39
|
+
session_data: session_data,
|
40
|
+
cookie_password: cookie_password,
|
41
|
+
)
|
42
|
+
|
43
|
+
expect(session1.jwks.map(&:export)).to eq(session2.jwks.map(&:export))
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'fetches JWKS from remote when cache is expired' do
|
47
|
+
expect(Net::HTTP).to receive(:get).twice
|
48
|
+
session1 = WorkOS::Session.new(
|
49
|
+
user_management: user_management,
|
50
|
+
client_id: client_id,
|
51
|
+
session_data: session_data,
|
52
|
+
cookie_password: cookie_password,
|
53
|
+
)
|
54
|
+
|
55
|
+
allow(Time).to receive(:now).and_return(Time.now + 301)
|
56
|
+
|
57
|
+
session2 = WorkOS::Session.new(
|
58
|
+
user_management: user_management,
|
59
|
+
client_id: client_id,
|
60
|
+
session_data: session_data,
|
61
|
+
cookie_password: cookie_password,
|
62
|
+
)
|
63
|
+
|
64
|
+
expect(session1.jwks.map(&:export)).to eq(session2.jwks.map(&:export))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
22
68
|
it 'raises an error if cookie_password is nil or empty' do
|
23
69
|
expect do
|
24
70
|
WorkOS::Session.new(
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: workos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.11.
|
4
|
+
version: 5.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- WorkOS
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: encryptor
|
@@ -136,6 +136,7 @@ files:
|
|
136
136
|
- lib/workos/audit_logs.rb
|
137
137
|
- lib/workos/authentication_factor_and_challenge.rb
|
138
138
|
- lib/workos/authentication_response.rb
|
139
|
+
- lib/workos/cache.rb
|
139
140
|
- lib/workos/challenge.rb
|
140
141
|
- lib/workos/client.rb
|
141
142
|
- lib/workos/configuration.rb
|
@@ -184,6 +185,7 @@ files:
|
|
184
185
|
- lib/workos/webhooks.rb
|
185
186
|
- lib/workos/widgets.rb
|
186
187
|
- spec/lib/workos/audit_logs_spec.rb
|
188
|
+
- spec/lib/workos/cache_spec.rb
|
187
189
|
- spec/lib/workos/client.rb
|
188
190
|
- spec/lib/workos/configuration_spec.rb
|
189
191
|
- spec/lib/workos/directory_sync_spec.rb
|
@@ -403,6 +405,7 @@ specification_version: 4
|
|
403
405
|
summary: API client for WorkOS
|
404
406
|
test_files:
|
405
407
|
- spec/lib/workos/audit_logs_spec.rb
|
408
|
+
- spec/lib/workos/cache_spec.rb
|
406
409
|
- spec/lib/workos/client.rb
|
407
410
|
- spec/lib/workos/configuration_spec.rb
|
408
411
|
- spec/lib/workos/directory_sync_spec.rb
|