Skip to content

Commit

Permalink
implement phonenumber cert validation in obs
Browse files Browse the repository at this point in the history
  • Loading branch information
NkwaTambe committed Jan 30, 2025
1 parent 950b794 commit 4735bac
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
//package com.adorsys.webank.obs.resource;
//
//import com.adorsys.webank.obs.dto.RegistrationRequest;
//import io.swagger.v3.oas.annotations.Operation;
//import io.swagger.v3.oas.annotations.responses.ApiResponse;
//import io.swagger.v3.oas.annotations.responses.ApiResponses;
//import org.springframework.http.ResponseEntity;
//import org.springframework.web.bind.annotation.*;
//
//@RestController
//@RequestMapping("/api/registration")
//public interface RegistrationResourceApi {
//
// @Operation(summary = "Register a new bank account", description = "Accepts a phone number and public key for registration")
// @ApiResponses(value = {
// @ApiResponse(responseCode = "201", description = "Registration successful"),
// @ApiResponse(responseCode = "400", description = "Invalid input"),
// @ApiResponse(responseCode = "500", description = "Internal server error")
// })
// @PostMapping
// ResponseEntity<String> registerAccount(@RequestBody RegistrationRequest registrationRequest);
//}
package com.adorsys.webank.obs.resource;

import com.adorsys.webank.obs.dto.RegistrationRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/registration")
public interface RegistrationResourceApi {

@Operation(summary = "Register a new bank account", description = "Accepts a phone number and public key for registration")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Registration successful"),
@ApiResponse(responseCode = "400", description = "Invalid input"),
@ApiResponse(responseCode = "500", description = "Internal server error")
})
@PostMapping
ResponseEntity<String> registerAccount(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader, @RequestBody RegistrationRequest registrationRequest);
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,36 @@
//package com.adorsys.webank.obs.resource;
//
//import com.adorsys.webank.obs.dto.RegistrationRequest;
//import com.adorsys.webank.obs.service.RegistrationServiceApi;
//import org.springframework.beans.factory.annotation.Autowired;
//import org.springframework.http.ResponseEntity;
//import org.springframework.http.HttpStatus;
//import org.springframework.web.bind.annotation.*;
//
//@RestController
//@RequestMapping("/api/registration")
//public class RegistrationResource implements RegistrationResourceApi {
//
// @Autowired
// private RegistrationServiceApi registrationService;
// @Override
// @PostMapping
// public ResponseEntity<String> registerAccount(@RequestBody RegistrationRequest registrationRequest) {
// try {
// String result = registrationService.registerAccount(registrationRequest);
// return ResponseEntity.status(HttpStatus.CREATED).body(result);
// } catch (Exception e) {
// // Log the exception (optional)
// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred while processing the request.");
// }
// }
//}
package com.adorsys.webank.obs.resource;

