diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/ActivationCodeController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/ActivationCodeController.java
new file mode 100644
index 000000000..af1b34775
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/ActivationCodeController.java
@@ -0,0 +1,98 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.controller.api;
+
+import com.wultra.app.enrollmentserver.errorhandling.ActivationCodeException;
+import com.wultra.app.enrollmentserver.errorhandling.InvalidRequestObjectException;
+import com.wultra.app.enrollmentserver.impl.service.ActivationCodeService;
+import com.wultra.app.enrollmentserver.model.request.ActivationCodeRequest;
+import com.wultra.app.enrollmentserver.model.response.ActivationCodeResponse;
+import io.getlime.core.rest.model.base.request.ObjectRequest;
+import io.getlime.core.rest.model.base.response.ObjectResponse;
+import io.getlime.security.powerauth.crypto.lib.encryptor.ecies.model.EciesScope;
+import io.getlime.security.powerauth.crypto.lib.enums.PowerAuthSignatureTypes;
+import io.getlime.security.powerauth.rest.api.base.authentication.PowerAuthApiAuthentication;
+import io.getlime.security.powerauth.rest.api.base.exception.PowerAuthAuthenticationException;
+import io.getlime.security.powerauth.rest.api.spring.annotation.EncryptedRequestBody;
+import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuth;
+import io.getlime.security.powerauth.rest.api.spring.annotation.PowerAuthEncryption;
+import io.swagger.v3.oas.annotations.Parameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Controller publishing REST services for obtaining a new activation code.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+@ConditionalOnProperty(
+ value = "enrollment-server.activation-spawn.enabled",
+ havingValue = "true"
+)
+@RestController
+@RequestMapping(value = "api/activation")
+public class ActivationCodeController {
+
+ private static final Logger logger = LoggerFactory.getLogger(ActivationCodeController.class);
+
+ private final ActivationCodeService activationCodeService;
+
+ /**
+ * Default autowiring constructor.
+ *
+ * @param activationCodeService Activation code service.
+ */
+ @Autowired
+ public ActivationCodeController(ActivationCodeService activationCodeService) {
+ this.activationCodeService = activationCodeService;
+ }
+
+ /**
+ * Controller request handler for requesting the activation code.
+ *
+ * @param request Request with activation OTP.
+ * @param apiAuthentication Authentication object with user and app details.
+ * @return New activation code, activation code signature and activation ID.
+ * @throws PowerAuthAuthenticationException In case user authentication fails.
+ * @throws InvalidRequestObjectException In case the object validation fails.
+ * @throws ActivationCodeException In case fetching the activation code fails.
+ */
+ @RequestMapping(value = "code", method = RequestMethod.POST)
+ @PowerAuthEncryption(scope = EciesScope.ACTIVATION_SCOPE)
+ @PowerAuth(resourceId = "api/activation/code", signatureType = {
+ PowerAuthSignatureTypes.POSSESSION_BIOMETRY,
+ PowerAuthSignatureTypes.POSSESSION_KNOWLEDGE
+ })
+ public ObjectResponse requestActivationCode(@EncryptedRequestBody ObjectRequest request, @Parameter(hidden = true) PowerAuthApiAuthentication apiAuthentication) throws PowerAuthAuthenticationException, InvalidRequestObjectException, ActivationCodeException {
+ // Check if the authentication object is present
+ if (apiAuthentication == null) {
+ logger.error("Unable to verify device registration when fetching activation code");
+ throw new PowerAuthAuthenticationException("Unable to verify device registration when fetching activation code");
+ }
+
+ // Request the activation code details.
+ final ActivationCodeResponse response = activationCodeService.requestActivationCode(request.getRequestObject(), apiAuthentication);
+ return new ObjectResponse<>(response);
+ }
+
+}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/PushRegistrationController.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/PushRegistrationController.java
index 53950e4e5..7fad41d03 100644
--- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/PushRegistrationController.java
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/controller/api/PushRegistrationController.java
@@ -111,9 +111,9 @@ private Response registerDeviceImpl(
// Check if the context is authenticated - if it is, add activation ID.
// This assures that the activation is assigned with a correct device.
- String userId = apiAuthentication.getUserId();
- String activationId = apiAuthentication.getActivationId();
- Long applicationId = apiAuthentication.getApplicationId();
+ final String userId = apiAuthentication.getUserId();
+ final String activationId = apiAuthentication.getActivationId();
+ final Long applicationId = apiAuthentication.getApplicationId();
return pushRegistrationService.registerDevice(request, userId, activationId, applicationId);
}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/ActivationCodeException.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/ActivationCodeException.java
new file mode 100644
index 000000000..958d64585
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/ActivationCodeException.java
@@ -0,0 +1,27 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.errorhandling;
+
+/**
+ * Exception thrown in case activation code cannot be fetched for any reason.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+public class ActivationCodeException extends Exception {
+ private static final long serialVersionUID = 368452747764248241L;
+}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java
index 8f5d0bf06..54eaafcbd 100644
--- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/errorhandling/DefaultExceptionHandler.java
@@ -122,4 +122,16 @@ public class DefaultExceptionHandler {
return new ErrorResponse(ex.getCode(), ex.getMessage());
}
+ /**
+ * Handling of activation code exceptions.
+ * @param ex Exception.
+ * @return Response with error details.
+ */
+ @ExceptionHandler(ActivationCodeException.class)
+ @ResponseStatus(HttpStatus.BAD_REQUEST)
+ public @ResponseBody ErrorResponse handleActivationCodeException(ActivationCodeException ex) {
+ logger.warn("Unable to fetch activation code", ex);
+ return new ErrorResponse("ACTIVATION_CODE_FAILED", "Unable to fetch activation code.");
+ }
+
}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/ActivationCodeDelegate.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/ActivationCodeDelegate.java
new file mode 100644
index 000000000..0f9bf037d
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/ActivationCodeDelegate.java
@@ -0,0 +1,70 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.impl.service;
+
+import java.util.List;
+
+/**
+ * Callback provider for lifecycle events of activation code.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+public interface ActivationCodeDelegate {
+
+ /**
+ * Fetch destination application ID value based in application ID. Check if the source app can
+ * activate the destination one.
+ *
+ * @param applicationId Application identifier for app lookup.
+ * @param sourceAppId Source application ID.
+ * @param activationFlags Activation flags.
+ * @param applicationRoles Application roles.
+ * @return Destination application ID.
+ */
+ Long destinationApplicationId(String applicationId, Long sourceAppId, List activationFlags, List applicationRoles);
+
+ /**
+ * Callback method to add new activation flags to activation.
+ *
+ * @param sourceActivationId Source activation ID (activation used to fetch the code).
+ * @param sourceActivationFlags Source activation flags (flags of the activation that initiated the transfer).
+ * @param userId User ID (user ID who requested the activation).
+ * @param sourceAppId Source app ID (the app that initiated the process).
+ * @param sourceApplicationRoles Source application roles (roles of the app that intiated the transfer).
+ * @param destinationAppId Destination app ID (the app that is to be activated).
+ * @param destinationActivationId Destination activation ID (the activation ID of the new activation).
+ * @param activationCode Activation code of the new activation.
+ * @param activationCodeSignature Activation code signature of the new activation code.
+ * @return List of new activation flags for the destination activation.
+ */
+ List addActivationFlags(String sourceActivationId, List sourceActivationFlags, String userId, Long sourceAppId, List sourceApplicationRoles, Long destinationAppId, String destinationActivationId, String activationCode, String activationCodeSignature);
+
+ /**
+ * Callback method with newly created activation code information.
+ *
+ * @param sourceActivationId Source activation ID (activation used to fetch the code).
+ * @param userId User ID (user ID who requested the activation).
+ * @param sourceAppId Source app ID (the app that initiated the process).
+ * @param destinationAppId Destination app ID (the app that is to be activated).
+ * @param destinationActivationId Destination activation ID (the activation ID of the new activation).
+ * @param activationCode Activation code of the new activation.
+ * @param activationCodeSignature Activation code signature of the new activation code.
+ */
+ void didReturnActivationCode(String sourceActivationId, String userId, Long sourceAppId, Long destinationAppId, String destinationActivationId, String activationCode, String activationCodeSignature);
+
+}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/ActivationCodeService.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/ActivationCodeService.java
new file mode 100644
index 000000000..c866c6d45
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/ActivationCodeService.java
@@ -0,0 +1,153 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.impl.service;
+
+import com.wultra.app.enrollmentserver.errorhandling.ActivationCodeException;
+import com.wultra.app.enrollmentserver.errorhandling.InvalidRequestObjectException;
+import com.wultra.app.enrollmentserver.impl.service.converter.ActivationCodeConverter;
+import com.wultra.app.enrollmentserver.model.request.ActivationCodeRequest;
+import com.wultra.app.enrollmentserver.model.response.ActivationCodeResponse;
+import com.wultra.app.enrollmentserver.model.validator.ActivationCodeRequestValidator;
+import com.wultra.security.powerauth.client.PowerAuthClient;
+import com.wultra.security.powerauth.client.model.error.PowerAuthClientException;
+import com.wultra.security.powerauth.client.v3.ActivationOtpValidation;
+import com.wultra.security.powerauth.client.v3.InitActivationResponse;
+import io.getlime.security.powerauth.rest.api.base.authentication.PowerAuthApiAuthentication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * Service responsible for fetching the new activation codes.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+@Service
+public class ActivationCodeService {
+
+ private static final Logger logger = LoggerFactory.getLogger(ActivationCodeService.class);
+
+ private final PowerAuthClient powerAuthClient;
+ private final ActivationCodeConverter activationCodeConverter;
+
+ private ActivationCodeDelegate activationCodeDelegate;
+
+ /**
+ * Autowiring constructor.
+ *
+ * @param powerAuthClient PowerAuth Client instance.
+ * @param activationCodeConverter Activation code converter class.
+ */
+ @Autowired
+ public ActivationCodeService(PowerAuthClient powerAuthClient, ActivationCodeConverter activationCodeConverter) {
+ this.powerAuthClient = powerAuthClient;
+ this.activationCodeConverter = activationCodeConverter;
+ }
+
+ /**
+ * Set activation code delegate via auto-wiring.
+ *
+ * @param activationCodeDelegate Activation code delegate bean.
+ */
+ @Autowired(required = false)
+ public void setActivationCodeDelegate(ActivationCodeDelegate activationCodeDelegate) {
+ this.activationCodeDelegate = activationCodeDelegate;
+ }
+
+ /**
+ * Request activation code for provided OTP value, user ID and app ID.
+ *
+ * @param request Request with OTP value.
+ * @param apiAuthentication Authentication object.
+ * @return Response with activation code, activation code signature, and activation ID.
+ * @throws ActivationCodeException In case of invalid user / app attributes or communication with PowerAuth Service fails.
+ * @throws InvalidRequestObjectException In case request object validation fails.
+ */
+ public ActivationCodeResponse requestActivationCode(ActivationCodeRequest request, PowerAuthApiAuthentication apiAuthentication) throws InvalidRequestObjectException, ActivationCodeException {
+
+ // Fetch information from the authentication object
+ final String sourceActivationId = apiAuthentication.getActivationId();
+ final String sourceUserId = apiAuthentication.getUserId();
+ final Long sourceAppId = apiAuthentication.getApplicationId();
+ final List sourceActivationFlags = apiAuthentication.getActivationFlags();
+ final List sourceApplicationRoles = apiAuthentication.getApplicationRoles();
+
+ logger.info("Activation code registration started, user ID: {}", sourceUserId);
+
+ // Verify that activation code delegate is implemented
+ if (activationCodeDelegate == null) {
+ logger.error("Missing activation code implementation");
+ throw new ActivationCodeException();
+ }
+
+ // Validate the request object
+ final String error = ActivationCodeRequestValidator.validate(request);
+ if (error != null) {
+ logger.error("Invalid object in activation code request - {}, user ID: {}", error, sourceUserId);
+ throw new InvalidRequestObjectException();
+ }
+
+ // Get the request parameters
+ final String otp = request.getOtp();
+ final String applicationId = request.getApplicationId();
+
+ final Long destinationAppId = activationCodeDelegate.destinationApplicationId(applicationId, sourceAppId, sourceActivationFlags, sourceApplicationRoles);
+ if (destinationAppId == null) {
+ logger.error("Invalid application ID. The provided source app ID: {} cannot activate the destination app ID: {}.", sourceAppId, applicationId);
+ throw new ActivationCodeException();
+ }
+
+ try {
+ // Create a new activation
+ logger.info("Calling PowerAuth Server with new activation request, user ID: {}, app ID: {}, otp: {}, ", sourceUserId, destinationAppId, otp);
+ final InitActivationResponse iar = powerAuthClient.initActivation(
+ sourceUserId, destinationAppId, ActivationOtpValidation.ON_KEY_EXCHANGE, otp
+ );
+ logger.info("Successfully obtained a new activation with ID: {}", iar.getActivationId());
+
+ // Notify systems about newly created activation
+ activationCodeDelegate.didReturnActivationCode(
+ sourceActivationId, sourceUserId, sourceAppId, destinationAppId,
+ iar.getActivationId(), iar.getActivationCode(), iar.getActivationSignature()
+ );
+
+ // Add the activation flags
+ final List flags = activationCodeDelegate.addActivationFlags(
+ sourceActivationId, sourceActivationFlags, sourceUserId, sourceAppId, sourceApplicationRoles, destinationAppId,
+ iar.getActivationId(), iar.getActivationCode(), iar.getActivationSignature()
+ );
+ if (flags != null && !flags.isEmpty()) {
+ logger.info("Calling PowerAuth Server to add activation flags to activation ID: {}, flags: {}.", iar.getActivationId(), flags.toArray());
+ powerAuthClient.addActivationFlags(iar.getActivationId(), flags);
+ logger.info("Successfully added flags to activation ID: {}.", iar.getActivationId());
+ } else {
+ logger.info("Activation with ID: {} has no additional flags.", iar.getActivationId());
+ }
+
+ return activationCodeConverter.convert(iar);
+ } catch (PowerAuthClientException e) {
+ logger.error("Unable to call upstream service.", e);
+ throw new ActivationCodeException();
+ }
+
+ }
+
+}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java
index 8fd312097..0bb28631a 100644
--- a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/PushRegistrationService.java
@@ -24,7 +24,7 @@
import com.wultra.app.enrollmentserver.model.validator.PushRegisterRequestValidator;
import io.getlime.core.rest.model.base.request.ObjectRequest;
import io.getlime.core.rest.model.base.response.Response;
-import io.getlime.push.client.MobilePlatform;
+import io.getlime.push.model.enumeration.MobilePlatform;
import io.getlime.push.client.PushServerClient;
import io.getlime.push.client.PushServerClientException;
import org.slf4j.Logger;
@@ -74,8 +74,8 @@ public Response registerDevice(
}
// Get the values from the request
- String platform = requestObject.getPlatform();
- String token = requestObject.getToken();
+ final String platform = requestObject.getPlatform();
+ final String token = requestObject.getToken();
// Register the device and return response
MobilePlatform mobilePlatform = MobilePlatform.Android;
@@ -83,7 +83,7 @@ public Response registerDevice(
mobilePlatform = MobilePlatform.iOS;
}
try {
- boolean result = client.createDevice(applicationId, token, mobilePlatform, activationId);
+ final boolean result = client.createDevice(applicationId, token, mobilePlatform, activationId);
if (result) {
logger.info("Push registration succeeded, user ID: {}", userId);
return new Response();
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/converter/ActivationCodeConverter.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/converter/ActivationCodeConverter.java
new file mode 100644
index 000000000..f1e2d91ff
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/impl/service/converter/ActivationCodeConverter.java
@@ -0,0 +1,49 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.impl.service.converter;
+
+import com.wultra.app.enrollmentserver.model.response.ActivationCodeResponse;
+import com.wultra.security.powerauth.client.v3.InitActivationResponse;
+import org.springframework.stereotype.Component;
+
+/**
+ * Converter between objects representing activation codes.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+@Component
+public class ActivationCodeConverter {
+
+ /**
+ * Convert the PowerAuth Server InitActivationResponse into ActivationCodeResponse.
+ *
+ * @param iar Original response.
+ * @return Converted response.
+ */
+ public ActivationCodeResponse convert(InitActivationResponse iar) {
+ if (iar == null) {
+ return null;
+ }
+ final ActivationCodeResponse response = new ActivationCodeResponse();
+ response.setActivationId(iar.getActivationId());
+ response.setActivationCode(iar.getActivationCode());
+ response.setActivationSignature(iar.getActivationSignature());
+ return response;
+ }
+
+}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/request/ActivationCodeRequest.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/request/ActivationCodeRequest.java
new file mode 100644
index 000000000..ce3613cc4
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/request/ActivationCodeRequest.java
@@ -0,0 +1,33 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.model.request;
+
+import lombok.Data;
+
+/**
+ * Request with attributes of a new activation code.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+@Data
+public class ActivationCodeRequest {
+
+ public String applicationId;
+ public String otp;
+
+}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/response/ActivationCodeResponse.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/response/ActivationCodeResponse.java
new file mode 100644
index 000000000..39d2d57ea
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/response/ActivationCodeResponse.java
@@ -0,0 +1,34 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.model.response;
+
+import lombok.Data;
+
+/**
+ * Response with a new activation code.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+@Data
+public class ActivationCodeResponse {
+
+ private String activationId;
+ private String activationCode;
+ private String activationSignature;
+
+}
diff --git a/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/validator/ActivationCodeRequestValidator.java b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/validator/ActivationCodeRequestValidator.java
new file mode 100644
index 000000000..82abb46ab
--- /dev/null
+++ b/enrollment-server/src/main/java/com/wultra/app/enrollmentserver/model/validator/ActivationCodeRequestValidator.java
@@ -0,0 +1,61 @@
+/*
+ * PowerAuth Enrollment Server
+ * Copyright (C) 2021 Wultra s.r.o.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+package com.wultra.app.enrollmentserver.model.validator;
+
+import com.wultra.app.enrollmentserver.model.request.ActivationCodeRequest;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Validator for activation code request.
+ *
+ * @author Petr Dvorak, petr@wultra.com
+ */
+public class ActivationCodeRequestValidator {
+
+ private static final int MIN_OTP_LEN = 16;
+
+ /**
+ * Validate activation code request object.
+ *
+ * @param request Activation code request.
+ * @return Technical error message in case object is invalid, null in case the request object is valid.
+ */
+ public static String validate(ActivationCodeRequest request) {
+
+ // Validate request is present
+ if (request == null) {
+ return "No request object provided.";
+ }
+
+ // Validate application value
+ final String applicationId = request.getApplicationId();
+ if (StringUtils.isBlank(applicationId)) { // application ID must be present
+ return "No application ID was provided.";
+ }
+
+ // Validate OTP value
+ final String otp = request.getOtp();
+ if (StringUtils.isBlank(otp)) { // OTP must be present
+ return "No OTP was provided.";
+ } else if (otp.length() < MIN_OTP_LEN) { // OTP must be sufficiently long
+ return "OTP is too short, minimum of " + MIN_OTP_LEN + " characters is required for the activation code fetch use-case.";
+ }
+
+ return null;
+ }
+}
diff --git a/enrollment-server/src/main/resources/application.properties b/enrollment-server/src/main/resources/application.properties
index a1b85efd8..9cfc9ff92 100644
--- a/enrollment-server/src/main/resources/application.properties
+++ b/enrollment-server/src/main/resources/application.properties
@@ -72,4 +72,5 @@ powerauth.service.security.clientSecret=
# powerauth.push.service.url=http://localhost:8080/powerauth-push-server
# Enrollment Server Configuration
-enrollment-server.mtoken.enabled=true
\ No newline at end of file
+enrollment-server.mtoken.enabled=true
+enrollment-server.activation-spawn.enabled=false
\ No newline at end of file