diff --git a/pom.xml b/pom.xml index e7ed2d68e..65a31096d 100644 --- a/pom.xml +++ b/pom.xml @@ -23,6 +23,7 @@ 3.1.1 1.1.0 + 4.7.3.4 @@ -59,6 +60,7 @@ com.github.spotbugs spotbugs-maven-plugin + ${spotbugs-maven-plugin.version} diff --git a/yoti-sdk-spring-boot-example/README.md b/yoti-sdk-spring-boot-example/README.md index 4556d125f..494171be0 100644 --- a/yoti-sdk-spring-boot-example/README.md +++ b/yoti-sdk-spring-boot-example/README.md @@ -1,16 +1,17 @@ -# Spring Boot Yoti SDK Example +# Spring Boot Yoti SDK Examples +This project shows example implementations of a server-app with an endpoint which will be called by Yoti -This project shows an example implementation of a server-app with an endpoint which will be called by Yoti with a `token`. -You will need to pass this token to Yoti-SDK in order to retrieve the profile of a user which has been logged in by Yoti. +# Prerequisites +Before you start, you'll need to create an Application in [Yoti Hub](https://hub.yoti.com) and set the domain to `https://localhost:8443/` -Before you start, you'll need to create an Application in [Yoti Hub](https://hub.yoti.com) and verify the domain. - -**NOTE: While creating Application in Yoti Hub, some of the attributes (except phone number and selfie) require users to have a Yoti with a verified passport. If your application, for instance, requires the user's date of birth and she/he has not added their passport to their Yoti account, this will lead to a failed login.** +Note that: +- Your endpoint must be accessible from the machine that is displaying the QR code.** +- In order to receive calls on endpoint, you need to expose your server-app to the outside world. We require that you use the domain from the Callback URL and HTTPS +- While creating Application in Yoti Hub, some of the attributes (except phone number and selfie) require users to have a Yoti with a verified passport. If your application, for instance, requires the user's date of birth and she/he has not added their passport to their Yoti account, this will lead to a failed login ## Project Structure -* The logic for retrieving the profile can be found in `com.yoti.api.examples.springboot.YotiLoginController#doLogin`. * `resources/app-keypair.pem` is the keypair you can get from Yoti Hub. -* `resource/application.yml` contains the configuration that enforces SSL usage by your server-app (in case you are not using a proxy server like NGINX). Make sure that you update the SDK Application ID and the configuration points to the right path to the java keystore with an SSL key (there is an already one included in the project ``` server.keystore.jks ```). +* `resources/application.yml` contains the configuration that enforces SSL usage by your server-app (in case you are not using a proxy server like NGINX). Make sure that you update the SDK Application ID and the configuration points to the right path to the java keystore with an SSL key (there is an already one included in the project ``` server.keystore.jks ```). * This project contains a Spring-boot server application. In this example we used the current SDK version by including the specific Maven dependency with its repository: ```xml @@ -20,26 +21,31 @@ Before you start, you'll need to create an Application in [Yoti Hub](https://hub ``` -## Building your example server-app -1. In the [Yoti Hub](https://hub.yoti.com) set the application domain of your app to `https://localhost:8443/`. Note that your endpoint must be accessible from the machine that is displaying the QR code. -1. Still in the Hub, set the scenario callback URL to `/login`. -1. Copy the [resources/application.yml.example](src/main/resources/application.yml.example) and create a new file `resources/application.yml` -1. Edit the `resources/application.yml` and replace the `yoti-client-sdk-id-from-hub` value with the `Yoti client SDK ID` you can find in Yoti Hub. -1. Download your Application's key pair from Yoti Hub and copy it to `resources/app-keypair.pem`. -1. Run `mvn clean package` to build the project. - -## Running -* You can run your server-app by executing `java -jar target/yoti-sdk-spring-boot-example-3.8.0-SNAPSHOT.jar` - * If you are using Java 9, you can run the server-app as follows `java -jar target/yoti-sdk-spring-boot-example-3.8.0-SNAPSHOT.jar --add-exports java.base/jdk.internal.ref=ALL-UNNAMED` +## Building your server-app and run the example +* Copy the [application.yml.example](src/main/resources/application.yml.example) and rename it to `application.yml` +* Edit the newly renamed file and replace `yoti-client-sdk-id-from-hub` value with `Yoti client SDK ID` you can find in Yoti Hub +* Download your Application's key pair from Yoti Hub and save it to `resources/app-keypair.pem` +* Run `mvn clean package` to build the project + +### Share v1 +* In the Hub, set the scenario callback URL to `/login`. + * In order to receive calls on your /login endpoint, you need to expose your server-app to the outside world. + * You **must** use the domain from the Callback URL and HTTPS +* You can run your server-app by executing `java -jar target/yoti-sdk-spring-boot-example.jar` * Navigate to: - * `https://localhost:8443` to initiate a login using Yoti. The Spring demo is listening for the response on `https://localhost:8443/login`. - * `https://localhost:8443/dynamic-share` to initiate a dynamic share with location with result displayed in profile page. - * `https://localhost:8443/dbs-check` to initiate a BDS standard check with location with result displayed in profile page. + * [https://localhost:8443](https://localhost:8443) to initiate a login using Yoti. The Spring demo is listening for the response on `https://localhost:8443/login` + * [https://localhost:8443/dynamic-share](https://localhost:8443/dynamic-share) to initiate a dynamic share with location with result displayed in profile page. + * [https://localhost:8443/dbs-check](https://localhost:8443/dbs-check) to initiate a BDS standard check with location with result displayed in profile page. -In order to receive calls on your /login endpoint, you need to expose your server-app to the outside world. We require that you use the domain from the Callback URL and HTTPS. +The logic for all the v1 share examples can be found in the `YotiLoginController` -## Requirements for running the application -* Java 8 or above -* If you are using Oracle JDK/JRE you need to install JCE extension in your server's Java to allow strong encryption (http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). This is not a requirement if you are using OpenJDK. +### Share v2 +* You can run your server-app by executing `java -jar -Dyoti.api.url="https://api.yoti.com/share" target/yoti-sdk-spring-boot-example.jar`. The JVM argument is required to override the default `https://api.yoti.com/api/v1` +* Navigate to: + * [https://localhost:8443/v2/digital-identity-share](https://localhost:8443/v2/digital-identity-share) to initiate a login using the Yoti share v2 +The logic for the v2 share example session creation and receipt can be found respectively in the `IdentitySessionController` and `IdentityLoginController` +## Requirements for running the application +* Java 8 or above +* If you are using Oracle JDK/JRE you need to install JCE extension in your server's Java to allow strong encryption (http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). This is not a requirement if you are using OpenJDK diff --git a/yoti-sdk-spring-boot-example/pom.xml b/yoti-sdk-spring-boot-example/pom.xml index f2d3d9d6a..941cd07bf 100644 --- a/yoti-sdk-spring-boot-example/pom.xml +++ b/yoti-sdk-spring-boot-example/pom.xml @@ -44,6 +44,7 @@ 8 32.1.1-jre + 4.7.3.4 @@ -92,6 +93,7 @@ + ${project.artifactId} @@ -118,6 +120,7 @@ com.github.spotbugs spotbugs-maven-plugin + ${spotbugs-maven-plugin.version} diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/DigitalIdentityController.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/DigitalIdentityController.java deleted file mode 100644 index f8a2c087e..000000000 --- a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/DigitalIdentityController.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.yoti.api.examples.springboot; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.function.Supplier; - -import com.yoti.api.client.DigitalIdentityClient; -import com.yoti.api.client.identity.ShareSession; -import com.yoti.api.client.identity.ShareSessionQrCode; -import com.yoti.api.client.identity.ShareSessionRequest; -import com.yoti.api.client.identity.policy.Policy; -import com.yoti.api.spring.ClientProperties; -import com.yoti.api.spring.DigitalIdentityProperties; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Controller; -import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -@ConditionalOnClass(DigitalIdentityClient.class) -@EnableConfigurationProperties({ ClientProperties.class, DigitalIdentityProperties.class }) -@Controller -@EnableWebMvc -@RequestMapping("/v2") -public class DigitalIdentityController implements WebMvcConfigurer { - - private final DigitalIdentityClient client; - private final ClientProperties properties; - - @Override - public void addResourceHandlers(ResourceHandlerRegistry registry) { - registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); - } - - @Autowired - public DigitalIdentityController(DigitalIdentityClient client, ClientProperties properties) { - this.client = client; - this.properties = properties; - } - - @RequestMapping("/") - public String home(Model model) { - model.addAttribute("clientSdkId", properties.getClientSdkId()); - model.addAttribute("scenarioId", properties.getScenarioId()); - return "index"; - } - - @RequestMapping("/digital-identity-share") - public String identityShare(Model model) throws URISyntaxException { - model.addAttribute("sdkId", properties.getClientSdkId()); - model.addAttribute("message", "Example page for identity share"); - - Policy policy = Policy.builder().build(); - - ShareSessionRequest shareSessionRequest = ShareSessionRequest.builder() - .withPolicy(policy) - .withRedirectUri(new URI("https://host/redirect/")) - .build(); - - ShareSession session = execute(() -> client.createShareSession(shareSessionRequest), model); - if (session == null) { - return "error"; - } - - String sessionId = session.getId(); - - model.addAttribute("session_id", sessionId); - model.addAttribute("session_status", session.getStatus()); - model.addAttribute("session_expiry", session.getExpiry()); - - ShareSessionQrCode sessionQrCode = execute(() -> client.createShareQrCode(sessionId), model); - if (sessionQrCode == null) { - return "error"; - } - - String qrCodeId = sessionQrCode.getId(); - - model.addAttribute("session_qrcode_id", qrCodeId); - model.addAttribute("session_qrcode_uri", sessionQrCode.getUri()); - - ShareSessionQrCode fetchQrCode = execute(() -> client.fetchShareQrCode(qrCodeId), model); - if (fetchQrCode == null) { - return "error"; - } - - model.addAttribute("qrcode_expiry", fetchQrCode.getExpiry()); - model.addAttribute("qrcode_extensions", fetchQrCode.getExtensions()); - model.addAttribute("qrcode_redirect_uri", fetchQrCode.getRedirectUri()); - model.addAttribute("qrcode_session_id", fetchQrCode.getSession().getId()); - model.addAttribute("qrcode_session_status", fetchQrCode.getSession().getStatus()); - model.addAttribute("qrcode_session_expiry", fetchQrCode.getSession().getExpiry()); - - ShareSession fetchSession = execute(() -> client.fetchShareSession(sessionId), model); - if (fetchSession == null) { - return "error"; - } - - model.addAttribute("fetch_session_id", fetchSession.getId()); - model.addAttribute("fetch_session_created", fetchSession.getCreated()); - model.addAttribute("fetch_session_updated", fetchSession.getUpdated()); - model.addAttribute("fetch_session_expiry", fetchSession.getExpiry()); - model.addAttribute("fetch_session_status", fetchSession.getStatus()); - model.addAttribute("fetch_session_qrcode_id", fetchSession.getQrCodeId()); - model.addAttribute("fetch_session_receipt_id", fetchSession.getReceiptId()); - - return "digital-identity-share"; - } - - private static T execute(Supplier supplier, Model model) { - try { - return supplier.get(); - } catch (Exception ex) { - model.addAttribute("error", ex.getMessage()); - return null; - } - } - -} diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/DisplayAttribute.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/DisplayAttribute.java deleted file mode 100644 index 85f174e5c..000000000 --- a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/DisplayAttribute.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.yoti.api.examples.springboot; - -import com.yoti.api.client.Attribute; - -public class DisplayAttribute { - - private final String displayName; - private final String preValue; - private final String icon; - private final Attribute attribute; - - public DisplayAttribute(String preValue, String displayName, Attribute attribute, String icon) { - this.displayName = displayName; - this.preValue = preValue; - this.icon = icon; - this.attribute = attribute; - } - - public DisplayAttribute(String displayName, Attribute attribute, String icon) { - this.displayName = displayName; - this.preValue = ""; - this.icon = icon; - this.attribute = attribute; - } - - public String getDisplayName() { - return displayName; - } - - public String getPreValue() { - return preValue; - } - - public String getIcon() { - return icon; - } - - public Attribute getAttribute() { - return attribute; - } - - public String getDisplayValue() { - return this.preValue + this.attribute.getValue(); - } - -} diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/IdentityLoginController.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/IdentityLoginController.java new file mode 100644 index 000000000..68438b53a --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/IdentityLoginController.java @@ -0,0 +1,111 @@ +package com.yoti.api.examples.springboot; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import com.yoti.api.client.ApplicationProfile; +import com.yoti.api.client.Attribute; +import com.yoti.api.client.DigitalIdentityClient; +import com.yoti.api.client.HumanProfile; +import com.yoti.api.client.spi.remote.call.identity.Receipt; +import com.yoti.api.examples.springboot.attribute.AttributeMapper; +import com.yoti.api.examples.springboot.attribute.DisplayAttribute; +import com.yoti.api.spring.ClientProperties; +import com.yoti.api.spring.DigitalIdentityProperties; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@ConditionalOnClass(DigitalIdentityClient.class) +@EnableConfigurationProperties({ ClientProperties.class, DigitalIdentityProperties.class }) +@Controller +@EnableWebMvc +@RequestMapping("/v2") +public class IdentityLoginController implements WebMvcConfigurer { + + private final DigitalIdentityClient client; + private final ClientProperties properties; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); + } + + @Autowired + public IdentityLoginController(DigitalIdentityClient client, ClientProperties properties) { + this.client = client; + this.properties = properties; + } + + @RequestMapping("/") + public String home(Model model) { + model.addAttribute("clientSdkId", properties.getClientSdkId()); + model.addAttribute("scenarioId", properties.getScenarioId()); + return "index"; + } + + @RequestMapping("/digital-identity-share") + public String identityShare(Model model) { + model.addAttribute("sdkId", properties.getClientSdkId()); + model.addAttribute("message", "Example page for identity share"); + + return "digital-identity-share"; + } + + @GetMapping(value = "/receipt") + public String receipt(@RequestParam("receiptId") String receiptId, Model model) { + Receipt receipt = execute(() -> client.fetchShareReceipt(receiptId), model); + + if (receipt == null || receipt.getError().isPresent()) { + model.addAttribute("error", receipt.getError().get()); + return "error"; + } + + Receipt.ApplicationContent applicationContent = receipt.getApplicationContent(); + + Optional.ofNullable(applicationContent.getProfile()) + .map(ApplicationProfile::getApplicationLogo) + .map(attr -> model.addAttribute("appLogo", attr.getValue().getBase64Content())); + + receipt.getProfile().map(HumanProfile::getSelfie) + .map(attr -> model.addAttribute("base64Selfie", attr.getValue().getBase64Content())); + receipt.getProfile().map(HumanProfile::getFullName) + .map(attr -> model.addAttribute("fullName", attr.getValue())); + receipt.getProfile().map(HumanProfile::getAttributes) + .map(attr -> model.addAttribute("displayAttributes", mapAttributes(attr))); + + return "profile"; + } + + private List mapAttributes(Collection> attributes) { + return attributes.stream() + .map(AttributeMapper::mapToDisplayAttribute) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private static T execute(Supplier supplier, Model model) { + try { + return supplier.get(); + } catch (Exception ex) { + model.addAttribute("error", ex.getMessage()); + return null; + } + } + +} diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/IdentitySessionController.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/IdentitySessionController.java new file mode 100644 index 000000000..e037e4840 --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/IdentitySessionController.java @@ -0,0 +1,124 @@ +package com.yoti.api.examples.springboot; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import com.yoti.api.client.DigitalIdentityClient; +import com.yoti.api.client.identity.ShareSession; +import com.yoti.api.client.identity.ShareSessionRequest; +import com.yoti.api.client.identity.extension.Extension; +import com.yoti.api.client.identity.extension.LocationConstraintContent; +import com.yoti.api.client.identity.extension.LocationConstraintExtensionBuilder; +import com.yoti.api.client.identity.policy.Policy; +import com.yoti.api.client.identity.policy.WantedAttribute; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/v2") +public class IdentitySessionController { + + private static final URI REDIRECT_URI = URI.create("https://localhost:8443/v2/receipt"); + + private final DigitalIdentityClient client; + + @Autowired + public IdentitySessionController(DigitalIdentityClient client) { + this.client = client; + } + + @GetMapping("/digital-identity-session") + public String identityShareSession() { + ShareSession session = client.createShareSession( + // forMinimalShare() + forDynamicScenarioShare() + // forIdentityProfileShare() + // forLocationExtensionShare() + ); + + Map response = new HashMap<>(); + response.put("sessionId", session.getId()); + + return toJson(response); + } + + private static String toJson(Map map) { + try { + return new ObjectMapper().writeValueAsString(map); + } catch (JsonProcessingException e) { + return "error"; + } + } + + private static ShareSessionRequest forMinimalShare() { + return ShareSessionRequest.builder() + .withPolicy(Policy.builder().build()) + .withRedirectUri(REDIRECT_URI) + .build(); + } + + private static ShareSessionRequest forDynamicScenarioShare() { + WantedAttribute givenNamesWantedAttribute = WantedAttribute.builder() + .withName("given_names") + .build(); + + WantedAttribute emailAddressWantedAttribute = WantedAttribute.builder() + .withName("email_address") + .build(); + + Policy policy = Policy.builder() + .withWantedAttribute(givenNamesWantedAttribute) + .withWantedAttribute(emailAddressWantedAttribute) + .withFullName() + .withSelfie() + .withPhoneNumber() + .withAgeOver(18) + .build(); + + return ShareSessionRequest.builder() + .withPolicy(policy) + .withRedirectUri(REDIRECT_URI) + .build(); + } + + private static ShareSessionRequest forIdentityProfileShare() { + Map scheme = new HashMap<>(); + scheme.put("type", "DBS"); + scheme.put("objective", "BASIC"); + + Map identityProfile = new HashMap<>(); + identityProfile.put("trust_framework", "UK_TFIDA"); + identityProfile.put("scheme", scheme); + + Policy policy = Policy.builder() + .withWantedRememberMe(true) + .withIdentityProfile(identityProfile) + .build(); + + return ShareSessionRequest.builder() + .withPolicy(policy) + .withRedirectUri(REDIRECT_URI) + .build(); + } + + private static ShareSessionRequest forLocationExtensionShare() { + Extension locationExtension = new LocationConstraintExtensionBuilder() + .withLatitude(51.5074) + .withLongitude(-0.1278) + .withRadius(6000) + .build(); + + return ShareSessionRequest.builder() + .withPolicy(Policy.builder().withWantedRememberMe(true).build()) + .withExtension(locationExtension) + .withRedirectUri(REDIRECT_URI) + .build(); + } + +} diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/YotiLoginController.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/YotiLoginController.java index 0e27105ac..124bef78f 100644 --- a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/YotiLoginController.java +++ b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/YotiLoginController.java @@ -1,13 +1,11 @@ package com.yoti.api.examples.springboot; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.stream.Collectors; -import com.yoti.api.attributes.AttributeConstants; import com.yoti.api.client.ActivityDetails; import com.yoti.api.client.Attribute; import com.yoti.api.client.HumanProfile; @@ -22,10 +20,11 @@ import com.yoti.api.client.shareurl.extension.LocationConstraintExtensionBuilder; import com.yoti.api.client.shareurl.policy.DynamicPolicy; import com.yoti.api.client.shareurl.policy.WantedAttribute; +import com.yoti.api.examples.springboot.attribute.AttributeMapper; +import com.yoti.api.examples.springboot.attribute.DisplayAttribute; import com.yoti.api.spring.ClientProperties; import com.yoti.api.spring.YotiProperties; -import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -39,7 +38,6 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.thymeleaf.util.StringUtils; @Configuration @ConditionalOnClass(YotiClient.class) @@ -149,8 +147,8 @@ public String doLogin(@RequestParam("token") final String token, final Model mod } List displayAttributes = humanProfile.getAttributes().stream() - .map(this::mapToDisplayAttribute) - .filter(displayAttribute -> displayAttribute != null) + .map(AttributeMapper::mapToDisplayAttribute) + .filter(Objects::nonNull) .collect(Collectors.toList()); model.addAttribute("displayAttributes", displayAttributes); @@ -195,64 +193,4 @@ public String dbsCheck(final Model model) { return "dbs-check"; } - private DisplayAttribute mapToDisplayAttribute(Attribute attribute) { - switch (attribute.getName()) { - case AttributeConstants.HumanProfileAttributes.FULL_NAME: - return new DisplayAttribute("Full name", attribute, "yoti-icon-profile"); - case AttributeConstants.HumanProfileAttributes.GIVEN_NAMES: - return new DisplayAttribute("Given names", attribute, "yoti-icon-profile"); - case AttributeConstants.HumanProfileAttributes.FAMILY_NAME: - return new DisplayAttribute("Family name", attribute, "yoti-icon-profile"); - case AttributeConstants.HumanProfileAttributes.NATIONALITY: - return new DisplayAttribute("Nationality", attribute, "yoti-icon-nationality"); - case AttributeConstants.HumanProfileAttributes.POSTAL_ADDRESS: - return new DisplayAttribute("Address", attribute, "yoti-icon-address"); - case AttributeConstants.HumanProfileAttributes.STRUCTURED_POSTAL_ADDRESS: - return new DisplayAttribute("Structured Postal Address", attribute, "yoti-icon-address"); - case AttributeConstants.HumanProfileAttributes.PHONE_NUMBER: - return new DisplayAttribute("Mobile number", attribute, "yoti-icon-phone"); - case AttributeConstants.HumanProfileAttributes.EMAIL_ADDRESS: - return new DisplayAttribute("Email address", attribute, "yoti-icon-email"); - case AttributeConstants.HumanProfileAttributes.DATE_OF_BIRTH: - return new DisplayAttribute("Date of birth", attribute, "yoti-icon-calendar"); - case AttributeConstants.HumanProfileAttributes.SELFIE: - return null; // Do nothing - we already display the selfie - case AttributeConstants.HumanProfileAttributes.GENDER: - return new DisplayAttribute("Gender", attribute, "yoti-icon-gender"); - case AttributeConstants.HumanProfileAttributes.IDENTITY_PROFILE_REPORT: - return new DisplayAttribute("Identity Profile Report", toJsonAttribute(attribute), "yoti-icon-document"); - - default: - if (attribute.getName().contains(":")) { - return handleAgeVerification(attribute); - } else { - return handleProfileAttribute(attribute); - } - } - } - - private Attribute toJsonAttribute(Attribute attribute) { - ObjectMapper MAPPER = new ObjectMapper(); - - String json; - - try { - json = MAPPER.readTree(MAPPER.writeValueAsString(attribute.getValue()).getBytes(StandardCharsets.UTF_8)) - .toString(); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - - return new Attribute<>(attribute.getName(), json); - } - - private DisplayAttribute handleAgeVerification(Attribute attribute) { - return new DisplayAttribute("Age Verification/", "Age verified", attribute, "yoti-icon-verified"); - } - - private DisplayAttribute handleProfileAttribute(Attribute attribute) { - String attributeName = StringUtils.capitalize(attribute.getName()); - return new DisplayAttribute(attributeName, attribute, "yoti-icon-profile"); - } - } diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/AttributeDisplayProperty.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/AttributeDisplayProperty.java new file mode 100644 index 000000000..80790f5b1 --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/AttributeDisplayProperty.java @@ -0,0 +1,59 @@ +package com.yoti.api.examples.springboot.attribute; + +import java.util.EnumSet; +import java.util.Optional; + +import com.yoti.api.client.Attribute; + +public enum AttributeDisplayProperty { + + FAMILY_NAME("family_name", "Family name", "yoti-icon-profile"), + GIVEN_NAMES("given_names", "Given names", "yoti-icon-profile"), + FULL_NAME("full_name", "Full name", "yoti-icon-profile"), + DATE_OF_BIRTH("date_of_birth", "Date of birth", "yoti-icon-calendar"), + GENDER("gender", "Gender", "yoti-icon-gender"), + POSTAL_ADDRESS("postal_address", "Address", "yoti-icon-address"), + STRUCTURED_POSTAL_ADDRESS("structured_postal_address", "Structured address", "yoti-icon-address"), + NATIONALITY("nationality", "Nationality", "yoti-icon-nationality"), + PHONE_NUMBER("phone_number", "Mobile number", "yoti-icon-phone"), + EMAIL_ADDRESS("email_address", "Email address", "yoti-icon-email"), + IDENTITY_PROFILE_REPORT("identity_profile_report", "Identity Profile Report", "yoti-icon-document", true); + + private final String name; + private final String label; + private final String icon; + private final boolean isJson; + + AttributeDisplayProperty(String name, String label, String icon) { + this.name = name; + this.label = label; + this.icon = icon; + isJson = false; + } + + AttributeDisplayProperty(String name, String label, String icon, boolean isJson) { + this.name = name; + this.label = label; + this.icon = icon; + this.isJson = isJson; + } + + public static Optional fromAttribute(Attribute attribute) { + return EnumSet.allOf(AttributeDisplayProperty.class).stream() + .filter(adp -> adp.name.equals(attribute.getName())) + .findFirst(); + } + + public String label() { + return label; + } + + public String icon() { + return icon; + } + + public boolean isJson() { + return isJson; + } + +} diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/AttributeMapper.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/AttributeMapper.java new file mode 100644 index 000000000..e9bfc3d0a --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/AttributeMapper.java @@ -0,0 +1,51 @@ +package com.yoti.api.examples.springboot.attribute; + +import java.io.IOException; + +import com.yoti.api.client.Attribute; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.thymeleaf.util.StringUtils; + +public class AttributeMapper { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public static DisplayAttribute mapToDisplayAttribute(Attribute attribute) { + return AttributeDisplayProperty.fromAttribute(attribute) + .map(property -> property.isJson() + ? new DisplayAttribute(toJson(attribute), property) + : new DisplayAttribute(attribute, property) + ) + .orElseGet(() -> { + if (attribute.getName().contains(":")) { + return handleAgeVerification(attribute); + } else { + return handleProfileAttribute(attribute); + } + }); + } + + private static Attribute toJson(Attribute attribute) { + String json; + + try { + json = MAPPER.writeValueAsString(attribute.getValue()); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + return new Attribute<>(attribute.getName(), json); + } + + private static DisplayAttribute handleAgeVerification(Attribute attribute) { + return new DisplayAttribute("Age Verification/", "Age verified", attribute, "yoti-icon-verified"); + } + + private static DisplayAttribute handleProfileAttribute(Attribute attribute) { + return attribute.getName().equalsIgnoreCase("selfie") + ? null + : new DisplayAttribute(StringUtils.capitalize(attribute.getName()), attribute, "yoti-icon-profile"); + } + +} diff --git a/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/DisplayAttribute.java b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/DisplayAttribute.java new file mode 100644 index 000000000..cfeb457d8 --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/java/com/yoti/api/examples/springboot/attribute/DisplayAttribute.java @@ -0,0 +1,49 @@ +package com.yoti.api.examples.springboot.attribute; + +import com.yoti.api.client.Attribute; + +public class DisplayAttribute { + + private final String label; + private final String preValue; + private final String icon; + private final Attribute attribute; + + public DisplayAttribute(String preValue, String label, Attribute attribute, String icon) { + this.label = label; + this.preValue = preValue; + this.icon = icon; + this.attribute = attribute; + } + + public DisplayAttribute(String label, Attribute attribute, String icon) { + this.label = label; + this.preValue = ""; + this.icon = icon; + this.attribute = attribute; + } + + public DisplayAttribute(Attribute attribute, AttributeDisplayProperty property) { + this.label = property.label(); + this.preValue = ""; + this.icon = property.icon(); + this.attribute = attribute; + } + + public String getLabel() { + return label; + } + + public String getIcon() { + return icon; + } + + public Attribute getAttribute() { + return attribute; + } + + public String getDisplayValue() { + return this.preValue + this.attribute.getValue(); + } + +} diff --git a/yoti-sdk-spring-boot-example/src/main/resources/application.properties b/yoti-sdk-spring-boot-example/src/main/resources/application.properties new file mode 100644 index 000000000..73092c52c --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/resources/application.properties @@ -0,0 +1 @@ +logging.level.org.springframework.boot.autoconfigure=ERROR diff --git a/yoti-sdk-spring-boot-example/src/main/resources/static/digital-identity-share.css b/yoti-sdk-spring-boot-example/src/main/resources/static/digital-identity-share.css new file mode 100644 index 000000000..7d06fc281 --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/resources/static/digital-identity-share.css @@ -0,0 +1,79 @@ +.yoti-body { + margin: 0; + font-family: Roboto, sans-serif; +} + +.yoti-top-section { + display: flex; + flex-direction: column; + + padding: 38px 0; + + background-color: #f7f8f9; + + align-items: center; +} + +.yoti-logo-section { + margin-bottom: 25px; +} + +.yoti-logo-image { + display: block; +} + +.yoti-top-header { + font-family: Roboto, sans-serif; + font-size: 40px; + font-weight: 700; + line-height: 1.2; + margin-top: 0; + margin-bottom: 80px; + text-align: center; + + color: #000; +} + +.yoti-sponsor-app-section { + display: flex; + flex-direction: column; + + padding: 50px 0; + + align-items: center; +} + +.yoti-sponsor-app-header { + font-family: Roboto, sans-serif; + font-size: 20px; + font-weight: 700; + line-height: 1.2; + + margin: 0; + + text-align: center; + + color: #000; +} + +.yoti-store-buttons-section { + margin-top: 40px; + display: grid; + grid-gap: 10px; + grid-template-columns: 1fr 1fr; +} + +@media (min-width: 600px) { + .yoti-top-header { + line-height: 1.4; + } +} + +.yoti-sdk-integration-section { + margin: 30px 0; +} + +.box { + inline-size: 600px; + overflow-wrap: break-word; +} diff --git a/yoti-sdk-spring-boot-example/src/main/resources/static/error.css b/yoti-sdk-spring-boot-example/src/main/resources/static/error.css new file mode 100644 index 000000000..b79b35166 --- /dev/null +++ b/yoti-sdk-spring-boot-example/src/main/resources/static/error.css @@ -0,0 +1,28 @@ +.yoti-body { + margin: 0; + font-family: Roboto, sans-serif; +} + +.yoti-top-section { + display: flex; + flex-direction: column; + + padding: 38px 0; + + background-color: #f7f8f9; + + align-items: center; +} + +.yoti-logo-section { + margin-bottom: 25px; +} + +.yoti-logo-image { + display: block; +} + +.box { + inline-size: 600px; + overflow-wrap: break-word; +} diff --git a/yoti-sdk-spring-boot-example/src/main/resources/templates/dbs-check.html b/yoti-sdk-spring-boot-example/src/main/resources/templates/dbs-check.html index 1e58fa1ae..cbd4952a6 100644 --- a/yoti-sdk-spring-boot-example/src/main/resources/templates/dbs-check.html +++ b/yoti-sdk-spring-boot-example/src/main/resources/templates/dbs-check.html @@ -1,76 +1,77 @@ - + - + + + + DBS Standard Example + + + + - - - - DBS Standard Example - - - + +
+
+
+ Yoti +
+ +

