Skip to content

Commit

Permalink
Feature: discord fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateusz Czeladka committed Sep 18, 2023
1 parent e8c69d4 commit c8e193f
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
import java.util.Arrays;
import java.util.List;

import static org.springframework.http.HttpMethod.*;

@Configuration
@Import(SecurityProblemSupport.class)
public class SpringSecurityConfiguration {
Expand Down Expand Up @@ -77,8 +79,12 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.anonymous(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
.requestMatchers(new AntPathRequestMatcher("/api/sms/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/api/discord/**")).hasRole("BOT")
.requestMatchers(new AntPathRequestMatcher("/api/user-verification/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/api/discord/user-verification/is-verified/**", GET.name())).hasRole("BOT")
.requestMatchers(new AntPathRequestMatcher("/api/discord/user-verification/start-verification", POST.name())).hasRole("BOT")
.requestMatchers(new AntPathRequestMatcher("/api/discord/user-verification/start-verification", PUT.name())).hasRole("BOT")
.requestMatchers(new AntPathRequestMatcher("/api/discord/user-verification/check-verification", POST.name())).permitAll()

.requestMatchers(new AntPathRequestMatcher("/api/user-verification/verified/**", GET.name())).permitAll()
.anyRequest().denyAll()
)
.rememberMe(AbstractHttpConfigurer::disable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
@ToString
public class DiscordCheckVerificationRequest {

@NotBlank
private String eventId;

@NotBlank
private String stakeAddress;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
public interface DiscordUserVerificationRepository extends JpaRepository<DiscordUserVerification, String> {

@Query("SELECT uv FROM DiscordUserVerification uv WHERE uv.status = 'VERIFIED' AND uv.eventId = :eventId AND uv.stakeAddress = :stakeAddress")
Optional<DiscordUserVerification> findCompletedVerification(@Param("eventId") String eventId,
@Param("stakeAddress") String stakeAddress
List<DiscordUserVerification> findCompletedVerifications(@Param("eventId") String eventId,
@Param("stakeAddress") String stakeAddress
);

@Query("SELECT uv FROM DiscordUserVerification uv WHERE uv.status = 'VERIFIED' AND uv.eventId = :eventId AND uv.discordIdHash = :discordIdHash")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;
import org.zalando.problem.Problem;

import static org.springframework.web.bind.annotation.RequestMethod.*;
import static org.zalando.problem.Status.BAD_REQUEST;

@RestController
@RequestMapping("/api/discord/user-verification")
Expand All @@ -36,7 +36,7 @@ public ResponseEntity<?> isDiscordUserVerified(@PathVariable("discordIdHash") St

return discordUserVerificationService.isVerifiedBasedOnDiscordIdHash(discordBotEventIdBinding, discordIdHash)
.fold(problem -> {
return ResponseEntity.status(Objects.requireNonNull(problem.getStatus()).getStatusCode()).body(problem);
return ResponseEntity.status(problem.getStatus().getStatusCode()).body(problem);
},
userVerification -> {
return ResponseEntity.ok().body(userVerification);
Expand All @@ -59,12 +59,20 @@ public ResponseEntity<?> startVerification(@RequestBody @Valid DiscordStartVerif
);
}

@RequestMapping(value = "/check-verification", method = { POST , PUT }, produces = "application/json")
@RequestMapping(value = "/check-verification", method = { POST }, produces = "application/json")
@Timed(value = "resource.discord.checkVerification", histogram = true)
public ResponseEntity<?> checkVerification(@RequestBody @Valid DiscordCheckVerificationRequest checkVerificationRequest) {
log.info("Received discord checkVerification request: {}", checkVerificationRequest);

return discordUserVerificationService.checkVerification(discordBotEventIdBinding, checkVerificationRequest)
if (!checkVerificationRequest.getEventId().equals(discordBotEventIdBinding)) {
return ResponseEntity.badRequest().
body(Problem.builder().withTitle("EVENT_ID_AND_DISCORD_ID_BOT_MISMATCH")
.withDetail("Event id and discord id bot mismatch!")
.withStatus(BAD_REQUEST)
.build());
}

return discordUserVerificationService.checkVerification(checkVerificationRequest)
.fold(problem -> {
return ResponseEntity.status(problem.getStatus().getStatusCode()).body(problem);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import lombok.extern.slf4j.Slf4j;
import org.cardano.foundation.voting.domain.IsVerifiedRequest;
import org.cardano.foundation.voting.domain.IsVerifiedResponse;
import org.cardano.foundation.voting.service.discord.DiscordUserVerificationService;
import org.cardano.foundation.voting.service.sms.SMSUserVerificationService;
import org.cardano.foundation.voting.service.common.UserVerificationService;
import org.cardano.foundation.voting.utils.CompletableFutures;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -27,46 +26,22 @@
@RequiredArgsConstructor
public class UserVerificationResource {

private final SMSUserVerificationService smsUserVerificationService;

private final DiscordUserVerificationService discordUserVerificationService;
private final UserVerificationService userVerificationService;

@RequestMapping(value = "/verified/{eventId}/{stakeAddress}", method = GET, produces = "application/json")
@Timed(value = "resource.isVerified", histogram = true)
public ResponseEntity<?> isVerified(@PathVariable("eventId") String eventId,
@PathVariable("stakeAddress") String stakeAddress) {
var isVerifiedRequest = new IsVerifiedRequest(eventId, stakeAddress);

log.info("Received isVerified request: {}", isVerifiedRequest);

CompletableFuture<Either<Problem, IsVerifiedResponse>> smsVerificationFuture = CompletableFuture.supplyAsync(() -> {
return smsUserVerificationService.isVerified(isVerifiedRequest);
});

CompletableFuture<Either<Problem, IsVerifiedResponse>> discordVerificationFuture = CompletableFuture.supplyAsync(() -> {
return discordUserVerificationService.isVerifiedBasedOnStakeAddress(isVerifiedRequest);
});

var allFutures = CompletableFutures.anyResultsOf(List.of(smsVerificationFuture, discordVerificationFuture));

List<Either<Problem, IsVerifiedResponse>> allResponses = allFutures.orTimeout(30, SECONDS)
.join();

var successCount = allResponses.stream().filter(Either::isRight).count();

if (successCount != 2) {
var problem = allResponses.stream().filter(Either::isLeft).findFirst().orElseThrow().getLeft();

return ResponseEntity.status(problem.getStatus().getStatusCode()).body(problem);
}

var successes = allResponses.stream().filter(Either::isRight).toList().stream().map(Either::get).toList();

var isVerified = successes.stream().reduce((a, b) -> {
return new IsVerifiedResponse(a.isVerified() || b.isVerified());
}).orElse(new IsVerifiedResponse(false));

return ResponseEntity.ok().body(isVerified);
return userVerificationService.isVerified(isVerifiedRequest)
.fold(problem -> {
return ResponseEntity.status(problem.getStatus().getStatusCode()).body(problem);
},
isVerifiedResponse -> {
return ResponseEntity.ok().body(isVerifiedResponse);
}
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.cardano.foundation.voting.service.common;

import io.vavr.control.Either;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cardano.foundation.voting.domain.IsVerifiedRequest;
import org.cardano.foundation.voting.domain.IsVerifiedResponse;
import org.cardano.foundation.voting.service.discord.DiscordUserVerificationService;
import org.cardano.foundation.voting.service.sms.SMSUserVerificationService;
import org.cardano.foundation.voting.utils.CompletableFutures;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.zalando.problem.Problem;

import java.util.List;
import java.util.concurrent.CompletableFuture;

import static java.util.concurrent.TimeUnit.SECONDS;

@Service
@Slf4j
@RequiredArgsConstructor
public class DefaultUserVerificationService implements UserVerificationService {

private final SMSUserVerificationService smsUserVerificationService;

private final DiscordUserVerificationService discordUserVerificationService;

@Override
@Transactional(readOnly = true)
public Either<Problem, IsVerifiedResponse> isVerified(IsVerifiedRequest isVerifiedRequest) {
log.info("Received isVerified request: {}", isVerifiedRequest);

CompletableFuture<Either<Problem, IsVerifiedResponse>> smsVerificationFuture = CompletableFuture.supplyAsync(() -> {
return smsUserVerificationService.isVerified(isVerifiedRequest);
});

CompletableFuture<Either<Problem, IsVerifiedResponse>> discordVerificationFuture = CompletableFuture.supplyAsync(() -> {
return discordUserVerificationService.isVerifiedBasedOnStakeAddress(isVerifiedRequest);
});

var allFutures = CompletableFutures.anyResultsOf(List.of(smsVerificationFuture, discordVerificationFuture));

List<Either<Problem, IsVerifiedResponse>> allResponses = allFutures.orTimeout(30, SECONDS)
.join();

var successCount = allResponses.stream().filter(Either::isRight).count();

if (successCount != 2) {
var problem = allResponses.stream().filter(Either::isLeft).findFirst().orElseThrow().getLeft();

return Either.left(problem);
}

var successes = allResponses.stream().filter(Either::isRight).toList().stream().map(Either::get).toList();

var isVerified = successes.stream().reduce((a, b) -> {
return new IsVerifiedResponse(a.isVerified() || b.isVerified());
}).orElse(new IsVerifiedResponse(false));

return Either.right(isVerified);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.cardano.foundation.voting.service.common;

import io.vavr.control.Either;
import org.cardano.foundation.voting.domain.IsVerifiedRequest;
import org.cardano.foundation.voting.domain.IsVerifiedResponse;
import org.zalando.problem.Problem;

public interface UserVerificationService {

Either<Problem, IsVerifiedResponse> isVerified(IsVerifiedRequest isVerifiedRequest);

}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ public Either<Problem, DiscordStartVerificationResponse> startVerification(Strin

@Override
@Transactional
public Either<Problem, IsVerifiedResponse> checkVerification(String eventId, DiscordCheckVerificationRequest checkVerificationRequest) {
public Either<Problem, IsVerifiedResponse> checkVerification(DiscordCheckVerificationRequest checkVerificationRequest) {
var eventId = checkVerificationRequest.getEventId();
var eventDetails = chainFollowerClient.findEventById(eventId);

if (eventDetails.isEmpty()) {
Expand Down Expand Up @@ -281,8 +282,10 @@ public Either<Problem, IsVerifiedResponse> checkVerification(String eventId, Dis
}

@Override
@Transactional(readOnly = true)
public Either<Problem, IsVerifiedResponse> isVerifiedBasedOnStakeAddress(IsVerifiedRequest isVerifiedRequest) {
var isVerified = userVerificationRepository.findCompletedVerification(isVerifiedRequest.getEventId(), isVerifiedRequest.getStakeAddress())
var isVerified = userVerificationRepository.findCompletedVerifications(isVerifiedRequest.getEventId(), isVerifiedRequest.getStakeAddress())
.stream().findFirst()
.map(uv -> new IsVerifiedResponse(true)).orElse(new IsVerifiedResponse(false));

return Either.right(isVerified);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface DiscordUserVerificationService {

Either<Problem, DiscordStartVerificationResponse> startVerification(String eventId, DiscordStartVerificationRequest startVerificationRequest);

Either<Problem, IsVerifiedResponse> checkVerification(String eventId, DiscordCheckVerificationRequest checkVerificationRequest);
Either<Problem, IsVerifiedResponse> checkVerification(DiscordCheckVerificationRequest checkVerificationRequest);

Either<Problem, IsVerifiedResponse> isVerifiedBasedOnStakeAddress(IsVerifiedRequest isVerifiedRequest);

Expand Down

0 comments on commit c8e193f

Please sign in to comment.