1797. Design Authentication Manager

MediumDesignHash Table
Leetcode Link

Problem Description

The problem describes an authentication system that issues authentication tokens, each with an expiration time set in seconds. The expiration time (timeToLive) is a fixed duration from the moment the token is either generated or renewed. The currentTime is a timestamp indicating the current time in seconds. The system should perform three main functions:

  1. Generate a new token with a unique tokenId and set its expiry time.
  2. Renew an existing unexpired token with the given tokenId. If the token is expired or does not exist, no action is taken.
  3. Count the number of unexpired tokens at a given currentTime.

The catch is that the expiration is processed before any other action if they both occur at the same time. The task is to implement the AuthenticationManager class that can handle these operations.

Intuition

The solution is to implement the AuthenticationManager class with the methods that handle tokens and their expiration times.

  • When a new token is generated, we just add the tokenId and its expiration time (current time + timeToLive) to a dictionary. In this dictionary, the keys are the tokenIds, and the values are their respective expiration times.

  • To renew a token, we check if the token exists and is not expired by comparing its stored expiration time with the currentTime. If it's not expired, we update its expiration time. If the token is expired or doesn't exist, we ignore the renew request.

  • To count the unexpired tokens, we iterate through the dictionary's values, which are the expiration times, and count how many of these times are greater than the currentTime.

The choice of a dictionary makes these operations efficient. The main challenge of the problem is managing the expiration logic correctly and ensuring that we're not attempting to renew or count expired tokens.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

Which algorithm should you use to find a node that is close to the root of the tree?

Solution Approach

The solution utilizes a Python dictionary for storing tokens and their expiration times, which allows for quick lookups, inserts, and updates. Here's how the implementation works:

  • Initialization (__init__ method): The AuthenticationManager is initialized with the timeToLive value, which determines the lifespan of each token. We also define a dictionary self.d that will map each tokenId to its expiration time.

  • Generating tokens (generate method): When a new token is generated, we calculate its expiration time (currentTime + self.t) and store it in the dictionary self.d with tokenId as the key. This process is instant due to the efficient nature of dictionary operations in Python.

    1def generate(self, tokenId: str, currentTime: int) -> None:
    2    self.d[tokenId] = currentTime + self.t
  • Renewing tokens (renew method): To renew a token, we first check whether the token exists and is not expired by comparing the current time with the stored expiration time. If these conditions are met, we update the token's expiration time to the new current time plus timeToLive. If the token is not found or is expired, we do nothing.

    1def renew(self, tokenId: str, currentTime: int) -> None:
    2    if tokenId in self.d and self.d[tokenId] > currentTime:
    3        self.d[tokenId] = currentTime + self.t
  • Counting unexpired tokens (countUnexpiredTokens method): This function iterates over all expiration times in the dictionary and counts the number of times greater than the current time, indicating the tokens still valid.

    1def countUnexpiredTokens(self, currentTime: int) -> int:
    2    return sum(exp > currentTime for exp in self.d.values())

The use of the sum function combined with a generator expression makes counting efficient. The expression exp > currentTime evaluates to True or False, which in Python correspond to 1 and 0. Thus, sum effectively counts the number of unexpired tokens.

With this implementation, the AuthenticationManager accomplishes the requested operations with a time complexity of O(1) for generating or renewing a token and O(n) for counting unexpired tokens, where n is the number of tokens being managed.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the maximum element can be found in:

Example Walkthrough

Let's walk through an example to understand how the AuthenticationManager is implemented and functions with the given solution approach.

  • Assume the timeToLive value for tokens is set to 5 seconds.
  • The currentTime when a token is generated is 1.

