TwoFactorAuthService.java

package com.edtech.service;

import dev.samstevens.totp.code.CodeVerifier;
import dev.samstevens.totp.code.DefaultCodeGenerator;
import dev.samstevens.totp.code.DefaultCodeVerifier;
import dev.samstevens.totp.code.HashingAlgorithm;
import dev.samstevens.totp.exceptions.QrGenerationException;
import dev.samstevens.totp.qr.QrData;
import dev.samstevens.totp.qr.QrGenerator;
import dev.samstevens.totp.qr.ZxingPngQrGenerator;
import dev.samstevens.totp.secret.DefaultSecretGenerator;
import dev.samstevens.totp.secret.SecretGenerator;
import dev.samstevens.totp.time.SystemTimeProvider;
import dev.samstevens.totp.time.TimeProvider;
import java.util.Base64;
import org.springframework.stereotype.Service;

/** Javadoc. */
@Service
public class TwoFactorAuthService {

  private final SecretGenerator secretGenerator;
  private final QrGenerator qrGenerator;
  private final CodeVerifier codeVerifier;

  /** Javadoc. */
  public TwoFactorAuthService() {
    this.secretGenerator = new DefaultSecretGenerator();
    this.qrGenerator = new ZxingPngQrGenerator();

    TimeProvider timeProvider = new SystemTimeProvider();
    DefaultCodeGenerator codeGenerator = new DefaultCodeGenerator();
    this.codeVerifier = new DefaultCodeVerifier(codeGenerator, timeProvider);
  }

  /** Generates a new Base32 encoded secret. */
  public String generateSecret() {
    return secretGenerator.generate();
  }

  /** Generates a QR Code Image (Data URI encoded in Base64) for the given user email and secret. */
  public String getQrCodeImageUri(String secret, String email) throws QrGenerationException {
    QrData data =
        new QrData.Builder()
            .label(email)
            .secret(secret)
            .issuer("EdTech")
            .algorithm(HashingAlgorithm.SHA1)
            .digits(6)
            .period(30)
            .build();

    byte[] imageData = qrGenerator.generate(data);
    String mimeType = qrGenerator.getImageMimeType();

    return "data:" + mimeType + ";base64," + Base64.getEncoder().encodeToString(imageData);
  }

  /** Verifies the given code against the given secret. */
  public boolean verifyCode(String secret, String code) {
    return codeVerifier.isValidCode(secret, code);
  }
}