Skip to content

Commit 09bb783

Browse files
committed
Switch to in-memory caches for auth
The main reason for this switch is security. By keeping the data only in-memory we do not have to worry about an external system being compromised. Additionally, since each server will only have some of the data we limit the scope of what may be compromised (though really, if the web server is compromised there are a lot more issues). Another reasons for this switch is we do not control what type of Rails cache may be used here. The default Rails cache is file system. Unfortunately, the file system cache is not thread safe and has some issues with being multi-process safe (regarding incrementing and multiple key management). While Redis is thread/process safe, there's no guarantee that is the cache type used. Due to the type and size of the data being stored in this cache, 5 megabytes should be more than enough. This would allow caching tens of thousands of tokens. Since the main goal of these caches is to help speed up repeated API requests for the same tokens, this size should be a sufficient balance between what it can hold and memory overhead.
1 parent a18d840 commit 09bb783

File tree

4 files changed

+33
-8
lines changed

4 files changed

+33
-8
lines changed

lib/kracken/authenticator.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ module Kracken
22
class Authenticator
33
attr_reader :auth_hash
44

5+
# @private
6+
def self.cache
7+
@cache
8+
end
9+
@cache = ActiveSupport::Cache.lookup_store(
10+
:memory_store,
11+
size: 5.megabytes,
12+
expires_in: 1.hour,
13+
race_condition_ttl: 1.second,
14+
)
15+
516
## Factory Methods
617

718
# Login the user with their credentails. Used for proxying the
@@ -23,7 +34,7 @@ def self.user_with_token(token)
2334
# for the user, set it to nil, fetch from cache and only query if there
2435
# was a cache-hit (thus user is still nil).
2536
user = nil
26-
user_id = Rails.cache.fetch("auth/#{token}/#{auth.etag}") {
37+
user_id = Authenticator.cache.fetch("auth/#{token}/#{auth.etag}") {
2738
user = self.new(auth.body).to_app_user
2839
user.id
2940
}

lib/kracken/controllers/token_authenticatable.rb

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
module Kracken
22
module Controllers
33
module TokenAuthenticatable
4+
# @private
5+
def self.cache
6+
@cache
7+
end
8+
49
def self.included(base)
510
base.define_singleton_method(:realm) do |realm = nil|
611
realm ||= (superclass.try(:realm) || 'Application')
@@ -35,13 +40,13 @@ def request_http_token_authentication(realm = 'Application')
3540

3641
def cache_valid_auth(token, force: false, &generate_cache)
3742
cache_key = TOKEN_AUTH_CACHE_PREFIX + token
38-
val = Rails.cache.read(cache_key) unless force
43+
val = TokenAuthenticatable.cache.read(cache_key) unless force
3944
val ||= store_valid_auth(cache_key, &generate_cache)
4045
shallow_freeze(val)
4146
end
4247

4348
def clear_auth_cache
44-
Rails.cache.delete_matched TOKEN_AUTH_CACHE_PREFIX + "*"
49+
TokenAuthenticatable.cache.delete_matched TOKEN_AUTH_CACHE_PREFIX + "*"
4550
end
4651

4752
def shallow_freeze(val)
@@ -52,7 +57,7 @@ def shallow_freeze(val)
5257

5358
def store_valid_auth(cache_key)
5459
val = yield
55-
Rails.cache.write(cache_key, val, CACHE_TTL_OPTS) if val
60+
TokenAuthenticatable.cache.write(cache_key, val, CACHE_TTL_OPTS) if val
5661
val
5762
end
5863

@@ -63,6 +68,11 @@ def store_valid_auth(cache_key)
6368
race_condition_ttl: 1.second,
6469
}.freeze
6570

71+
@cache = ActiveSupport::Cache.lookup_store(
72+
:memory_store,
73+
CACHE_TTL_OPTS.merge(size: 5.megabytes),
74+
)
75+
6676
# `authenticate_or_request_with_http_token` is a nice Rails helper:
6777
# http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token/ControllerMethods.html#method-i-authenticate_or_request_with_http_token
6878
def authenticate_user_with_token!

spec/kracken/controllers/token_authenticatable_spec.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def authenticate_or_request_with_http_token(realm = nil)
7272
'HTTP_AUTHORIZATION' => "Token token=\"#{cached_token}\""
7373
}
7474

75-
Rails.cache.write cache_key, auth_info
75+
Controllers::TokenAuthenticatable.cache.write cache_key, auth_info
7676
stub_const "Kracken::Authenticator", spy("Kracken::Authenticator")
7777
end
7878

@@ -163,7 +163,7 @@ def authenticate_or_request_with_http_token(realm = nil)
163163
expect {
164164
a_controller.authenticate_user_with_token!
165165
}.not_to change {
166-
Rails.cache.exist?("auth/token/#{invalid_token}")
166+
Controllers::TokenAuthenticatable.cache.exist?("auth/token/#{invalid_token}")
167167
}.from false
168168
end
169169
end
@@ -221,14 +221,16 @@ def authenticate_or_request_with_http_token(realm = nil)
221221
it "sets the auth info as the cache value" do
222222
expect {
223223
a_controller.authenticate_user_with_token!
224-
}.to change { Rails.cache.read("auth/token/any token") }.from(nil).to(
224+
}.to change {
225+
Controllers::TokenAuthenticatable.cache.read("auth/token/any token")
226+
}.from(nil).to(
225227
id: :any_id,
226228
team_ids: [:some, :team, :ids],
227229
)
228230
end
229231

230232
it "sets the cache expiration to one minute by default" do
231-
expect(Rails.cache).to receive(:write).with(
233+
expect(Controllers::TokenAuthenticatable.cache).to receive(:write).with(
232234
"auth/token/any token",
233235
anything,
234236
include(expires_in: 1.minute),

spec/support/using_cache.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@
1010

1111
before do
1212
Rails.cache.clear
13+
Kracken::Controllers::TokenAuthenticatable.cache.clear
14+
Kracken::Authenticator.cache.clear
1315
end
1416
end

0 commit comments

Comments
 (0)