Now, let's run through the methods:

  1. Initialization:

    1manager = AuthenticationManager(5)

    We have created an instance of AuthenticationManager named manager with timeToLive set to 5 seconds. This means each token will expire 5 seconds after it has been created or renewed.

  2. Generating a token:

    1manager.generate("token123", 1)

    After this call, the internal dictionary self.d will contain:

    1{"token123": 6}

    This indicates that token123 will expire at time 6 (currentTime + timeToLive).

  3. Renewing a token: Suppose the current time now is 3, and we wish to renew token123:

    1manager.renew("token123", 3)

    The internal dictionary gets updated:

    1{"token123": 8}

    This is because the current time is 3, and adding the timeToLive value of 5 seconds gives us the new expiration time at 8.

  4. Attempting to renew an expired token: If we fast-forward the time to 7 and attempt to renew token123:

    1manager.renew("token123", 7)

    Nothing changes since token123 was set to expire at time 8, so it's still valid and can be renewed:

    1{"token123": 12}
  5. Counting unexpired tokens: Let's now count the unexpired tokens at the current time, which is 7:

    1count = manager.countUnexpiredTokens(7)

    The count will be 1 because token123 will now expire at 12, which is greater than the current time of 7.

This example demonstrates how the AuthenticationManager efficiently manages tokens, handles renewals, and counts unexpired tokens as required by the problem description. The implementation ensures rapid generation and renewal using dictionary operations and provides an O(n) approach to count unexpired tokens, where n is the total number of tokens.

Solution Implementation

