| 
1 | 1 | import functools  | 
2 | 2 | import time  | 
3 | 3 | 
 
  | 
 | 4 | +import flask  | 
 | 5 | +import jwt  | 
4 | 6 | 
 
  | 
5 |  | -def timed_cache(ttl_seconds):  | 
 | 7 | + | 
 | 8 | +def request_auth_cache(maximum_ttl_seconds=1800):  | 
6 | 9 |     """  | 
7 |  | -    Decorator to cache the result of a function for a specified time-to-live (TTL) in seconds.  | 
 | 10 | +    Decorator to cache the result of a function for a specified maximum TTL in seconds.  | 
 | 11 | +    The actual cache duration is determined by the 'token' parameter's expiration.  | 
 | 12 | +    If no token is provided, the maximum TTL is used and the Authorization header is included in the cache key.  | 
8 | 13 |     """  | 
 | 14 | + | 
9 | 15 |     def decorator(func):  | 
10 | 16 |         cache = {}  | 
11 | 17 | 
 
  | 
12 | 18 |         @functools.wraps(func)  | 
13 | 19 |         def wrapper(*args, **kwargs):  | 
14 | 20 |             key = functools._make_key(args, kwargs, typed=False)  | 
15 | 21 |             now = time.time()  | 
 | 22 | + | 
 | 23 | +            # Extract token from args or kwargs  | 
 | 24 | +            token = kwargs.get("token")  | 
 | 25 | +            if token is None:  | 
 | 26 | +                # print("No token provided in kwargs")  | 
 | 27 | +                if type(args[0]) is str:  | 
 | 28 | +                    # If the first argument is a string, assume it's the token  | 
 | 29 | +                    token = args[0]  | 
 | 30 | +                else:  | 
 | 31 | +                    token = args[1] if len(args) > 1 else None  | 
 | 32 | + | 
 | 33 | +            # Calculate token expiration duration  | 
 | 34 | +            if token:  | 
 | 35 | +                # Decode the JWT token without verifying the signature to get the 'exp' claim  | 
 | 36 | +                # If the token is a string, decode it  | 
 | 37 | +                token = token.encode('utf-8') if isinstance(token, str) else token  | 
 | 38 | + | 
 | 39 | +                # we could check for jwt.exceptions.DecodeError here, but we assume the token is valid  | 
 | 40 | +                # and just decode it to get the expiration time  | 
 | 41 | +                payload = jwt.decode(token, options={"verify_signature": False})  | 
 | 42 | + | 
 | 43 | +                exp = payload.get("exp", now + maximum_ttl_seconds)  | 
 | 44 | +                token_ttl = max(0, exp - now)  | 
 | 45 | +            else:  | 
 | 46 | +                # If no token is provided, use the maximum TTL and add the Authorization header to the key.  | 
 | 47 | +                # This is useful for cases where the function does not require a token,  | 
 | 48 | +                # but still needs to cache based on the Authorization header.  | 
 | 49 | +                auth_header = flask.request.headers.get('Authorization', '')  | 
 | 50 | +                # Add the Authorization header to the key  | 
 | 51 | +                key = functools._make_key(args + (auth_header,), kwargs, typed=False)  | 
 | 52 | +                token_ttl = maximum_ttl_seconds  | 
 | 53 | + | 
 | 54 | +            ttl = min(token_ttl, maximum_ttl_seconds)  | 
 | 55 | + | 
 | 56 | +            # Check if the result is already cached and still valid  | 
16 | 57 |             if key in cache:  | 
17 | 58 |                 result, timestamp = cache[key]  | 
18 |  | -                if now - timestamp < ttl_seconds:  | 
 | 59 | +                if now - timestamp < ttl:  | 
19 | 60 |                     return result  | 
 | 61 | + | 
 | 62 | +            # If not cached or expired, call the function and cache the result  | 
20 | 63 |             result = func(*args, **kwargs)  | 
21 | 64 |             cache[key] = (result, now)  | 
 | 65 | + | 
 | 66 | +            # Clean up any old cache entries  | 
 | 67 | +            keys_to_delete = [k for k, (v, t) in cache.items() if now - t >= ttl]  | 
 | 68 | +            for k in keys_to_delete:  | 
 | 69 | +                del cache[k]  | 
 | 70 | + | 
22 | 71 |             return result  | 
 | 72 | + | 
23 | 73 |         return wrapper  | 
 | 74 | + | 
24 | 75 |     return decorator  | 
0 commit comments