package com.thebund1st.daming.redis; import com.thebund1st.daming.core.MobilePhoneNumber; import com.thebund1st.daming.core.SmsVerificationScope; import com.thebund1st.daming.core.DomainEventPublisher; import com.thebund1st.daming.events.SmsVerificationCodeMismatchEvent; import com.thebund1st.daming.events.SmsVerificationCodeVerifiedEvent; import com.thebund1st.daming.events.TooManyFailureSmsVerificationAttemptsEvent; import com.thebund1st.daming.time.Clock; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.connection.StringRedisConnection; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import java.time.Duration; import java.util.List; import java.util.UUID; import static java.util.stream.Collectors.joining; @Slf4j @RequiredArgsConstructor public class RedisSmsVerificationCodeMismatchEventHandler { private static final String DELIMITER = ","; private final StringRedisTemplate redisTemplate; private final DeleteFromRedis deleteFromRedis; private final DomainEventPublisher domainEventPublisher; private final Clock clock; @Setter private int threshold = 5; public void on(SmsVerificationCodeMismatchEvent event) { String key = toKey(event.getMobile(), event.getScope()); List<Object> attempts = redisTemplate.executePipelined((RedisCallback<Long>) connection -> { StringRedisConnection conn = (StringRedisConnection) connection; conn.sAdd(key, event.toString()); long expires = Duration.between(event.getWhen(), event.getExpiresAt()).getSeconds(); conn.expire(key, expires); conn.sCard(key); return null; }); log.debug("Got Redis pipeline {}", attempts.stream().map(Object::toString).collect(joining(DELIMITER))); if (attempts.size() == 3) { if (toAttempts(attempts) >= threshold) { log.info("Too many failure verification attempts for {} {}", event.getMobile(), event.getScope()); remove(key); domainEventPublisher.publish(new TooManyFailureSmsVerificationAttemptsEvent(UUID.randomUUID().toString(), clock.now(), event.getMobile(), event.getScope())); } } } public void on(SmsVerificationCodeVerifiedEvent event) { String key = toKey(event.getMobile(), event.getScope()); remove(key); } private void remove(String key) { deleteFromRedis.delete(key); } private Long toAttempts(List<Object> attempts) { return (Long) attempts.get(attempts.size() - 1); } private String toKey(MobilePhoneNumber mobile, SmsVerificationScope scope) { return String.format("sms.verification.code.mismatch.%s.%s", mobile.getValue(), scope.getValue()); } }