Support hashed remoteStorage access tokens #14

Merged
raucao merged 1 commits from feature/hashed_rs_access_tokens into master 2026-11-04 05:58:46 +00:00
4 changed files with 68 additions and 2 deletions

No files matched your search

+10
View File
@@ -50,6 +50,16 @@ Running the app:
_TODO document options_
### Hashed access tokens
By default Liquor Cabinet looks up remoteStorage authorizations by the plaintext
bearer token, using the Redis key `authorizations:<user>:<token>`.
Set `RS_HASHED_ACCESS_TOKENS=true` to hash the presented token and look up its
SHA256 digest instead, using `authorizations:<user>:<sha256(token)>`. If no
authorization is stored under the digest, the plaintext key is checked as a
fallback.
## Contributing
We love pull requests. If you want to submit a patch:
+1
View File
@@ -1,5 +1,6 @@
development: &defaults
maintenance: false
hashed_access_tokens: <%= ENV["RS_HASHED_ACCESS_TOKENS"] == "true" %>
redis:
host: <%= ENV["REDIS_HOST"] || "localhost" %>
port: <%= ENV["REDIS_PORT"] || 6379 %>
+11 -2
View File
@@ -5,7 +5,7 @@ require "active_support/core_ext/time/conversions"
require "active_support/core_ext/numeric/time"
require "active_support/core_ext/hash"
require "redis"
require "digest/md5"
require "digest"
module RemoteStorage
module RestProvider
@@ -26,7 +26,7 @@ module RemoteStorage
server.halt 401, "Unauthorized" if token.nil? || token.empty?
authorizations = redis.smembers("authorizations:#{user}:#{token}")
authorizations = authorizations_for(user, token)
permission = directory_permission(authorizations, directory)
server.halt 401, "Unauthorized" unless permission
@@ -233,6 +233,15 @@ module RemoteStorage
end
end
def authorizations_for(user, token)
if settings.respond_to?(:hashed_access_tokens) && settings.hashed_access_tokens
digest = Digest::SHA256.hexdigest(token)
permissions = redis.smembers("authorizations:#{user}:#{digest}")
return permissions unless permissions.empty?
end
redis.smembers("authorizations:#{user}:#{token}")
end
def directory_permission(authorizations, directory)
authorizations = authorizations.map do |auth|
auth.index(":") ? auth.split(":") : [auth, "rw"]
+46
View File
@@ -704,4 +704,50 @@ JFIFddDuckyA␍⎺␉␊␍
end
end
end
describe "authorization with hashed access tokens" do
before do
redis.del "authorizations:phil:#{Digest::SHA256.hexdigest("amarillo")}"
redis.del "authorizations:phil:amarillo"
purge_redis
header "Authorization", "Bearer amarillo"
end
after do
app.set :hashed_access_tokens, false
end
context "when hashed access tokens are enabled" do
before do
app.set :hashed_access_tokens, true
end
it "authorizes using the digest key" do
redis.sadd "authorizations:phil:#{Digest::SHA256.hexdigest("amarillo")}", [":rw"]
put "/phil/food/aguacate", "si"
_(last_response.status).must_equal 201
end
it "falls back to the plaintext key during the rollout" do
redis.sadd "authorizations:phil:amarillo", [":rw"]
put "/phil/food/aguacate", "si"
_(last_response.status).must_equal 201
end
end
context "when hashed access tokens are disabled" do
it "does not authorize a digest-only key" do
redis.sadd "authorizations:phil:#{Digest::SHA256.hexdigest("amarillo")}", [":rw"]
put "/phil/food/aguacate", "si"
_(last_response.status).must_equal 401
_(last_response.body).must_equal "Unauthorized"
end
end
end
end