We now accept Yoti

+ +
+
+
+ + + + +
+ +
+

The Yoti app is free to download and use:

+ +
+ + Download on the App Store + + + + get it on Google Play + +
+
+
- -
-
-
- Yoti -
- -

We now accept Yoti

- -
-
-
- - - - -
- -
-

The Yoti app is free to download and use:

- -
- - Download on the App Store - - - - get it on Google Play - -
-
-
- - - - + + + diff --git a/yoti-sdk-spring-boot-example/src/main/resources/templates/digital-identity-share.html b/yoti-sdk-spring-boot-example/src/main/resources/templates/digital-identity-share.html index 9c8baccfb..da290f708 100644 --- a/yoti-sdk-spring-boot-example/src/main/resources/templates/digital-identity-share.html +++ b/yoti-sdk-spring-boot-example/src/main/resources/templates/digital-identity-share.html @@ -1,56 +1,80 @@ - + - - - Digital Identity Example - - + + + Digital Identity Example + + + -> -
-
-
- Yoti -
- -

Digital Identity Share Example page

- -

SdkId:

- -
-

Created Session

-

ID:

-

Status:

-

Expiry:

-
-
-

Created Session QR Code

-

ID:

-

URI:

-
- -
-

Fetched Session QR Code

-

Expiry:

