Skip to content

Commit

Permalink
Fix #296: Add timestamps for process, identity and OTP (#318)
Browse files Browse the repository at this point in the history
  • Loading branch information
romanstrobl authored Aug 29, 2022
1 parent c8789b6 commit 3fb0829
Show file tree
Hide file tree
Showing 25 changed files with 125 additions and 36 deletions.
6 changes: 5 additions & 1 deletion docs/sql/mysql/onboarding/create-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ CREATE TABLE es_onboarding_process (
error_score INTEGER NOT NULL DEFAULT 0,
timestamp_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
timestamp_last_updated DATETIME,
timestamp_finished DATETIME
timestamp_finished DATETIME,
timestamp_failed DATETIME
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE INDEX onboarding_process_status ON es_onboarding_process (status);
Expand All @@ -48,6 +49,7 @@ CREATE TABLE es_onboarding_otp (
timestamp_expiration DATETIME NOT NULL,
timestamp_last_updated DATETIME,
timestamp_verified DATETIME,
timestamp_failed DATETIME,
FOREIGN KEY (process_id) REFERENCES es_onboarding_process (id)
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Expand All @@ -70,6 +72,8 @@ CREATE TABLE es_identity_verification (
session_info TEXT,
timestamp_created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
timestamp_last_updated DATETIME,
timestamp_finished DATETIME,
timestamp_failed DATETIME,
FOREIGN KEY (process_id) REFERENCES es_onboarding_process (id)
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

Expand Down
6 changes: 5 additions & 1 deletion docs/sql/oracle/onboarding/create-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ CREATE TABLE ES_ONBOARDING_PROCESS (
ERROR_SCORE INTEGER DEFAULT 0 NOT NULL,
TIMESTAMP_CREATED TIMESTAMP(6) NOT NULL,
TIMESTAMP_LAST_UPDATED TIMESTAMP(6),
TIMESTAMP_FINISHED TIMESTAMP(6)
TIMESTAMP_FINISHED TIMESTAMP(6),
TIMESTAMP_FAILED TIMESTAMP(6)
);

CREATE INDEX ONBOARDING_PROCESS_STATUS ON ES_ONBOARDING_PROCESS (STATUS);
Expand All @@ -50,6 +51,7 @@ CREATE TABLE ES_ONBOARDING_OTP (
TIMESTAMP_EXPIRATION TIMESTAMP(6) NOT NULL,
TIMESTAMP_LAST_UPDATED TIMESTAMP(6),
TIMESTAMP_VERIFIED TIMESTAMP(6),
TIMESTAMP_FAILED TIMESTAMP(6),
FOREIGN KEY (PROCESS_ID) REFERENCES ES_ONBOARDING_PROCESS (ID)
);

Expand All @@ -73,6 +75,8 @@ CREATE TABLE ES_IDENTITY_VERIFICATION (
SESSION_INFO CLOB,
TIMESTAMP_CREATED TIMESTAMP(6) NOT NULL,
TIMESTAMP_LAST_UPDATED TIMESTAMP(6),
TIMESTAMP_FINISHED TIMESTAMP(6),
TIMESTAMP_FAILED TIMESTAMP(6),
FOREIGN KEY (PROCESS_ID) REFERENCES ES_ONBOARDING_PROCESS (ID)
);

Expand Down
6 changes: 5 additions & 1 deletion docs/sql/postgresql/onboarding/create-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ CREATE TABLE es_onboarding_process (
error_score INTEGER NOT NULL DEFAULT 0,
timestamp_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
timestamp_last_updated TIMESTAMP,
timestamp_finished TIMESTAMP
timestamp_finished TIMESTAMP,
timestamp_failed TIMESTAMP
);

CREATE INDEX onboarding_process_status ON es_onboarding_process (status);
Expand All @@ -54,6 +55,7 @@ CREATE TABLE es_onboarding_otp (
timestamp_expiration TIMESTAMP NOT NULL,
timestamp_last_updated TIMESTAMP,
timestamp_verified TIMESTAMP,
timestamp_failed TIMESTAMP,
FOREIGN KEY (process_id) REFERENCES es_onboarding_process (id)
);

Expand All @@ -77,6 +79,8 @@ CREATE TABLE es_identity_verification (
session_info TEXT,
timestamp_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
timestamp_last_updated TIMESTAMP,
timestamp_finished TIMESTAMP,
timestamp_failed TIMESTAMP,
FOREIGN KEY (process_id) REFERENCES es_onboarding_process (id)
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.wultra.app.enrollmentserver.api.model.onboarding.response.OtpVerifyResponse;
import com.wultra.app.enrollmentserver.model.enumeration.OtpType;
import com.wultra.app.enrollmentserver.model.integration.OwnerId;
import com.wultra.app.onboardingserver.common.api.OtpService;
import com.wultra.app.onboardingserver.common.errorhandling.OnboardingProcessException;

Expand All @@ -42,12 +43,13 @@ public ActivationOtpService(OtpService otpService) {
/**
* Verify an OTP code during activation.
* @param processId Onboarding process identifier.
* @param ownerId Owner identification.
* @param otpCode OTP code.
* @throws OnboardingProcessException Thrown when onboarding process or OTP code is not found.
* @return OTP verification response.
*/
public OtpVerifyResponse verifyOtpCode(String processId, String otpCode) throws OnboardingProcessException {
return otpService.verifyOtpCode(processId, otpCode, OtpType.ACTIVATION);
public OtpVerifyResponse verifyOtpCode(String processId, OwnerId ownerId, String otpCode) throws OnboardingProcessException {
return otpService.verifyOtpCode(processId, ownerId, otpCode, OtpType.ACTIVATION);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.wultra.app.enrollmentserver.api.model.onboarding.response.OtpVerifyResponse;
import com.wultra.app.enrollmentserver.model.enumeration.OtpType;
import com.wultra.app.enrollmentserver.model.integration.OwnerId;
import com.wultra.app.onboardingserver.common.annotation.PublicApi;
import com.wultra.app.onboardingserver.common.errorhandling.OnboardingProcessException;

Expand All @@ -34,10 +35,11 @@ public interface OtpService {
* Verify an OTP code.
*
* @param processId Process identifier.
* @param ownerId Owner identification.
* @param otpCode OTP code sent by the user.
* @param otpType OTP type.
* @return Verify OTP code response.
* @throws OnboardingProcessException Thrown when process or OTP code is not found.
*/
OtpVerifyResponse verifyOtpCode(String processId, String otpCode, OtpType otpType) throws OnboardingProcessException;
OtpVerifyResponse verifyOtpCode(String processId, OwnerId ownerId, String otpCode, OtpType otpType) throws OnboardingProcessException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ public interface OnboardingOtpRepository extends CrudRepository<OnboardingOtpEnt
@Query("UPDATE OnboardingOtpEntity o SET " +
"o.status = com.wultra.app.enrollmentserver.model.enumeration.OtpStatus.FAILED, " +
"o.timestampLastUpdated = CURRENT_TIMESTAMP, " +
"o.errorDetail = '" + OnboardingOtpEntity.ERROR_EXPIRED + "' " +
"o.errorDetail = '" + OnboardingOtpEntity.ERROR_EXPIRED + "', " +
"o.errorOrigin = 'OTP_VERIFICATION', " +
"o.timestampFailed = :timestampExpired " +
"WHERE o.status = com.wultra.app.enrollmentserver.model.enumeration.OtpStatus.ACTIVE " +
"AND o.timestampCreated < :dateCreatedBefore")
void terminateExpiredOtps(Date dateCreatedBefore);
void terminateExpiredOtps(Date dateCreatedBefore, Date timestampExpired);

@Query("SELECT SUM(o.failedAttempts) FROM OnboardingOtpEntity o WHERE o.process.id = :processId AND o.type = :type")
int getFailedAttemptsByProcess(String processId, OtpType type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.wultra.app.onboardingserver.common.database;

import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin;
import com.wultra.app.onboardingserver.common.database.entity.OnboardingProcessEntity;
import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus;
import org.springframework.data.jpa.repository.Modifying;
Expand Down Expand Up @@ -55,20 +56,24 @@ public interface OnboardingProcessRepository extends CrudRepository<OnboardingPr
@Modifying
@Query("UPDATE OnboardingProcessEntity p SET " +
"p.status = com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED, " +
"p.timestampLastUpdated = CURRENT_TIMESTAMP, " +
"p.errorDetail = :errorDetail " +
"p.timestampLastUpdated = :timestampExpired, " +
"p.timestampFailed = :timestampExpired, " +
"p.errorDetail = :errorDetail, " +
"p.errorOrigin = :errorOrigin " +
"WHERE p.status = :status " +
"AND p.timestampCreated < :dateCreatedBefore")
void terminateExpiredProcessesByStatus(Date dateCreatedBefore, OnboardingStatus status, String errorDetail);
void terminateExpiredProcessesByStatus(Date dateCreatedBefore, Date timestampExpired, OnboardingStatus status, String errorDetail, ErrorOrigin errorOrigin);

@Modifying
@Query("UPDATE OnboardingProcessEntity p SET " +
"p.status = com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED, " +
"p.timestampLastUpdated = CURRENT_TIMESTAMP, " +
"p.errorDetail = :errorDetail " +
"p.timestampLastUpdated = :timestampExpired, " +
"p.timestampFailed = :timestampExpired, " +
"p.errorDetail = :errorDetail, " +
"p.errorOrigin = :errorOrigin " +
"WHERE p.status <> com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FINISHED " +
"AND p.status <> com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus.FAILED " +
"AND p.timestampCreated < :dateCreatedBefore")
void terminateExpiredProcesses(Date dateCreatedBefore, String errorDetail);
void terminateExpiredProcesses(Date dateCreatedBefore, Date timestampExpired, String errorDetail, ErrorOrigin errorOrigin);

}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public class OnboardingOtpEntity implements Serializable {
@Column(name = "timestamp_verified")
private Date timestampVerified;

@Column(name = "timestamp_failed")
private Date timestampFailed;

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ public class OnboardingProcessEntity implements Serializable {
@Column(name = "timestamp_finished")
private Date timestampFinished;

@Column(name = "timestamp_failed")
private Date timestampFailed;

@OneToMany(mappedBy = "process", cascade = CascadeType.ALL)
@OrderBy("timestampCreated")
@ToString.Exclude
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.wultra.app.enrollmentserver.model.enumeration.OnboardingStatus;
import com.wultra.app.enrollmentserver.model.enumeration.OtpStatus;
import com.wultra.app.enrollmentserver.model.enumeration.OtpType;
import com.wultra.app.enrollmentserver.model.integration.OwnerId;
import com.wultra.app.onboardingserver.common.api.OtpService;
import com.wultra.app.onboardingserver.common.configuration.CommonOnboardingConfig;
import com.wultra.app.onboardingserver.common.database.OnboardingOtpRepository;
Expand Down Expand Up @@ -70,7 +71,7 @@ public CommonOtpService(
}

@Override
public OtpVerifyResponse verifyOtpCode(String processId, String otpCode, OtpType otpType) throws OnboardingProcessException {
public OtpVerifyResponse verifyOtpCode(String processId, OwnerId ownerId, String otpCode, OtpType otpType) throws OnboardingProcessException {
Optional<OnboardingProcessEntity> processOptional = onboardingProcessRepository.findById(processId);
if (processOptional.isEmpty()) {
logger.warn("Onboarding process not found: {}", processId);
Expand All @@ -86,7 +87,7 @@ public OtpVerifyResponse verifyOtpCode(String processId, String otpCode, OtpType
OnboardingOtpEntity otp = otpOptional.get();

// Verify OTP code
Date now = new Date();
final Date now = ownerId.getTimestamp();
boolean expired = false;
boolean verified = false;
int failedAttempts = onboardingOtpRepository.getFailedAttemptsByProcess(processId, otpType);
Expand All @@ -103,6 +104,7 @@ public OtpVerifyResponse verifyOtpCode(String processId, String otpCode, OtpType
otp.setErrorDetail(OnboardingOtpEntity.ERROR_EXPIRED);
otp.setErrorOrigin(ErrorOrigin.OTP_VERIFICATION);
otp.setTimestampLastUpdated(now);
otp.setTimestampFailed(now);
onboardingOtpRepository.save(otp);
} else if (otp.getOtpCode().equals(otpCode)) {
verified = true;
Expand All @@ -111,7 +113,7 @@ public OtpVerifyResponse verifyOtpCode(String processId, String otpCode, OtpType
otp.setTimestampLastUpdated(now);
onboardingOtpRepository.save(otp);
} else {
handleFailedOtpVerification(process, otp, otpType);
handleFailedOtpVerification(process, ownerId, otp, otpType);
}

OtpVerifyResponse response = new OtpVerifyResponse();
Expand All @@ -126,21 +128,22 @@ public OtpVerifyResponse verifyOtpCode(String processId, String otpCode, OtpType
/**
* Handle failed OTP verification.
* @param process Onboarding process entity.
* @param ownerId Owner identification.
* @param otp OTP entity.
* @param otpType OTP type.
*/
private void handleFailedOtpVerification(OnboardingProcessEntity process, OnboardingOtpEntity otp, OtpType otpType) {
final Date now = new Date();
private void handleFailedOtpVerification(OnboardingProcessEntity process, OwnerId ownerId, OnboardingOtpEntity otp, OtpType otpType) {
int failedAttempts = onboardingOtpRepository.getFailedAttemptsByProcess(process.getId(), otpType);
final int maxFailedAttempts = commonOnboardingConfig.getOtpMaxFailedAttempts();
otp.setFailedAttempts(otp.getFailedAttempts() + 1);
otp.setTimestampLastUpdated(now);
otp.setTimestampLastUpdated(ownerId.getTimestamp());
otp = onboardingOtpRepository.save(otp);
failedAttempts++;
if (failedAttempts >= maxFailedAttempts) {
otp.setStatus(OtpStatus.FAILED);
otp.setErrorDetail(OnboardingOtpEntity.ERROR_MAX_FAILED_ATTEMPTS);
otp.setErrorOrigin(ErrorOrigin.OTP_VERIFICATION);
otp.setTimestampFailed(ownerId.getTimestamp());
onboardingOtpRepository.save(otp);

// Onboarding process is failed, update it
Expand All @@ -160,6 +163,8 @@ private void handleFailedOtpVerification(OnboardingProcessEntity process, Onboar
otp.setStatus(OtpStatus.FAILED);
otp.setErrorDetail(process.getErrorDetail());
otp.setErrorOrigin(ErrorOrigin.OTP_VERIFICATION);
otp.setTimestampLastUpdated(ownerId.getTimestamp());
otp.setTimestampFailed(ownerId.getTimestamp());
onboardingOtpRepository.save(otp);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ public OnboardingProcessEntity failProcess(OnboardingProcessEntity entity, Strin
return entity;
}
entity.setStatus(OnboardingStatus.FAILED);
entity.setTimestampLastUpdated(new Date());
entity.setErrorDetail(errorDetail);
entity.setErrorOrigin(errorOrigin);
final Date now = new Date();
entity.setTimestampLastUpdated(now);
entity.setTimestampFailed(now);
return onboardingProcessRepository.save(entity);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ public ObjectResponse<OtpVerifyResponse> verifyOtp(@EncryptedRequestBody ObjectR
onboardingService.verifyProcessId(ownerId, processId);

final String otpCode = request.getRequestObject().getOtpCode();
return new ObjectResponse<>(identityVerificationOtpService.verifyOtpCode(processId, otpCode));
return new ObjectResponse<>(identityVerificationOtpService.verifyOtpCode(processId, ownerId, otpCode));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.wultra.app.onboardingserver.database;

import com.wultra.app.enrollmentserver.model.enumeration.ErrorOrigin;
import com.wultra.app.onboardingserver.database.entity.DocumentVerificationEntity;
import com.wultra.app.onboardingserver.database.entity.IdentityVerificationEntity;
import com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus;
Expand Down Expand Up @@ -51,10 +52,11 @@ public interface DocumentVerificationRepository extends JpaRepository<DocumentVe
"SET d.status = com.wultra.app.enrollmentserver.model.enumeration.DocumentStatus.FAILED, " +
" d.usedForVerification = false, " +
" d.errorDetail = :errorMessage, " +
" d.errorOrigin = :errorOrigin, " +
" d.timestampLastUpdated = :timestamp " +
"WHERE d.timestampLastUpdated < :cleanupDate " +
"AND d.status IN (:statuses)")
int failExpiredVerifications(Date cleanupDate, Date timestamp, String errorMessage, List<DocumentStatus> statuses);
int failExpiredVerifications(Date cleanupDate, Date timestamp, String errorMessage, ErrorOrigin errorOrigin, List<DocumentStatus> statuses);

@Modifying
@Query("UPDATE DocumentVerificationEntity d " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ public class IdentityVerificationEntity implements Serializable {
@Column(name = "timestamp_last_updated")
private Date timestampLastUpdated;

@Column(name = "timestamp_finished")
private Date timestampFinished;

@Column(name = "timestamp_failed")
private Date timestampFailed;

@OneToMany(mappedBy = "identityVerification", cascade = CascadeType.ALL)
@OrderBy("timestampCreated")
private Set<DocumentVerificationEntity> documentVerifications = new LinkedHashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import reactor.util.retry.Retry;

import java.time.Duration;
import java.util.Date;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Stream;
Expand Down Expand Up @@ -128,6 +129,9 @@ private static String getVerificationId(final IdentityVerificationEntity identit

private Consumer<EvaluateClientResponse> createSuccessConsumer(final IdentityVerificationEntity identityVerification) {
return response -> {
final Date now = new Date();
identityVerification.setTimestampLastUpdated(now);
identityVerification.setTimestampFinished(now);
if (response.isSuccessful()) {
logger.info("Client evaluation successful for {}", identityVerification);
identityVerification.setStatus(IdentityVerificationStatus.ACCEPTED);
Expand All @@ -148,6 +152,9 @@ private Consumer<Throwable> createErrorConsumer(final IdentityVerificationEntity
identityVerification.setStatus(IdentityVerificationStatus.FAILED);
identityVerification.setErrorDetail(IdentityVerificationEntity.ERROR_MAX_FAILED_ATTEMPTS_CLIENT_EVALUATION);
identityVerification.setErrorOrigin(ErrorOrigin.PROCESS_LIMIT_CHECK);
final Date now = new Date();
identityVerification.setTimestampLastUpdated(now);
identityVerification.setTimestampFailed(now);
saveInTransaction(identityVerification);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ public void finishIdentityVerification(OwnerId ownerId) throws RemoteCommunicati
// Terminate onboarding process
final OnboardingProcessEntity processEntity = onboardingService.findExistingProcessWithVerificationInProgress(ownerId.getActivationId());
processEntity.setStatus(OnboardingStatus.FINISHED);
processEntity.setTimestampFinished(new Date());
final Date now = ownerId.getTimestamp();
processEntity.setTimestampLastUpdated(now);
processEntity.setTimestampFinished(now);
onboardingService.updateProcess(processEntity);
}

Expand Down
Loading

0 comments on commit 3fb0829

Please sign in to comment.