Skip to content

Commit

Permalink
Merge pull request #19 from ADORSYS-GIS/18-tk-019-create-a-rest-endpo…
Browse files Browse the repository at this point in the history
…int-that-verifies-the-otp-in-the-prs-e5

18 tk 019 create a rest endpoint that verifies the otp in the prs e5
  • Loading branch information
Arielpetit authored Jan 9, 2025
2 parents 6f57704 + edaa817 commit e22cbb2
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ public String sendOtp(OtpRequest request) {

@Override
public boolean validateOtp(OtpValidationRequest request) {
return otpService.validateOtp(request.getPhoneNumber(), request.getOtp());
return otpService.validateOtp(request.getPhoneNumber(), request.getPublicKey() ,request.getOtpInput(), request.getOtpHash() );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,47 @@

public class OtpValidationRequest {
private String phoneNumber;
private String otp;
private String otpInput;
private String otpHash;
private String publicKey;

public OtpValidationRequest() {}

public OtpValidationRequest(String phoneNumber, String otp) {
public OtpValidationRequest(String phoneNumber, String otpInput , String otpHash, String publicKey) {
this.phoneNumber = phoneNumber;
this.otp = otp;
this.otpInput = otpInput;
this.otpHash = otpHash;
this.publicKey = publicKey;
}



public String getPhoneNumber() {
return phoneNumber;
}
public String getPublicKey(){
return publicKey;
}
public String getOtpHash() {
return otpHash;
}
public String getOtpInput() {
return otpInput;
}




public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}

public String getOtp() {
return otp;
public void setPublicKey(String publicKey) {
this.publicKey = publicKey;
}

public void setOtp(String otp) {
this.otp = otp;
public void setOtpHash(String otpHash) {
this.otpHash = otpHash;
}
public void setOtpInput(String otpInput) {
this.otpInput = otpInput;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ public interface OtpServiceApi {
String generateOtp();
String sendOtp(String phoneNumber, String publicKey);
String computeHash(String otp, String phoneNumber, String publicKey, String salt);
boolean validateOtp(String phoneNumber, String otp);
boolean validateOtp(String phoneNumber, String publicKey, String otpInput , String otpHash);
}
20 changes: 20 additions & 0 deletions prs/prs-service-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,32 @@
<version>0.0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>


<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.15.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>

<!--- twillor -->
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio</artifactId>
<version>10.6.4</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@
import java.util.Base64;
import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import jakarta.annotation.PostConstruct;
import com.twilio.type.PhoneNumber;


@Service
public class OtpServiceImpl implements OtpServiceApi {
private static final Logger logger = LoggerFactory.getLogger(OtpServiceImpl.class);

// Twilio credentials
@Value("${twilio.account.sid}")
Expand All @@ -40,7 +37,6 @@ public void initTwilio() {
Twilio.init(accountSid, authToken); // Initialize Twilio once
}


@Override
public String generateOtp() {
SecureRandom secureRandom = new SecureRandom();
Expand All @@ -51,35 +47,35 @@ public String generateOtp() {
@Override
public String sendOtp(String phoneNumber, String publicKey) {
if (phoneNumber == null || !phoneNumber.matches("\\+?[1-9]\\d{1,14}")) {
logger.error("Invalid phone number format.");
throw new IllegalArgumentException("Invalid phone number format");
}

try {

String otp = generateOtp();
String otpHash = computeHash(otp, phoneNumber,publicKey, salt);
String otpHash = computeHash(otp, phoneNumber, publicKey, salt);

// Send OTP via Twilio
Message message = Message.creator(
Message.creator(
new PhoneNumber(phoneNumber),
new PhoneNumber(fromPhoneNumber),
"Your OTP is: " + otp
).create();

logger.info("OTP sent successfully to {}. SID: {}", phoneNumber, message.getSid());

return otpHash;

} catch (Exception e) {
logger.error("Failed to send OTP: {}", e.getMessage());
throw new FailedToSendOTPException("Failed to send OTP");
}
}

@Override
public boolean validateOtp(String phoneNumber, String otp) {
return false;
public boolean validateOtp(String phoneNumber, String publicKey, String otpInput, String otpHash) {
try {
String newOtpHash = computeHash(otpInput, phoneNumber, publicKey, salt);
return newOtpHash.equals(otpHash);
} catch (Exception e) {
return false;
}
}

@Override
Expand All @@ -97,4 +93,4 @@ public String computeHash(String otp, String phoneNumber, String publicKey, Stri
throw new HashComputationException("Error computing hash");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
package com.adorsys.webank.serviceimpl;
import static org.junit.jupiter.api.Assertions.*;

import com.adorsys.webank.exceptions.HashComputationException;
import org.junit.jupiter.api.Test;
import com.twilio.*;
import com.twilio.rest.api.v2010.account.*;
import com.twilio.type.*;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.extension.*;
import org.mockito.*;
import org.mockito.junit.jupiter.*;
import org.springframework.test.util.*;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.nio.charset.*;
import java.security.*;
import java.util.*;

public class OtpServiceTest {
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;

@ExtendWith(MockitoExtension.class)
public class OtpServiceTest {

OtpServiceImpl otpServiceImpl = new OtpServiceImpl();

@InjectMocks
private OtpServiceImpl otpService;

@Mock
private Message message;

@Test
void generateFourDigitOtp() {
Expand All @@ -25,19 +37,67 @@ void generateFourDigitOtp() {
assert Integer.parseInt(otp) >= 1000 && Integer.parseInt(otp) <= 9999 : "Otp should be between 1000 and 9999";
}

@BeforeEach
void setup() {
// Inject test values for Twilio credentials
ReflectionTestUtils.setField(otpService, "accountSid", "testAccountSid");
ReflectionTestUtils.setField(otpService, "authToken", "testAuthToken");
ReflectionTestUtils.setField(otpService, "fromPhoneNumber", "+1234567890");
ReflectionTestUtils.setField(otpService, "salt", "testSalt");
Twilio.init("testAccountSid", "testAuthToken");
}

@Test
void testTwilioConnection() {
assertDoesNotThrow(() -> Twilio.init("testAccountSid", "testAuthToken"));
}

@Test
void testSendOtpSuccessfully() {
// Mock the MessageCreator and Message
Message mockMessage = Mockito.mock(Message.class);
MessageCreator mockMessageCreator = Mockito.mock(MessageCreator.class);

// Mock the behavior of create() to return a mock Message
Mockito.when(mockMessageCreator.create()).thenReturn(mockMessage);

// Mock the behavior of Message.creator to return the mock MessageCreator
Mockito.mockStatic(Message.class).when(() ->
Message.creator(any(PhoneNumber.class), any(PhoneNumber.class), any(String.class))
).thenReturn(mockMessageCreator);

String phoneNumber = "+1234567890";
String publicKey = "testPublicKey";

// Call the method
String otpHash = otpService.sendOtp(phoneNumber, publicKey);

// Assert the OTP hash is not null
assertNotNull(otpHash, "OTP hash should not be null");

// Verify interactions with the mock MessageCreator
Mockito.verify(mockMessageCreator, Mockito.times(1)).create();

// Update the assertion to check Base64 format
assertTrue(otpHash.matches("[a-zA-Z0-9+/=]+"), "OTP hash should be a valid Base64 string");
}

@Test
void testComputeHashWithValidInputs() throws NoSuchAlgorithmException {
String otp = "1234";
String phoneNumber = "+237654066316";
String publicKey = "public-key-123";
String salt = "unique-salt";

// Expected hash computation
String input = otp + phoneNumber + publicKey + salt;
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
String expectedHash = Base64.getEncoder().encodeToString(hashBytes);

// Compute hash using the method
String actualHash = otpServiceImpl.computeHash(otp, phoneNumber, publicKey, salt);

assertNotNull(actualHash, "Hash should not be null");
assertEquals(expectedHash, actualHash, "Hashes should match");
}
Expand All @@ -48,10 +108,10 @@ void testComputeHashWithEmptyInputs() {
String phoneNumber = "";
String publicKey = "";
String salt = "";

String actualHash = otpServiceImpl.computeHash(otp, phoneNumber, publicKey, salt);

assertNotNull(actualHash, "Hash should not be null");
assertFalse(actualHash.isEmpty(), "Hash should not be empty");
}


}

0 comments on commit e22cbb2

Please sign in to comment.