-

Extensions:

-

Redirect URI:

-

Session Status:

-

Session Expiry:

-
- -
-

Fetched Session

-

Created:

-

Updated:

-

Expiry:

-

Status:

-

QR Code ID:

-

Receipt ID:

-
-
+ +
+
+
+ Yoti +
+ +

Digital Identity Share Example page

+ +

SdkId:

+ +
+
+
+ +
+ +
+

The Yoti app is free to download and use:

+ +
+ + Download on the App Store + + + + get it on Google Play + +
+
+ + + diff --git a/yoti-sdk-spring-boot-example/src/main/resources/templates/dynamic-share.html b/yoti-sdk-spring-boot-example/src/main/resources/templates/dynamic-share.html index 21e1fce69..97c3e5af0 100644 --- a/yoti-sdk-spring-boot-example/src/main/resources/templates/dynamic-share.html +++ b/yoti-sdk-spring-boot-example/src/main/resources/templates/dynamic-share.html @@ -1,76 +1,77 @@ - + - + + + + Dynamic Share Example + + + + - - - - Dynamic Share Example - - - + +
+
+
+ Yoti +
+ +

We now accept Yoti

+ +
+
+
+ + + + +
+ +
+

The Yoti app is free to download and use:

+ +
+ + Download on the App Store + + + + get it on Google Play + +
+
+
- -
-
-
- Yoti -
- -

