1111import com .slack .api .bolt .response .Responder ;
1212import com .slack .api .bolt .response .Response ;
1313import com .slack .api .bolt .service .InstallationService ;
14+ import com .slack .api .methods .MethodsClient ;
1415import com .slack .api .methods .SlackApiException ;
1516import com .slack .api .methods .response .auth .AuthTestResponse ;
1617import com .slack .api .model .block .LayoutBlock ;
18+ import com .slack .api .util .thread .ExecutorServiceFactory ;
19+ import lombok .AllArgsConstructor ;
20+ import lombok .Data ;
1721import lombok .extern .slf4j .Slf4j ;
1822
1923import java .io .IOException ;
2024import java .util .List ;
25+ import java .util .Map ;
26+ import java .util .Optional ;
27+ import java .util .concurrent .ConcurrentHashMap ;
28+ import java .util .concurrent .ConcurrentMap ;
29+ import java .util .concurrent .ScheduledExecutorService ;
30+ import java .util .concurrent .TimeUnit ;
2131
2232import static com .slack .api .bolt .middleware .MiddlewareOps .isNoAuthRequiredRequest ;
2333import static com .slack .api .bolt .response .ResponseTypes .ephemeral ;
@@ -31,6 +41,17 @@ public class MultiTeamsAuthorization implements Middleware {
3141 private final AppConfig config ;
3242 private final InstallationService installationService ;
3343
44+ @ Data
45+ @ AllArgsConstructor
46+ static class CachedAuthTestResponse {
47+ private AuthTestResponse response ;
48+ private long cachedMillis ;
49+ }
50+
51+ // token -> auth.test response
52+ private final ConcurrentMap <String , CachedAuthTestResponse > tokenToAuthTestCache = new ConcurrentHashMap <>();
53+ private final Optional <ScheduledExecutorService > tokenToAuthTestCacheCleaner ;
54+
3455 private boolean alwaysRequestUserTokenNeeded ;
3556
3657 public boolean isAlwaysRequestUserTokenNeeded () {
@@ -45,6 +66,40 @@ public MultiTeamsAuthorization(AppConfig config, InstallationService installatio
4566 this .config = config ;
4667 this .installationService = installationService ;
4768 setAlwaysRequestUserTokenNeeded (config .isAlwaysRequestUserTokenNeeded ());
69+ if (config .isAuthTestCacheEnabled ()) {
70+ boolean permanentCacheEnabled = config .getAuthTestCacheExpirationMillis () < 0 ;
71+ if (permanentCacheEnabled ) {
72+ this .tokenToAuthTestCacheCleaner = Optional .empty ();
73+ } else {
74+ this .tokenToAuthTestCacheCleaner = Optional .of (buildTokenToAuthTestCacheCleaner (() -> {
75+ long expirationMillis = System .currentTimeMillis () - config .getAuthTestCacheExpirationMillis ();
76+ for (Map .Entry <String , CachedAuthTestResponse > each : tokenToAuthTestCache .entrySet ()) {
77+ if (each .getValue () == null || each .getValue ().getCachedMillis () < expirationMillis ) {
78+ tokenToAuthTestCache .remove (each .getKey ());
79+ }
80+ }
81+ }));
82+ }
83+ } else {
84+ this .tokenToAuthTestCacheCleaner = Optional .empty ();
85+ }
86+ }
87+
88+ private ScheduledExecutorService buildTokenToAuthTestCacheCleaner (Runnable task ) {
89+ String threadGroupName = MultiTeamsAuthorization .class .getSimpleName ();
90+ ScheduledExecutorService tokenToAuthTestCacheCleaner =
91+ ExecutorServiceFactory .createDaemonThreadScheduledExecutor (threadGroupName );
92+ tokenToAuthTestCacheCleaner .scheduleAtFixedRate (task , 120_000 , 30_000 , TimeUnit .MILLISECONDS );
93+ log .debug ("The tokenToAuthTestCacheCleaner (daemon thread) started" );
94+ return tokenToAuthTestCacheCleaner ;
95+ }
96+
97+ @ Override
98+ protected void finalize () throws Throwable {
99+ if (this .tokenToAuthTestCacheCleaner .isPresent ()) {
100+ this .tokenToAuthTestCacheCleaner .get ().shutdown ();
101+ }
102+ super .finalize ();
48103 }
49104
50105 @ Override
@@ -110,7 +165,7 @@ public Response apply(Request req, Response resp, MiddlewareChain chain) throws
110165
111166 try {
112167 String token = botToken != null ? botToken : userToken ;
113- AuthTestResponse authTestResponse = context . client (). authTest ( r -> r . token ( token ));
168+ AuthTestResponse authTestResponse = callAuthTest ( token , config , context . client ( ));
114169 if (authTestResponse .isOk ()) {
115170 context .setBotToken (botToken );
116171 context .setRequestUserToken (userToken );
@@ -131,6 +186,28 @@ public Response apply(Request req, Response resp, MiddlewareChain chain) throws
131186 }
132187 }
133188
189+ protected AuthTestResponse callAuthTest (String token , AppConfig config , MethodsClient client ) throws IOException , SlackApiException {
190+ if (config .isAuthTestCacheEnabled ()) {
191+ CachedAuthTestResponse cachedResponse = tokenToAuthTestCache .get (token );
192+ if (cachedResponse != null ) {
193+ boolean permanentCacheEnabled = config .getAuthTestCacheExpirationMillis () < 0 ;
194+ if (permanentCacheEnabled ) {
195+ return cachedResponse .getResponse ();
196+ }
197+ long millisToExpire = cachedResponse .getCachedMillis () + config .getAuthTestCacheExpirationMillis ();
198+ if (millisToExpire > System .currentTimeMillis ()) {
199+ return cachedResponse .getResponse ();
200+ }
201+ }
202+ AuthTestResponse response = client .authTest (r -> r .token (token ));
203+ CachedAuthTestResponse newCache = new CachedAuthTestResponse (response , System .currentTimeMillis ());
204+ tokenToAuthTestCache .put (token , newCache );
205+ return response ;
206+ } else {
207+ return client .authTest (r -> r .token (token ));
208+ }
209+ }
210+
134211 protected Response handleAuthTestError (
135212 String errorCode ,
136213 Bot foundBot ,
0 commit comments