content: package com.nonononoki.alovoa.service;
  
  import java.awt.Color;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.nio.charset.StandardCharsets;
  import java.security.MessageDigest;
  import java.security.NoSuchAlgorithmException;
  import java.util.Base64;
  import java.util.Date;
  
  import javax.imageio.ImageIO;
  import jakarta.servlet.http.HttpServletRequest;
  
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.stereotype.Service;
  
  import com.nonononoki.alovoa.entity.Captcha;
  import com.nonononoki.alovoa.lib.OxCaptcha;
  import com.nonononoki.alovoa.repo.CaptchaRepository;
  
  @Service
  public class CaptchaService {
  
  	@Autowired
  	private CaptchaRepository captchaRepo;
  
  	@Autowired
  	private HttpServletRequest request;
  
  	@Value("${app.captcha.length}")
  	private int captchaLength;
  
  	@Value("${app.text.salt}")
  	private String salt;
  
  	private static final int WIDTH = 120;
  	private static final int HEIGHT = 70;
  
  	private static final Color BG_COLOR = new Color(0, 0, 0, 0);
  	private static final Color FG_COLOR = new Color(118, 118, 118);
  
  	public Captcha generate() throws NoSuchAlgorithmException, IOException {
  
  		String ipHash = getIpHash(request.getRemoteAddr());
  		Captcha oldCaptcha = captchaRepo.findByHashCode(ipHash);
  		if (oldCaptcha != null) {
  			captchaRepo.delete(oldCaptcha);
  			captchaRepo.flush();
  		}
  
  		OxCaptcha ox = generateCaptchaImage();
  		ByteArrayOutputStream baos = new ByteArrayOutputStream();
  		ImageIO.write(ox.getImage(), "webp", baos);
  		byte[] ba = baos.toByteArray();
  		String encoded = Base64.getEncoder().encodeToString(ba);
  		Captcha captcha = new Captcha();
  		captcha.setDate(new Date());
  		captcha.setImage(encoded);
  		captcha.setText(ox.getText());
  		captcha.setHashCode(ipHash);
  		captcha = captchaRepo.saveAndFlush(captcha);
  		return captcha;
  	}
  
  	private OxCaptcha generateCaptchaImage() {
  		OxCaptcha c = new OxCaptcha(WIDTH, HEIGHT);
  		c.foreground(FG_COLOR);
  		c.background(BG_COLOR);
  		c.text(captchaLength);
  
  		c.distortion();
  		c.noiseStraightLine();
  		c.noiseStraightLine();
  		c.noiseStraightLine();
  
  		return c;
  	}
  
  	public boolean isValid(long id, String text) throws UnsupportedEncodingException, NoSuchAlgorithmException {
  
  		Captcha captcha = captchaRepo.findById(id).orElse(null);
  		if (captcha == null) {
  			return false;
  		}
  
  		captchaRepo.delete(captcha);
  		captchaRepo.flush();
  
  		return captcha.getHashCode().equals(getIpHash(request.getRemoteAddr()))
  				&& captcha.getText().equalsIgnoreCase(text);
  	}
  
  	private String getIpHash(String ip) throws UnsupportedEncodingException, NoSuchAlgorithmException {
  		// don't need slow hashing algorithm because
  		MessageDigest md = MessageDigest.getInstance("MD5");
  		md.update(salt.getBytes()); // salting to prevent rainbow tables
  		md.update(ip.getBytes(StandardCharsets.UTF_8));
  		byte[] bytes = md.digest();
  		return Base64.getEncoder().encodeToString(bytes);
  	}
  }
  