We now accept Yoti

- -
-
-
- - - - -
- -
-

The Yoti app is free to download and use:

- -
- - Download on the App Store - - - - get it on Google Play - -
-
-
- - - - + + + diff --git a/yoti-sdk-spring-boot-example/src/main/resources/templates/error.html b/yoti-sdk-spring-boot-example/src/main/resources/templates/error.html index 962f37a45..eed82bc96 100644 --- a/yoti-sdk-spring-boot-example/src/main/resources/templates/error.html +++ b/yoti-sdk-spring-boot-example/src/main/resources/templates/error.html @@ -1,14 +1,27 @@ - + - - - Welcome - - - + + + + Error + + + + + + +
+
+
+ Yoti +
+
+

Home

+

Oops, something went wrong.

+

Error:

+
+
+
+ - -

Home

-

Could not login user for the following reason:

- diff --git a/yoti-sdk-spring-boot-example/src/main/resources/templates/index.html b/yoti-sdk-spring-boot-example/src/main/resources/templates/index.html index fb30dc012..bebbd0ba6 100644 --- a/yoti-sdk-spring-boot-example/src/main/resources/templates/index.html +++ b/yoti-sdk-spring-boot-example/src/main/resources/templates/index.html @@ -1,76 +1,77 @@ - + - + + + + Yoti client example + + + + - - - - Yoti client example - - - + +
+
+
+ Yoti +
+ +

