slackapi / slackapi/node-slack-sdk
Race condition when running multiple functions simultaneously produces invalid installation
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 3.4k
- Forks
- 688
- Avg merge
- 15h 31m
- Merged PRs (30d)
- 27
Description
Token rotation must be enabled.
If the refresh token is about to expire (the SDK does this when there are less than 2 hours left on the expiry), and we run multiple functions simultaneously - in a Promise.all for instance - it seems the same refresh token is exchanged for 2 different fresh refresh tokens that have the same expires_at. At this point the SDK saves both tokens (via storeInstallation), but one is invalid and it is impossible to determine which one without trying it.
We've been having this issue since we started using the SDK.
I've tried to use the various callbacks to implement a user-land lock, but I couldn't quite get it to work because there's not enough control over what's being passed around.
Packages:
Select all that apply:
-
@slack/web-api -
@slack/rtm-api -
@slack/webhooks -
@slack/oauth -
@slack/socket-mode -
@slack/types - I don't know
Reproducible in:
The Slack SDK version
I've tried the same with older versions and latest
"slack/bolt": "^3.22.0",
"slack/oauth": "^2.6.3",
"slack/web-api": "^6.13.0",
"slack/webhook": "^7.0.3",
"slack/bolt": "^4.6.0",
"slack/oauth": "^3.0.4",
"slack/web-api": "^7.13.0",
"slack/webhook": "^7.0.6",
Node.js runtime version
v22.22.0
OS info
Docker - node:22.22.0-alpine3.22
Steps to reproduce:
- Token rotation must be enabled
- Refresh token about to expire
- Multiple calls to a Slack function (
chat.postMessagefor instance) in aPromise.all - Internally,
authorize()is called for all calls fetchInstallation()is called for all callsrefreshExpiringToken()is called for all calls - 2 different refresh tokens are createdstoreInstallation()is called for all calls
Expected result:
A single call to storeInstallation with a installation OR multiple calls with different expires_ats
Actual result:
Multiple calls to storeInstallation, with some installations immediately invalid
Requirements
For general questions/issues about Slack API platform or its server-side, could you submit questions at https://my.slack.com/help/requests/new instead. 🙇
Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you are agreeing to those rules.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing authorize(), fetchInstallation(), refreshExpiringToken(), and storeInstallation() through the @slack/oauth package while reproducing concurrent calls with Promise.all. Done means concurrent refreshes no longer persist an immediately invalid installation, matching the stated single-store or differing-expires_at expectation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, typescript
- Domain
- authentication, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100