1from collections import defaultdict
2
3class AuthenticationManager:
4    def __init__(self, time_to_live: int):
5        # Initialize the time_to_live for the tokens
6        self.time_to_live = time_to_live
7        # Use a dictionary to keep track of the tokens and their expiration times
8        self.token_expirations = defaultdict(int)
9
10    def generate(self, token_id: str, current_time: int) -> None:
11        # Create a new token with an expiration time based on current_time + time_to_live
12        self.token_expirations[token_id] = current_time + self.time_to_live
13
14    def renew(self, token_id: str, current_time: int) -> None:
15        # Renew a token's expiration time if it hasn't already expired
16        if self.token_expirations[token_id] > current_time:
17            self.token_expirations[token_id] = current_time + self.time_to_live
18
19    def count_unexpired_tokens(self, current_time: int) -> int:
20        # Count the number of tokens that have not yet expired
21        return sum(expiration_time > current_time for expiration_time in self.token_expirations.values())
22
23# Example of how the AuthenticationManager class can be used:
24# authentication_manager = AuthenticationManager(time_to_live)
25# authentication_manager.generate(token_id, current_time)
26# authentication_manager.renew(token_id, current_time)
27# unexpired_tokens_count = authentication_manager.count_unexpired_tokens(current_time)
28
1import java.util.HashMap;
2import java.util.Map;
3
4class AuthenticationManager {
5    private int timeToLive; // duration for which the tokens are valid
6    private Map<String, Integer> tokenExpiryMap; // holds token IDs and their corresponding expiration times
7
8    // Constructor initializes the AuthenticationManager with a specific time to live for tokens
9    public AuthenticationManager(int timeToLive) {
10        this.timeToLive = timeToLive;
11        this.tokenExpiryMap = new HashMap<>();
12    }
13
14    // Generates a new token with an expiration time based on the current time
15    public void generate(String tokenId, int currentTime) {
16        // Put the token and its expiration time into the map
17        tokenExpiryMap.put(tokenId, currentTime + timeToLive);
18    }
19
20    // Renews an existing token if it hasn't expired yet
21    public void renew(String tokenId, int currentTime) {
22        // Retrieve the current expiration time for the token
23        Integer expirationTime = tokenExpiryMap.getOrDefault(tokenId, 0);
24      
25        // If the token is still valid (hasn't expired), renew it by updating its expiration time
26        if (expirationTime > currentTime) {
27            generate(tokenId, currentTime);
28        }
29    }
30
31    // Counts the number of unexpired tokens at a given current time
32    public int countUnexpiredTokens(int currentTime) {
33        // Initialize a counter to keep track of valid tokens
34        int count = 0;
35      
36        // Iterate through all tokens and increment the count if they're not expired
37        for (int expirationTime : tokenExpiryMap.values()) {
38            if (expirationTime > currentTime) {
39                count++;
40            }
41        }
42      
43        // Return the total number of unexpired tokens
44        return count;
45    }
46}
47
48// The AuthenticationManager class functionality can be utilized as shown below:
49// AuthenticationManager obj = new AuthenticationManager(timeToLive);
50// obj.generate(tokenId, currentTime);
51// obj.renew(tokenId, currentTime);
52// int param_3 = obj.countUnexpiredTokens(currentTime);
53
1#include <unordered_map>
2#include <string>
3using namespace std;
4
5class AuthenticationManager {
6public:
7    // Constructor with initialization of timeToLive for tokens
8    AuthenticationManager(int timeToLive) : timeToLive_(timeToLive) {
9    }
10
11    // Generate a new token with a given tokenId and current time
12    void generate(string tokenId, int currentTime) {
13        // Token is valid from the current time until 'currentTime + timeToLive_'
14        tokens_[tokenId] = currentTime + timeToLive_;
15    }
16
17    // Renew a token if it's still valid at the given current time
18    void renew(string tokenId, int currentTime) {
19        // If the token exists and is not expired, renew its expiration time
20        if (tokens_.find(tokenId) != tokens_.end() && tokens_[tokenId] > currentTime) {
21            generate(tokenId, currentTime);
22        }
23    }
24
25    // Return the count of tokens that are not yet expired at the given current time
26    int countUnexpiredTokens(int currentTime) {
27        int count = 0;
28        for (const auto& tokenPair : tokens_) {
29            // If the token's expiration time is greater than currentTime, increment the count
30            if (tokenPair.second > currentTime) {
31                count++;
32            }
33        }
34        return count;
35    }
36
37private:
38    // Time to live for each token
39    int timeToLive_;
40
41    // Stores token IDs and their respective expiration times
42    unordered_map<string, int> tokens_;
43};
44
45/**
46 * Usage:
47 * AuthenticationManager* authManager = new AuthenticationManager(timeToLive);
48 * authManager->generate(tokenId, currentTime);
49 * authManager->renew(tokenId, currentTime);
50 * int activeTokens = authManager->countUnexpiredTokens(currentTime);
51 * delete authManager; // Remember to deallocate memory
52 */
53
1// Time-to-live for authentication tokens
2let timeToLive: number;
3
4// A map to store token IDs and their expiration times
5let tokenMap: Map<string, number>;
6
7// Initializes variables with the specified timeToLive
8function initializeAuthenticationManager(ttl: number): void {
9    timeToLive = ttl;
10    tokenMap = new Map<string, number>();
11}
12
13// Generates a new token with a given tokenId that expires after timeToLive
14function generateToken(tokenId: string, currentTime: number): void {
15    tokenMap.set(tokenId, currentTime + timeToLive);
16}
17
18// Renews the token with the given tokenId if it hasn't expired
19function renewToken(tokenId: string, currentTime: number): void {
20    const expirationTime = tokenMap.get(tokenId);
21    if (expirationTime && expirationTime > currentTime) {
22        tokenMap.set(tokenId, currentTime + timeToLive);
23    }
24}
25
26// Counts the number of unexpired tokens at the given currentTime
27function countUnexpiredTokens(currentTime: number): number {
28    let count = 0;
29    tokenMap.forEach((expirationTime) => {
30        if (expirationTime > currentTime) {
31            count++;
32        }
33    });
34    return count;
35}
36
37// Example usage:
38// initializeAuthenticationManager(3600); // Initialize with 1 hour TTL
39// generateToken("token123", 100); // Generate a new token with ID "token123"
40// renewToken("token123", 500); // Attempt to renew "token123" at time 500
41// let unexpiredCount = countUnexpiredTokens(1000); // Count unexpired tokens at time 1000
42
Not Sure What to Study? Take the 2-min Quiz:

Given an array of 1,000,000 integers that is almost sorted, except for 2 pairs of integers. Which algorithm is fastest for sorting the array?

Time and Space Complexity

Time Complexity:

  • __init__: O(1) because we are just setting up variables.
  • generate: O(1) as it involves a single operation of assigning a new expiration time for a token.
  • renew: O(1) for accessing the dictionary and updating a token's expiration time conditionally. The conditional check and dictionary update happen in constant time.
  • countUnexpiredTokens: O(n) where n is the number of tokens in the dictionary. This is due to the iteration over all values to sum the number of unexpired tokens.

Space Complexity:

  • The space complexity of the entire class is O(n), where n is the number of different tokens stored. The dictionary self.d grows as new tokens are generated or renewed.

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

Which of the following array represent a max heap?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