Skip to content

Commit 8334d17

Browse files
authored
Improve token rotation error handling and installation error text (#861)
1 parent 84bb73d commit 8334d17

File tree

12 files changed

+54
-10
lines changed

12 files changed

+54
-10
lines changed

slack_bolt/authorization/async_authorize.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from logging import Logger
22
from typing import Optional, Callable, Awaitable, Dict, Any, List
33

4-
from slack_sdk.errors import SlackApiError
4+
from slack_sdk.errors import SlackApiError, SlackTokenRotationError
55
from slack_sdk.oauth.installation_store import Bot, Installation
66
from slack_sdk.oauth.installation_store.async_installation_store import (
77
AsyncInstallationStore,
@@ -274,6 +274,11 @@ async def __call__(
274274
user_token = refreshed.user_token
275275
user_scopes = refreshed.user_scopes
276276

277+
except SlackTokenRotationError as rotation_error:
278+
# When token rotation fails, it is usually unrecoverable
279+
# So, this built-in middleware gives up continuing with the following middleware and listeners
280+
self.logger.error(f"Failed to rotate tokens due to {rotation_error}")
281+
return None
277282
except NotImplementedError as _:
278283
self.find_installation_available = False
279284

@@ -307,6 +312,11 @@ async def __call__(
307312
bot_token = refreshed.bot_token
308313
bot_scopes = refreshed.bot_scopes
309314

315+
except SlackTokenRotationError as rotation_error:
316+
# When token rotation fails, it is usually unrecoverable
317+
# So, this built-in middleware gives up continuing with the following middleware and listeners
318+
self.logger.error(f"Failed to rotate tokens due to {rotation_error}")
319+
return None
310320
except NotImplementedError as _:
311321
self.find_bot_available = False
312322
except Exception as e:

slack_bolt/authorization/authorize.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from logging import Logger
22
from typing import Optional, Callable, Dict, Any, List
33

4-
from slack_sdk.errors import SlackApiError
4+
from slack_sdk.errors import SlackApiError, SlackTokenRotationError
55
from slack_sdk.oauth import InstallationStore
66
from slack_sdk.oauth.installation_store.models.bot import Bot
77
from slack_sdk.oauth.installation_store.models.installation import Installation
@@ -271,6 +271,11 @@ def __call__(
271271
user_token = refreshed.user_token
272272
user_scopes = refreshed.user_scopes
273273

274+
except SlackTokenRotationError as rotation_error:
275+
# When token rotation fails, it is usually unrecoverable
276+
# So, this built-in middleware gives up continuing with the following middleware and listeners
277+
self.logger.error(f"Failed to rotate tokens due to {rotation_error}")
278+
return None
274279
except NotImplementedError as _:
275280
self.find_installation_available = False
276281

@@ -304,6 +309,11 @@ def __call__(
304309
bot_token = refreshed.bot_token
305310
bot_scopes = refreshed.bot_scopes
306311

312+
except SlackTokenRotationError as rotation_error:
313+
# When token rotation fails, it is usually unrecoverable
314+
# So, this built-in middleware gives up continuing with the following middleware and listeners
315+
self.logger.error(f"Failed to rotate tokens due to {rotation_error}")
316+
return None
307317
except NotImplementedError as _:
308318
self.find_bot_available = False
309319
except Exception as e:

slack_bolt/middleware/authorization/async_internals.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from slack_bolt.middleware.authorization.internals import _build_error_text
12
from slack_bolt.request.async_request import AsyncBoltRequest
23
from slack_bolt.response import BoltResponse
34

@@ -18,5 +19,5 @@ def _build_error_response() -> BoltResponse:
1819
# show an ephemeral message to the end-user
1920
return BoltResponse(
2021
status=200,
21-
body=":x: Please install this app into the workspace :bow:",
22+
body=_build_error_text(),
2223
)

slack_bolt/middleware/authorization/async_multi_teams_authorization.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from slack_bolt.response import BoltResponse
88
from .async_authorization import AsyncAuthorization
99
from .async_internals import _build_error_response, _is_no_auth_required
10-
from .internals import _is_no_auth_test_call_required
10+
from .internals import _is_no_auth_test_call_required, _build_error_text
1111
from ...authorization import AuthorizeResult
1212
from ...authorization.async_authorize import AsyncAuthorize
1313

@@ -91,6 +91,9 @@ async def async_process(
9191
"Although the app should be installed into this workspace, "
9292
"the AuthorizeResult (returned value from authorize) for it was not found."
9393
)
94+
if req.context.response_url is not None:
95+
await req.context.respond(_build_error_text())
96+
return BoltResponse(status=200, body="")
9497
return _build_error_response()
9598

9699
except SlackApiError as e:

slack_bolt/middleware/authorization/async_single_team_authorization.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from slack_sdk.web.async_slack_response import AsyncSlackResponse
99
from slack_sdk.errors import SlackApiError
1010
from .async_internals import _build_error_response, _is_no_auth_required
11-
from .internals import _to_authorize_result, _is_no_auth_test_call_required
11+
from .internals import _to_authorize_result, _is_no_auth_test_call_required, _build_error_text
1212
from ...authorization import AuthorizeResult
1313

1414

@@ -57,6 +57,9 @@ async def async_process(
5757
else:
5858
# Just in case
5959
self.logger.error("auth.test API call result is unexpectedly None")
60+
if req.context.response_url is not None:
61+
await req.context.respond(_build_error_text())
62+
return BoltResponse(status=200, body="")
6063
return _build_error_response()
6164
except SlackApiError as e:
6265
self.logger.error(f"Failed to authorize with the given token ({e})")

slack_bolt/middleware/authorization/internals.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,18 @@ def _is_no_auth_test_call_required(req: Union[BoltRequest, "AsyncBoltRequest"])
4343
return _is_no_auth_test_events(req)
4444

4545

46+
def _build_error_text() -> str:
47+
return (
48+
":warning: We apologize, but for some unknown reason, your installation with this app is no longer available. "
49+
"Please reinstall this app into your workspace :bow:"
50+
)
51+
52+
4653
def _build_error_response() -> BoltResponse:
4754
# show an ephemeral message to the end-user
4855
return BoltResponse(
4956
status=200,
50-
body=":x: Please install this app into the workspace :bow:",
57+
body=_build_error_text(),
5158
)
5259

5360

slack_bolt/middleware/authorization/multi_teams_authorization.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
_build_error_response,
1212
_is_no_auth_required,
1313
_is_no_auth_test_call_required,
14+
_build_error_text,
1415
)
1516
from ...authorization import AuthorizeResult
1617
from ...authorization.authorize import Authorize
@@ -93,6 +94,9 @@ def process(
9394
"Although the app should be installed into this workspace, "
9495
"the AuthorizeResult (returned value from authorize) for it was not found."
9596
)
97+
if req.context.response_url is not None:
98+
req.context.respond(_build_error_text())
99+
return BoltResponse(status=200, body="")
96100
return _build_error_response()
97101

98102
except SlackApiError as e:

slack_bolt/middleware/authorization/single_team_authorization.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
_is_no_auth_required,
1313
_to_authorize_result,
1414
_is_no_auth_test_call_required,
15+
_build_error_text,
1516
)
1617
from ...authorization import AuthorizeResult
1718

@@ -71,6 +72,9 @@ def process(
7172
else:
7273
# Just in case
7374
self.logger.error("auth.test API call result is unexpectedly None")
75+
if req.context.response_url is not None:
76+
req.context.respond(_build_error_text())
77+
return BoltResponse(status=200, body="")
7478
return _build_error_response()
7579
except SlackApiError as e:
7680
self.logger.error(f"Failed to authorize with the given token ({e})")

tests/scenario_tests/test_authorize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def test_failure(self):
107107
request = self.build_valid_request()
108108
response = app.dispatch(request)
109109
assert response.status == 200
110-
assert response.body == ":x: Please install this app into the workspace :bow:"
110+
assert response.body == ""
111111
assert self.mock_received_requests.get("/auth.test") == None
112112

113113
def test_bot_context_attributes(self):

tests/scenario_tests_async/test_authorize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ async def test_failure(self):
115115
request = self.build_valid_request()
116116
response = await app.async_dispatch(request)
117117
assert response.status == 200
118-
assert response.body == ":x: Please install this app into the workspace :bow:"
118+
assert response.body == ""
119119
assert self.mock_received_requests.get("/auth.test") == None
120120

121121
@pytest.mark.asyncio

0 commit comments

Comments
 (0)