11import inspect
2+ import os
23from logging import Logger
34from typing import Optional , Callable , Dict , Any
45
56from slack_sdk .errors import SlackApiError
67from slack_sdk .oauth import InstallationStore
78from slack_sdk .oauth .installation_store import Bot
89from slack_sdk .oauth .installation_store .models .installation import Installation
10+ from slack_sdk .oauth .token_rotation .rotator import TokenRotator
911
1012from slack_bolt .authorization .authorize_args import AuthorizeArgs
1113from slack_bolt .authorization .authorize_result import AuthorizeResult
1214from slack_bolt .context .context import BoltContext
15+ from slack_bolt .error import BoltError
1316
1417
1518class Authorize :
@@ -97,12 +100,20 @@ class InstallationStoreAuthorize(Authorize):
97100 bot_only : bool
98101 find_installation_available : bool
99102 find_bot_available : bool
103+ token_rotator : Optional [TokenRotator ]
104+
105+ _config_error_message : str = (
106+ "InstallationStore with client_id/client_secret are required for token rotation"
107+ )
100108
101109 def __init__ (
102110 self ,
103111 * ,
104112 logger : Logger ,
105113 installation_store : InstallationStore ,
114+ client_id : Optional [str ] = None ,
115+ client_secret : Optional [str ] = None ,
116+ token_rotation_expiration_minutes : Optional [int ] = None ,
106117 # For v1.0.x compatibility and people who still want its simplicity
107118 # use only InstallationStore#find_bot(enterprise_id, team_id)
108119 bot_only : bool = False ,
@@ -117,6 +128,16 @@ def __init__(
117128 installation_store , "find_installation"
118129 )
119130 self .find_bot_available = hasattr (installation_store , "find_bot" )
131+ if client_id is not None and client_secret is not None :
132+ self .token_rotator = TokenRotator (
133+ client_id = client_id ,
134+ client_secret = client_secret ,
135+ )
136+ else :
137+ self .token_rotator = None
138+ self .token_rotation_expiration_minutes = (
139+ token_rotation_expiration_minutes or 120
140+ )
120141
121142 def __call__ (
122143 self ,
@@ -165,6 +186,19 @@ def __call__(
165186 installation .bot_token ,
166187 installation .user_token ,
167188 )
189+ if installation .user_refresh_token is not None :
190+ if self .token_rotator is None :
191+ raise BoltError (self ._config_error_message )
192+ refreshed = self .token_rotator .perform_token_rotation (
193+ installation = installation ,
194+ minutes_before_expiration = self .token_rotation_expiration_minutes ,
195+ )
196+ if refreshed is not None :
197+ self .installation_store .save (refreshed )
198+ bot_token , user_token = (
199+ refreshed .bot_token ,
200+ refreshed .user_token ,
201+ )
168202
169203 except NotImplementedError as _ :
170204 self .find_installation_available = False
@@ -194,6 +228,18 @@ def __call__(
194228 except Exception as e :
195229 self .logger .info (f"Failed to call find_bot method: { e } " )
196230
231+ if bot .bot_refresh_token is not None :
232+ # Token rotation
233+ if self .token_rotator is None :
234+ raise BoltError (self ._config_error_message )
235+ refreshed = self .token_rotator .perform_bot_token_rotation (
236+ bot = bot ,
237+ minutes_before_expiration = self .token_rotation_expiration_minutes ,
238+ )
239+ if refreshed is not None :
240+ self .installation_store .save_bot (refreshed )
241+ bot_token = refreshed .bot_token
242+
197243 token : Optional [str ] = bot_token or user_token
198244 if token is None :
199245 # No valid token was found
0 commit comments