Design (LLD)  Google Authenticator  - Machine Coding

Design (LLD) Google Authenticator - Machine Coding

Features Required:

  1. QR Code Generation: Ability to generate QR codes for two-factor authentication.

  2. Token Generation: Generate time-based tokens for authentication.

  3. Token Verification: Verify the correctness of the entered token.

  4. Secret Key Storage: Securely store and retrieve secret keys associated with users.

  5. User Registration: Register new users for two-factor authentication.

  6. User Management: Manage users, including adding, updating, and removing them.

  7. Algorithm Flexibility: Support for different hashing algorithms.

  8. Time Synchronization: Handle time synchronization issues.

Design Patterns Involved or Used:

  1. Singleton Pattern: For the GoogleAuthenticator instance, ensuring a single point of access to the authentication system.

  2. Factory Method Pattern: For creating instances of authentication tokens, allowing flexibility for different token types.

  3. Strategy Pattern: For supporting different hashing algorithms and making them interchangeable.

  4. Facade Pattern: Simplifying the complexity of the authentication system by providing a higher-level interface.

  5. Observer Pattern: For notifying users or systems about events like successful or failed authentication attempts.

  6. Command Pattern: For encapsulating token generation and verification requests as objects.

  7. Data Access Object (DAO) Pattern: For handling data access and storage, especially for user-related data.

Diagram

Code

import java.security.Key;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

// Command Interface
interface TokenCommand {
    void execute();
}

// Token Generation Command
class GenerateTokenCommand implements TokenCommand {
    private TokenFactory tokenFactory;
    private String secretKey;
    private long currentTimeMillis;

    public GenerateTokenCommand(TokenFactory tokenFactory, String secretKey, long currentTimeMillis) {
        this.tokenFactory = tokenFactory;
        this.secretKey = secretKey;
        this.currentTimeMillis = currentTimeMillis;
    }

    public void execute() {
        // Execute token generation
        String generatedToken = tokenFactory.generateToken(secretKey, currentTimeMillis);
        System.out.println("Generated Token: " + generatedToken);
        // Additional logic, if needed
    }
}

// Token Verification Command
class VerifyTokenCommand implements TokenCommand {
    private TokenVerifier tokenVerifier;
    private String enteredCode;
    private String generatedCode;

    public VerifyTokenCommand(TokenVerifier tokenVerifier, String enteredCode, String generatedCode) {
        this.tokenVerifier = tokenVerifier;
        this.enteredCode = enteredCode;
        this.generatedCode = generatedCode;
    }

    public void execute() {
        // Execute token verification
        boolean isVerified = tokenVerifier.verifyToken(enteredCode, generatedCode);
        System.out.println("Token Verification Result: " + isVerified);
        // Additional logic, if needed
    }
}

// Token Operation Invoker
class TokenOperationInvoker {
    private TokenCommand tokenCommand;

    public void setCommand(TokenCommand tokenCommand) {
        this.tokenCommand = tokenCommand;
    }

    public void executeOperation() {
        // Execute the assigned command
        tokenCommand.execute();
    }
}

// Singleton Pattern
class GoogleAuthenticator {
    private static GoogleAuthenticator instance;
    private TokenFactory tokenFactory;
    private TokenVerifier tokenVerifier;
    private UserDao userDao;
    private TokenOperationInvoker tokenOperationInvoker;

    private GoogleAuthenticator() {
        this.tokenFactory = new DefaultTokenFactory();
        this.tokenVerifier = new DefaultTokenVerifier();
        this.userDao = new InMemoryUserDao();
        this.tokenOperationInvoker = new TokenOperationInvoker();
    }

    public static GoogleAuthenticator getInstance() {
        if (instance == null) {
            instance = new GoogleAuthenticator();
        }
        return instance;
    }

    // Other methods for QR code generation, user registration, etc.

    public void generateAndExecuteTokenGenerationCommand(String userId, long currentTimeMillis) {
        User user = userDao.getUser(userId);
        if (user != null) {
            String secretKey = user.getSecretKey();
            // Create the token generation command
            TokenCommand generateTokenCommand = new GenerateTokenCommand(tokenFactory, secretKey, currentTimeMillis);
            // Set the command
            tokenOperationInvoker.setCommand(generateTokenCommand);
            // Execute the command
            tokenOperationInvoker.executeOperation();
        }
    }

    public void generateAndExecuteTokenVerificationCommand(String userId, String enteredCode, String generatedCode) {
        User user = userDao.getUser(userId);
        if (user != null) {
            // Create the token verification command
            TokenCommand verifyTokenCommand = new VerifyTokenCommand(tokenVerifier, enteredCode, generatedCode);
            // Set the command
            tokenOperationInvoker.setCommand(verifyTokenCommand);
            // Execute the command
            tokenOperationInvoker.executeOperation();
        }
    }

    // Other methods...
}

// Factory Method Pattern
interface TokenFactory {
    String generateToken(String secretKey, long currentTimeMillis);
}

class DefaultTokenFactory implements TokenFactory {
    @Override
    public String generateToken(String secretKey, long currentTimeMillis) {
        // Implementation details...
        return "generated_token";
    }
}

// Strategy Pattern
interface TokenVerifier {
    boolean verifyToken(String enteredCode, String generatedCode);
}

class DefaultTokenVerifier implements TokenVerifier {
    @Override
    public boolean verifyToken(String enteredCode, String generatedCode) {
        // Implementation details...
        return enteredCode.equals(generatedCode);
    }
}

// Data Access Object (DAO) Pattern
interface UserDao {
    void addUser(User user);

    User getUser(String userId);

    void updateUser(User user);

    void removeUser(String userId);
}

class User {
    private String userId;
    private String name;
    private String secretKey;

    public User(String userId, String name, String secretKey) {
        this.userId = userId;
        this.name = name;
        this.secretKey = secretKey;
    }

    public String getUserId() {
        return userId;
    }

    public String getSecretKey() {
        return secretKey;
    }
}

class InMemoryUserDao implements UserDao {
    private Map<String, User> users;

    public InMemoryUserDao() {
        this.users = new HashMap<>();
    }

    @Override
    public void addUser(User user) {
        users.put(user.getUserId(), user);
    }

    @Override
    public User getUser(String userId) {
        return users.get(userId);
    }

    @Override
    public void updateUser(User user) {
        users.put(user.getUserId(), user);
    }

    @Override
    public void removeUser(String userId) {
        users.remove(userId);
    }
}


public class GoogleAuthenticatorApp {
    public static void main(String[] args) {
        GoogleAuthenticator authenticator = GoogleAuthenticator.getInstance();

        User user = new User("123", "John Doe", "secretKey123");
        authenticator.generateAndExecuteTokenGenerationCommand(user.getUserId(), System.currentTimeMillis());

        // Simulating user entering the code
        String enteredCode = "generated_token"; // Replace with the actual user input
        authenticator.generateAndExecuteTokenVerificationCommand(user.getUserId(), enteredCode, "generated_token");
    }
}

This is a simplified implementation showcasing some aspects of the design. The actual implementation may require additional considerations and refinements based on specific use cases and security requirements.

Did you find this article valuable?

Support Subhahu Jain by becoming a sponsor. Any amount is appreciated!