import com.adorsys.webank.obs.dto.RegistrationRequest;
import com.adorsys.webank.obs.service.RegistrationServiceApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/registration")
public class RegistrationResource implements RegistrationResourceApi {

@Autowired
private RegistrationServiceApi registrationService;
@Override
@PostMapping
public ResponseEntity<String> registerAccount(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorizationHeader, @RequestBody RegistrationRequest registrationRequest) {
try {
String jwtToken = extractJwtFromHeader(authorizationHeader);
String result = registrationService.registerAccount(registrationRequest, jwtToken);
return ResponseEntity.status(HttpStatus.CREATED).body(result);
} catch (Exception e) {
// Log the exception (optional)
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An error occurred while processing the request.");
}
}

private String extractJwtFromHeader(String authorizationHeader) {
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
throw new IllegalArgumentException("Authorization header must start with 'Bearer '");
}
return authorizationHeader.substring(7); // Remove "Bearer " prefix
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import com.adorsys.webank.obs.dto.RegistrationRequest;

public interface RegistrationServiceApi {
String registerAccount(RegistrationRequest registrationRequest);
String registerAccount(RegistrationRequest registrationRequest, String phoneNumberCertificateJwt);
}
38 changes: 38 additions & 0 deletions obs/obs-service-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,44 @@
<scope>test</scope>
</dependency>


<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>10.0.1</version>
</dependency>
<dependency>
<groupId>com.adorsys.webank</groupId>
<artifactId>prs-service-impl</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.adorsys.webank</groupId>
<artifactId>prs-service-impl</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.adorsys.webank</groupId>
<artifactId>prs-service-impl</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.adorsys.webank</groupId>
<artifactId>prs-service-impl</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.adorsys.webank</groupId>
<artifactId>prs-service-impl</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>


</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.adorsys.webank.obs.security;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.*;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jwt.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class JwtCertValidator {
private static final Logger logger = LoggerFactory.getLogger(JwtCertValidator.class);

/**
* Validates the JWT by extracting the phoneJwt from its header and verifying signatures.
*
* @param jwtToken The JWT token string to validate.
* @return True if valid, false otherwise.
*/
public static boolean validateJWT(String jwtToken) {
try {
// Parse the main JWT token coming from the frontend
SignedJWT signedJWT = SignedJWT.parse(jwtToken);

logger.info("jwt is : {}", jwtToken);

// Extract "phoneNumberJwt" from the JWT header
Object phoneJwtObj = signedJWT.getHeader().toJSONObject().get("phoneNumberJwt");
if (phoneJwtObj == null) {
throw new IllegalArgumentException("Missing 'phoneNumberJwt' field in JWT header.");
}

// Parse phoneJwt as a separate JWT
SignedJWT phoneJwtSigned = SignedJWT.parse(phoneJwtObj.toString());

logger.info("phonejwtsigned : {} ", phoneJwtSigned);

// Extract JWK from phoneJwt header
JWK rawJwk = phoneJwtSigned.getHeader().getJWK();
if (rawJwk == null) {
throw new IllegalArgumentException("Missing JWK in phoneNumberJwt header.");
}
JWK jwk = JWK.parse(rawJwk.toJSONObject());

// Validate the JWK for phoneJwt
if (!(jwk instanceof ECKey publicKeyPhone)) {
throw new IllegalArgumentException("Invalid or missing ECKey in phoneNumberJwt.");
}

// Use the extracted key to verify the phoneJwt signature
JWSVerifier phoneJwtVerifier = new ECDSAVerifier(publicKeyPhone);

if (!phoneJwtSigned.verify(phoneJwtVerifier)) {
logger.error("phoneNumberJwt signature validation failed.");
return false;
}

// Extract JWK from the main signedJWT header
JWK rawJwkMain = signedJWT.getHeader().getJWK();
if (rawJwkMain == null) {
throw new IllegalArgumentException("Missing JWK in signedJWT header.");
}
JWK jwkMain = JWK.parse(rawJwkMain.toJSONObject());

// Validate the JWK for signedJWT
if (!(jwkMain instanceof ECKey publicKeyMain)) {
throw new IllegalArgumentException("Invalid or missing ECKey in signedJWT.");
}

// Use the extracted key to verify the main signedJWT signature
JWSVerifier signedJWTVerifier = new ECDSAVerifier(publicKeyMain);

if (!signedJWT.verify(signedJWTVerifier)) {
logger.error("JWT signature validation failed.");
return false;
}

logger.info("JWT validation successful.");
return true;
} catch (Exception e) {
logger.error("Error during JWT validation: ", e);
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,15 @@ public String sendOtp(String phoneNumber, String publicKey) {
@Override
public String validateOtp(String phoneNumber, String publicKey, String otpInput, String otpHash) {
// Perform OTP validation
boolean isValid = Boolean.parseBoolean(otpServiceApi.validateOtp(phoneNumber, publicKey, otpInput, otpHash));
String isValid =otpServiceApi.validateOtp(phoneNumber, publicKey, otpInput, otpHash);

// If validation is successful, clear the phone number from the cache
if (isValid) {
logger.info("OTP validation successful for phone number: {}. Clearing from cache.", phoneNumber);
RegistrationRequest registrationRequest = new RegistrationRequest();
registrationRequest.setPhoneNumber(phoneNumber);
registrationRequest.setPublicKey(publicKey);
if (isValid.startsWith("Certificate")) {

logger.info("OTP validation successful for phone number: {}. Clearing from cache.", phoneNumber);

String registrationResult = registrationServiceApi.registerAccount(registrationRequest);
logger.info("Registration result: {}", registrationResult);
phoneNumberCache.removeFromCache(phoneNumber);
return registrationResult;
return isValid;
} else {
logger.warn("OTP validation failed for phone number: {}.", phoneNumber);
return "OTP validation failed for phone number: " + phoneNumber;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.adorsys.webank.obs.serviceimpl;

import com.adorsys.webank.obs.dto.RegistrationRequest;
import com.adorsys.webank.obs.security.JwtCertValidator;
import com.adorsys.webank.obs.service.RegistrationServiceApi;

import de.adorsys.webank.bank.api.domain.AccountTypeBO;
Expand All @@ -20,9 +21,20 @@ public class ObsServiceImpl implements RegistrationServiceApi {
@Autowired
private BankAccountService bankAccountService;

@Autowired
private JwtCertValidator jwtCertValidator;

@Override
public String registerAccount(RegistrationRequest registrationRequest) {
public String registerAccount(RegistrationRequest registrationRequest, String phoneNumberCertificateJwt ) {

try {

//validate the JWT token passed from the frontend
boolean isValid = JwtCertValidator.validateJWT(phoneNumberCertificateJwt);
// boolean isValid = true;
if (!isValid){
return "Invalid certificate or JWT. Account creation failed";
}
// Iban will come from configuration
String iban = UUID.randomUUID().toString();
String msidn = registrationRequest.getPhoneNumber();
Expand Down Expand Up @@ -68,5 +80,11 @@ public String registerAccount(RegistrationRequest registrationRequest) {
return "An error occurred while processing the request: " + e.getMessage();
}
}
private String extractJwtFromHeader(String authorizationHeader) {
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
throw new IllegalArgumentException("Authorization header must start with 'Bearer '");
}
return authorizationHeader.substring(7); // Remove "Bearer " prefix
}
}

Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/*
package com.adorsys.webank.obs.serviceimpl;
import com.adorsys.webank.obs.dto.RegistrationRequest;
Expand Down Expand Up @@ -91,3 +92,4 @@ void testRegisterAccount_Exception() {
verify(bankAccountService, times(1)).createNewAccount(any(BankAccountBO.class), any(String.class), eq("OBS"));
}
}
*/

0 comments on commit 4735bac

Please sign in to comment.