Skip to content

Commit 0e18a1f

Browse files
committed
SDK-2584: Add example for digital identity match
1 parent 721fd43 commit 0e18a1f

File tree

6 files changed

+316
-0
lines changed

6 files changed

+316
-0
lines changed

yoti-sdk-spring-boot-example/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ The logic for all the v1 share examples can be found in the `YotiLoginController
4545
* Navigate to:
4646
* [https://localhost:8443/v2/digital-identity-share](https://localhost:8443/v2/digital-identity-share) to initiate a login using the Yoti share v2
4747

48+
### Digital Identity Match
49+
* You can run your server-app by executing `java -jar -Dyoti.api.url="https://api.yoti.com/did" target/yoti-sdk-spring-boot-example.jar`. The JVM argument is required to override the default `https://api.yoti.com/api/v1`
50+
* Navigate to:
51+
* [https://localhost:8443/did/](https://localhost:8443/did/) to display the form to initiate a digital ID match
52+
4853
The logic for the v2 share example session creation and receipt can be found respectively in the `IdentitySessionController` and `IdentityLoginController`
4954

5055
## Requirements for running the application
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.yoti.api.examples.springboot;
2+
3+
import java.net.URI;
4+
import java.util.AbstractMap;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.Optional;
8+
import java.util.function.Supplier;
9+
import java.util.stream.Collectors;
10+
import java.util.stream.IntStream;
11+
12+
import com.yoti.api.client.DigitalIdentityClient;
13+
import com.yoti.api.client.identity.MatchNotification;
14+
import com.yoti.api.client.identity.MatchRequest;
15+
import com.yoti.api.client.identity.MatchResult;
16+
import com.yoti.api.client.spi.remote.call.ResourceException;
17+
import com.yoti.api.examples.springboot.model.MatchInputForm;
18+
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.stereotype.Controller;
21+
import org.springframework.ui.Model;
22+
import org.springframework.web.bind.annotation.ModelAttribute;
23+
import org.springframework.web.bind.annotation.PostMapping;
24+
import org.springframework.web.bind.annotation.RequestMapping;
25+
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
26+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
27+
28+
@Controller
29+
@RequestMapping("/did")
30+
public class DigitalIdController implements WebMvcConfigurer {
31+
32+
private final DigitalIdentityClient client;
33+
34+
@Override
35+
public void addResourceHandlers(ResourceHandlerRegistry registry) {
36+
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
37+
}
38+
39+
@Autowired
40+
public DigitalIdController(DigitalIdentityClient client) {
41+
this.client = client;
42+
}
43+
44+
@RequestMapping("/")
45+
public String home(Model model) {
46+
model.addAttribute("matchInputForm", new MatchInputForm());
47+
return "match-input";
48+
}
49+
50+
@PostMapping("/match")
51+
public String match(@ModelAttribute("matchInputForm") MatchInputForm form, Model model) {
52+
MatchNotification.Builder notification = MatchNotification.forUrl(URI.create(form.getEndpoint()))
53+
.withVerifyTls(form.isVerifyTls())
54+
.withMethod(form.getHttpMethod());
55+
56+
List<String> keys = form.getHeaderKeys();
57+
List<String> values = form.getHeaderValues();
58+
59+
Map<String, String> headers = IntStream.range(0, keys.size())
60+
.mapToObj(i -> new AbstractMap.SimpleEntry<>(
61+
keys.get(i),
62+
i < values.size() ? values.get(i) : ""
63+
))
64+
.filter(entry -> entry.getKey() != null && !entry.getKey().isEmpty())
65+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
66+
67+
if (!headers.isEmpty()) {
68+
notification.withHeaders(headers);
69+
}
70+
71+
MatchRequest.Builder match = MatchRequest.builder(form.getValue()).withNotification(notification.build());
72+
73+
MatchResult result = execute(() -> client.fetchMatch(match.build()), model);
74+
75+
return Optional.ofNullable(result)
76+
.map(r -> {
77+
model.addAttribute("matchId", r.getId());
78+
model.addAttribute("matchResult", r.getResult());
79+
80+
return "match-result";
81+
})
82+
.orElse("match-error");
83+
}
84+
85+
private static <T> T execute(Supplier<T> supplier, Model model) {
86+
try {
87+
return supplier.get();
88+
} catch (Exception ex) {
89+
if (ex.getCause() instanceof ResourceException) {
90+
ResourceException resourceEx = (ResourceException) ex.getCause();
91+
model.addAttribute("jsonError", resourceEx.getResponseBody());
92+
} else {
93+
model.addAttribute("error", ex.getCause() != null ? ex.getCause().getMessage() : ex.getMessage());
94+
}
95+
96+
return null;
97+
}
98+
}
99+
100+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.yoti.api.examples.springboot.model;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.UUID;
6+
7+
public class MatchInputForm {
8+
9+
private String value;
10+
private String endpoint;
11+
private String httpMethod;
12+
private boolean verifyTls;
13+
private List<String> headerKeys = new ArrayList<>();
14+
private List<String> headerValues = new ArrayList<>();
15+
16+
public MatchInputForm() {
17+
value = "+442532733270";
18+
endpoint = "https://company.com/callback/did-match";
19+
httpMethod = "POST";
20+
verifyTls = true;
21+
headerKeys.add("X-Request-ID");
22+
headerValues.add(UUID.randomUUID().toString());
23+
}
24+
25+
public String getValue() {
26+
return value;
27+
}
28+
29+
public void setValue(String value) {
30+
this.value = value;
31+
}
32+
33+
public String getEndpoint() {
34+
return endpoint;
35+
}
36+
37+
public void setEndpoint(String endpoint) {
38+
this.endpoint = endpoint;
39+
}
40+
41+
public String getHttpMethod() {
42+
return httpMethod;
43+
}
44+
45+
public void setHttpMethod(String httpMethod) {
46+
this.httpMethod = httpMethod;
47+
}
48+
49+
public boolean isVerifyTls() {
50+
return verifyTls;
51+
}
52+
53+
public void setVerifyTls(boolean verifyTls) {
54+
this.verifyTls = verifyTls;
55+
}
56+
57+
public List<String> getHeaderKeys() {
58+
return headerKeys;
59+
}
60+
61+
public void setHeaderKeys(List<String> headerKeys) {
62+
this.headerKeys = headerKeys;
63+
}
64+
65+
public List<String> getHeaderValues() {
66+
return headerValues;
67+
}
68+
69+
public void setHeaderValues(List<String> headerValues) {
70+
this.headerValues = headerValues;
71+
}
72+
73+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<html xmlns:th="http://www.thymeleaf.org" lang="en">
2+
3+
<head>
4+
<meta charset="utf-8"/>
5+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
6+
<title>Error</title>
7+
<link rel="stylesheet" type="text/css" th:href="@{/static/error.css}"/>
8+
<link rel="shortcut icon" type="image/x-icon" href="https://www.yoti.com/wp-content/uploads/yoti-favicon.png"/>
9+
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet"/>
10+
</head>
11+
12+
<body class="yoti-body">
13+
<main>
14+
<section class="yoti-top-section">
15+
<div class="yoti-logo-section">
16+
<img class="yoti-logo-image" src="/static/assets/logo.png" srcset="/static/assets/[email protected] 2x" alt="Yoti"/>
17+
</div>
18+
<div class="box">
19+
<h2><a href="/did/">Home</a></h2>
20+
<h3>Oops, something went wrong.</h3>
21+
<h3>Error:</h3>
22+
<span th:text="${error}"></span>
23+
<div th:if="${jsonError}">
24+
<pre id="error-json" style="white-space: pre-wrap;"></pre>
25+
26+
<script th:inline="javascript">
27+
document.getElementById("error-json").textContent = JSON.stringify(/*[(${jsonError})]*/ '', null, 2);
28+
</script>
29+
</div>
30+
</div>
31+
</section>
32+
</main>
33+
</body>
34+
35+
</html>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<html xmlns:th="http://www.thymeleaf.org" lang="en">
2+
3+
<head>
4+
<meta charset="utf-8"/>
5+
<title>Run Match</title>
6+
<link rel="stylesheet" type="text/css" th:href="@{/static/index.css}"/>
7+
<link rel="shortcut icon" type="image/x-icon" href="https://www.yoti.com/wp-content/uploads/yoti-favicon.png"/>
8+
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet"/>
9+
</head>
10+
11+
<body class="yoti-body">
12+
<main>
13+
<section class="yoti-top-section">
14+
<div class="yoti-logo-section">
15+
<img class="yoti-logo-image" th:src="@{/static/assets/logo.png}" th:srcset="@{/static/assets/[email protected] 2x}" alt="Yoti"/>
16+
</div>
17+
<h1 class="yoti-top-header">Run Digital ID Match</h1>
18+
<form th:action="@{/did/match}" th:object="${matchInputForm}" method="post" class="yoti-login-dialog" onsubmit="return validateHttpMethod()">
19+
<h2 class="yoti-login-dialog-header">Enter Match Details:</h2>
20+
<label for="valueToMatch">Value to Match:</label>
21+
<input class="yoti-input" type="text" th:field="*{value}" id="valueToMatch" placeholder="Value to Match" required/>
22+
<h2 class="yoti-login-dialog-header" style="margin-top: 20px;">Notification Details:</h2>
23+
<label for="notificationEndpoint">Notification Endpoint URL:</label>
24+
<input class="yoti-input" type="text" th:field="*{endpoint}" id="notificationEndpoint" placeholder="Notification Endpoint URL" required/>
25+
<label for="httpMethod">HTTP Method (e.g., POST):</label>
26+
<input class="yoti-input" type="text" th:field="*{httpMethod}" id="httpMethod" placeholder="HTTP Method (e.g., POST)" required/>
27+
<div class="yoti-input" style="display: flex; align-items: center; border: none; padding-left: 0;">
28+
<input type="checkbox" th:field="*{verifyTls}" id="verifyTls" style="margin-right: 10px; height: auto; width: auto;"/>
29+
<label for="verifyTls">Verify TLS</label>
30+
</div>
31+
<h3 class="yoti-login-dialog-header" style="font-size: 1em; margin-top: 1em;">Headers:</h3>
32+
<div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 10px;">
33+
<label for="headerKey1">Header Key 1:</label>
34+
<input class="yoti-input" type="text" th:field="*{headerKeys[0]}" id="headerKey1" placeholder="Header Key 1"/>
35+
<label for="headerValue1">Header Value 1:</label>
36+
<input class="yoti-input" type="text" th:field="*{headerValues[0]}" id="headerValue1" placeholder="Header Value 1"/>
37+
<label for="headerKey2">Header Key 2:</label>
38+
<input class="yoti-input" type="text" th:field="*{headerKeys[1]}" id="headerKey2" placeholder="Header Key 2"/>
39+
<label for="headerValue2">Header Value 2:</label>
40+
<input class="yoti-input" type="text" th:field="*{headerValues[1]}" id="headerValue2" placeholder="Header Value 2"/>
41+
</div>
42+
43+
<div class="yoti-login-actions" style="justify-content: center;">
44+
<button type="submit" class="yoti-login-button">Run match</button>
45+
</div>
46+
</form>
47+
</section>
48+
</main>
49+
<script>
50+
function validateHttpMethod() {
51+
const input = document.getElementById('httpMethod');
52+
let value = input.value.trim().toUpperCase();
53+
const allowedMethods = ['POST', 'PUT', 'PATCH', 'GET', 'DELETE'];
54+
55+
if (value && !allowedMethods.includes(value)) {
56+
alert('Invalid HTTP Method. Allowed values are: POST, PUT, PATCH, GET, DELETE');
57+
input.value = '';
58+
return false;
59+
}
60+
input.value = value;
61+
return true;
62+
}
63+
</script>
64+
</body>
65+
66+
</html>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<html xmlns:th="http://www.thymeleaf.org" lang="en">
2+
3+
<head>
4+
<meta charset="utf-8"/>
5+
<title>Match Result</title>
6+
<link rel="stylesheet" type="text/css" th:href="@{/static/index.css}"/>
7+
<link rel="shortcut icon" type="image/x-icon" href="https://www.yoti.com/wp-content/uploads/yoti-favicon.png"/>
8+
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700" rel="stylesheet"/>
9+
<style>
10+
.match-id {
11+
word-break: break-all;
12+
}
13+
</style>
14+
</head>
15+
16+
<body class="yoti-body">
17+
<main>
18+
<section class="yoti-top-section">
19+
<div class="yoti-logo-section">
20+
<img class="yoti-logo-image" th:src="@{/static/assets/logo.png}"
21+
th:srcset="@{/static/assets/[email protected] 2x}" alt="Yoti"/>
22+
</div>
23+
<h1 class="yoti-top-header">Match Result</h1>
24+
<div class="yoti-login-dialog" style="text-align: center;">
25+
<p>Match ID: <strong class="match-id" th:text="${matchId}">ID_HERE</strong></p>
26+
<p>Match Result: <strong th:text="${matchResult}">RESULT_HERE</strong></p>
27+
<div class="yoti-login-actions" style="justify-content: center; margin-top: 20px;">
28+
<a th:href="@{/did/}" class="yoti-login-button"
29+
style="text-decoration: none; text-align: center; line-height: 50px; display: inline-block; padding-left: 10px; padding-right: 10px;">Run
30+
Another Match</a>
31+
</div>
32+
</div>
33+
</section>
34+
</main>
35+
</body>
36+
37+
</html>

0 commit comments

Comments
 (0)