Follow-up to #254. remoteStorage access tokens are currently stored in plaintext in two places:
the remote_storage_authorizations.token DB column, and
the Redis key authorizations:<cn>:<token>.
A leak of either (DB dump, Redis access, backup) hands over usable tokens for users' storage. Store only SHA256(token) instead, keeping the plaintext in memory just long enough to return it to the client at issuance. The token has 128 bits of entropy (SecureRandom.hex(16)), so a plain SHA-256 (no salt/stretching) is sufficient.
Scope
akkounts
Replace the plaintext token column with token_digest; the Redis key becomes authorizations:<cn>:<digest>, and deletion works off the digest.
Backfill token_digest = SHA256(token) for existing rows and re-key Redis (add the digest key, drop the plaintext key). Because the plaintext still exists in the DB today, this needs no re-authorization — that window closes once the plaintext column is dropped, so the backfill must ship with the migration.
Nothing in akkounts looks an authorization up by token, so only issuance, Redis storage and deletion are affected.
Liquor Cabinet
It reads the bearer token from the Authorization header and looks it up with SMEMBERS authorizations:<user>:<token> (lib/remote_storage/rest_provider.rb). It must hash the presented token before the lookup.
For a seamless rollout, look up the plaintext form first and fall back to the digest (or vice versa) during the transition, so existing client tokens keep working while akkounts re-keys Redis.
Notes
Cross-repo change (akkounts + Liquor Cabinet), so two PRs and a coordinated deploy.
Per-device revocation stays immediate (delete the key), unlike self-contained tokens.
Follow-up to #254. remoteStorage access tokens are currently stored in plaintext in two places:
- the `remote_storage_authorizations.token` DB column, and
- the Redis key `authorizations:<cn>:<token>`.
A leak of either (DB dump, Redis access, backup) hands over usable tokens for users' storage. Store only `SHA256(token)` instead, keeping the plaintext in memory just long enough to return it to the client at issuance. The token has 128 bits of entropy (`SecureRandom.hex(16)`), so a plain SHA-256 (no salt/stretching) is sufficient.
## Scope
**akkounts**
- Replace the plaintext `token` column with `token_digest`; the Redis key becomes `authorizations:<cn>:<digest>`, and deletion works off the digest.
- Backfill `token_digest = SHA256(token)` for existing rows and re-key Redis (add the digest key, drop the plaintext key). Because the plaintext still exists in the DB today, this needs no re-authorization — that window closes once the plaintext column is dropped, so the backfill must ship with the migration.
- Nothing in akkounts looks an authorization up by token, so only issuance, Redis storage and deletion are affected.
**Liquor Cabinet**
- It reads the bearer token from the `Authorization` header and looks it up with `SMEMBERS authorizations:<user>:<token>` (`lib/remote_storage/rest_provider.rb`). It must hash the presented token before the lookup.
- For a seamless rollout, look up the plaintext form first and fall back to the digest (or vice versa) during the transition, so existing client tokens keep working while akkounts re-keys Redis.
## Notes
- Cross-repo change (akkounts + Liquor Cabinet), so two PRs and a coordinated deploy.
- Per-device revocation stays immediate (delete the key), unlike self-contained tokens.
Part of #254.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follow-up to #254. remoteStorage access tokens are currently stored in plaintext in two places:
remote_storage_authorizations.tokenDB column, andauthorizations:<cn>:<token>.A leak of either (DB dump, Redis access, backup) hands over usable tokens for users' storage. Store only
SHA256(token)instead, keeping the plaintext in memory just long enough to return it to the client at issuance. The token has 128 bits of entropy (SecureRandom.hex(16)), so a plain SHA-256 (no salt/stretching) is sufficient.Scope
akkounts
tokencolumn withtoken_digest; the Redis key becomesauthorizations:<cn>:<digest>, and deletion works off the digest.token_digest = SHA256(token)for existing rows and re-key Redis (add the digest key, drop the plaintext key). Because the plaintext still exists in the DB today, this needs no re-authorization — that window closes once the plaintext column is dropped, so the backfill must ship with the migration.Liquor Cabinet
Authorizationheader and looks it up withSMEMBERS authorizations:<user>:<token>(lib/remote_storage/rest_provider.rb). It must hash the presented token before the lookup.Notes
Part of #254.