Repository navigation
Conversation
Add an installation store that saves installations in Redis with the client an app already uses, such as node-redis, ioredis, iovalkey or @upstash/redis, without adding a dependency. The store only needs get, set, del and append, and every command uses one key, so it also works with Redis Cluster. Each workspace's installation and each user's token are separate records. An authorization with only user scopes never replaces the bot token, and when InstallProvider#authorize() saves refreshed tokens, the store keeps the user who installed the app and writes only the records that changed, so a request whose refresh failed can't overwrite a token another request just refreshed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: faef24e The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Contributor
|
Hi @malewis5, thanks so much for your submission! I will need to discuss with the team whether the |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2786 +/- ##
==========================================
+ Coverage 89.34% 90.44% +1.10%
==========================================
Files 65 66 +1
Lines 10548 10831 +283
Branches 480 527 +47
==========================================
+ Hits 9424 9796 +372
+ Misses 1092 1004 -88
+ Partials 32 31 -1
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This adds
RedisInstallationStoreto@slack/oauth. It's an installation store for production that saves installations in Redis, using the Redis client the app already has.@slack/oauthgains no dependency.Any client works. The store needs a client with promise-based
get,set,delandappendmethods. Those four commands have the same names in node-redis, ioredis, iovalkey and@upstash/redis, so you can pass any of those clients directly.The store also accepts values that come back already parsed as JSON (what
@upstash/redisdoes by default) or as bytes. Every command uses a single key, so Redis Cluster works too.Keys. For each workspace (or org, for org-wide installs), the store writes:
<keyPrefix>[:<clientId>]:team:<teamId>(or:enterprise:<enterpriseId>)<…>:nobot<…>:user:<userId><…>:usersdeleteInstallation()uses it to delete every user's token.Why user tokens get their own keys.
FileInstallationStoresaves every installation asapp-latest, and token refreshes save the whole installation back. That loses tokens in two cases. I reproduced both onmain:app-latestthen has no bot, soauthorize()returns no bot token.fetchInstallation()clears the installer's token fields for that user.authorize()then saves the installation back overuser-<installer>-latest, which deletes the installer's user token.RedisInstallationStorehandles these cases differently:An authorization with only user scopes writes
:nobotand never replaces the installation that has a bot.fetchInstallation()remembers what it returned, in aWeakMapkeyed by the returneduserandbotobjects.authorize()saves a spread copy that keeps those objects, so the store can recognize it. It keeps the user who installed the app and writes only the records that changed.This also means a request whose refresh failed can't overwrite a token that another request just refreshed.
authorize()drops failed refreshes but still saves the whole installation back (install-provider.tsrefreshExpiringTokens), which is the path this covers.Lookups match
FileInstallationStore:userId, you get that user's token, or no user token when they never authorized.Errors and logs.
FileInstallationStore.causeset.causeand redact anything shaped like a token.@upstash/redisputs the command it sent, including the tokens, in its error messages, and ioredis attaches the command's arguments to its errors.Docs and release. The docs add a "Using
RedisInstallationStore" section todocs/english/oauth.md, and there's a minor changeset for@slack/oauth.Testing
redis-store.test.tshas 58 tests. The main suite runs once with each of three fake clients: one that stores strings, one that parses JSON like Upstash, and one that returns Buffers.InstallProvider#authorize()with a fakefetch.@upstash/redis1.39, with automatic deserialization on and off. Every key was cleaned up afterwards.npm test, lint and the build pass.Limitations
:userskeeps the IDs of users whose tokens were deleted until the workspace is deleted.Unrelated thing I noticed: the oauth
testscript runsbash -c '… src/**/*.test.ts'withoutglobstar. As a result it only runs the test files one folder deep.src/install-provider.test.tsand the other top-levelsrc/*.test.tsfiles don't run undernpm test. Happy to send that fix separately.Requirements
🤖 Generated with Claude Code