We now accept Yoti

+ +
+
+
+ + + + +
+ +
+

The Yoti app is free to download and use:

+ +
+ + Download on the App Store + + + + get it on Google Play + +
+
+
- -
-
-
- Yoti -
- -

We now accept Yoti

- -
-
-
- - - - -
- -
-

The Yoti app is free to download and use:

- -
- - Download on the App Store - - - - get it on Google Play - -
-
-
- - - - + + + diff --git a/yoti-sdk-spring-boot-example/src/main/resources/templates/profile.html b/yoti-sdk-spring-boot-example/src/main/resources/templates/profile.html index ad90998f9..7ab06d341 100644 --- a/yoti-sdk-spring-boot-example/src/main/resources/templates/profile.html +++ b/yoti-sdk-spring-boot-example/src/main/resources/templates/profile.html @@ -1,110 +1,110 @@ - - - - - - - - Yoti client example - - - - - -
-
-
- Powered by - Yoti -
- -
- -
- Yoti - -
- -
- -
- -
-
- -
-
- -
- -
- -
- -
-
Attribute
-
Value
-
Anchors
-
- -
-
-
S / V
-
Value
-
Sub type
-
-
- -
-
- -
-
- - -
-
- -
-
-
- - - - - -
-
-
- document_image -
-
- document_image -
-
-
-
- -
-
S / V
-
Value
-
Sub type
- -
-
Source
-
-
-
- -
-
Verifier
-
-
-
-
-
-
-
-
- + + + + + + Yoti client example + + + + + + +
+
+
+ Powered by + Yoti +
+ +
+ +
+ Yoti + +
+ +
+ +
+ +
+
+ +
+
+ +
+ +
+ +
+ +
+
Attribute
+
Value
+
Anchors
+
+ +
+
+
S / V
+
Value
+
Sub type
+
+
+ +
+
+ +
+
+ + +
+
+ +
+
+
+ + + + + +
+
+
+ document_image +
+
+ document_image +
+
+
+
+ +
+
S / V
+
Value
+
Sub type
+ +
+
Source
+
+
+
+ +
+
Verifier
+
+
+
+
+
+
+
+
+