Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotfix/v1.0.0 3 #26

Merged
merged 41 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
41a4f2d
Change the versioning of the endpoints from v2 to v1.
rubenmodamioin2 Mar 26, 2024
4c2975e
Update CHANGELOG.md
rubenmodamioin2 Mar 26, 2024
c4dc818
ADD dynamic VP expiration
jiabowangin2 Mar 28, 2024
3a7d691
FIX application yaml
jiabowangin2 Mar 28, 2024
eb41519
FIX test
jiabowangin2 Mar 28, 2024
a14f249
ADD tests
jiabowangin2 Mar 28, 2024
f802145
Merge remote-tracking branch 'origin/hotfix/v1.1.0' into hotfix/v1.1.0-2
jiabowangin2 Apr 2, 2024
a90762b
FIX version and ADD changelog
jiabowangin2 Apr 2, 2024
c9ea3a8
CHANGE version to v1.1.1
jiabowangin2 Apr 2, 2024
183f089
changes
rubenmodamioin2 Apr 3, 2024
3941584
Update application.yml
rubenmodamioin2 Apr 3, 2024
0330856
Update VerifiablePresentationController.java
rubenmodamioin2 Apr 3, 2024
b0ac4b3
return an empty list instead of an error
rubenmodamioin2 Apr 4, 2024
332c98c
changes
rubenmodamioin2 Apr 4, 2024
f4f6513
Merge branch 'main' into hotfix/v1.0.0-3
rubenmodamioin2 Apr 4, 2024
1f02eba
Update build.gradle
rubenmodamioin2 Apr 4, 2024
c1bf410
Update VerifiablePresentationControllerTest.java
rubenmodamioin2 Apr 4, 2024
b5ad629
update zoneTIme
rubenmodamioin2 Apr 4, 2024
353419d
Update status code to 404 when user don't have credentials
rubenmodamioin2 Apr 4, 2024
0d0724f
Solve requested changes
rubenmodamioin2 Apr 4, 2024
877c2f0
Merge branch 'hotfix/v1.0.0-3' into hotfix/v1.0.0-4
rubenmodamioin2 Apr 4, 2024
630cf11
dome changes
rubenmodamioin2 Apr 4, 2024
8164cdd
Test
rubenmodamioin2 Apr 4, 2024
5cd6027
test
rubenmodamioin2 Apr 4, 2024
b42b10b
Added a exception to ExceptionHandler
rubenmodamioin2 Apr 5, 2024
3bcd0df
Rename exception
rubenmodamioin2 Apr 5, 2024
9fda825
rename property
rubenmodamioin2 Apr 5, 2024
e6528c1
Update exception
rubenmodamioin2 Apr 5, 2024
1e1762c
Update DEFAULT_VC_TYPES_FOR_DOME_VERIFIER
rubenmodamioin2 Apr 8, 2024
ff06e23
Return the available format without modifiying the original vc_json
rubenmodamioin2 Apr 9, 2024
32d1013
Tests
rubenmodamioin2 Apr 9, 2024
abc1e24
Test for patterns
rubenmodamioin2 Apr 9, 2024
5331044
test
rubenmodamioin2 Apr 9, 2024
ff686b6
Revert "test"
rubenmodamioin2 Apr 9, 2024
6e13367
test
rubenmodamioin2 Apr 9, 2024
02d684f
Add again the LearCredential type because we will be able to login on…
rubenmodamioin2 Apr 10, 2024
a05bfa4
Changes
rubenmodamioin2 Apr 10, 2024
466fe49
Update UserDataServiceImpl.java
rubenmodamioin2 Apr 10, 2024
46d5058
Use only one model of credentialBasicInfo
rubenmodamioin2 Apr 12, 2024
61407ce
Update application.yml
rubenmodamioin2 Apr 12, 2024
287ad84
added todo
rubenmodamioin2 Apr 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = 'es.in2'
version = '1.1.1'
version = '1.1.2'

java {
sourceCompatibility = '17'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package es.in2.wallet.application.service;

import es.in2.wallet.domain.model.VcSelectorRequest;
import es.in2.wallet.domain.model.VcSelectorResponse;
import reactor.core.publisher.Mono;


public interface DomeAttestationExchangeService {
Mono<Void> getSelectableCredentialsRequiredToBuildThePresentation(String processId, String authorizationToken, String qrContent);
Mono<VcSelectorRequest> getSelectableCredentialsRequiredToBuildThePresentation(String processId, String authorizationToken, String qrContent);
public Mono<Void> buildAndSendVerifiablePresentationWithSelectedVCsForDome(String processId, String authorizationToken, VcSelectorResponse vcSelectorResponse);
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
package es.in2.wallet.application.service.impl;

import es.in2.wallet.application.service.DomeAttestationExchangeService;
import es.in2.wallet.domain.model.VcSelectorRequest;
import es.in2.wallet.domain.model.VcSelectorResponse;
import es.in2.wallet.domain.service.AuthorizationRequestService;
import es.in2.wallet.domain.service.DidKeyGeneratorService;
import es.in2.wallet.domain.service.AuthorizationResponseService;
import es.in2.wallet.domain.service.DomeVpTokenService;
import es.in2.wallet.domain.service.PresentationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class DomeAttestationExchangeServiceImpl implements DomeAttestationExchangeService {
private final AuthorizationRequestService authorizationRequestService;
private final DomeVpTokenService domeVpTokenService;
private final DidKeyGeneratorService didKeyGeneratorService;
private final PresentationService presentationService;
private final AuthorizationResponseService authorizationResponseService;


@Override
public Mono<Void> getSelectableCredentialsRequiredToBuildThePresentation(String processId, String authorizationToken, String qrContent) {
public Mono<VcSelectorRequest> getSelectableCredentialsRequiredToBuildThePresentation(String processId, String authorizationToken, String qrContent) {
log.info("ProcessID: {} - Processing a Verifiable Credential Login Request", processId);
// Get Authorization Request executing the VC Login Request
return authorizationRequestService.getAuthorizationRequestFromAuthorizationRequestClaims(processId, qrContent)
// Check which Verifiable Credentials are selectable
.flatMap(authorizationRequest -> domeVpTokenService.getVpRequest(processId,authorizationToken,authorizationRequest));
}
@Override
public Mono<Void> buildAndSendVerifiablePresentationWithSelectedVCsForDome(String processId, String authorizationToken, VcSelectorResponse vcSelectorResponse) {
// Get the Verifiable Credentials which will be used for the Presentation from the Wallet Data Service
return presentationService.createEncodedVerifiablePresentationForDome(processId,authorizationToken,vcSelectorResponse)
.flatMap(vpToken -> authorizationResponseService.sendAuthorizationResponseForDome(vpToken,vcSelectorResponse));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class UserDataUseCaseServiceImpl implements UserDataUseCaseService {
public Mono<List<CredentialsBasicInfoWithExpirationDate>> getUserVCs(String processId, String userId) {
return brokerService.getEntityById(processId, userId).flatMap(optionalEntity -> optionalEntity.map(userDataService::getUserVCsInJson).orElseGet(() -> {
log.error("User with ID {} has no entity or credentials yet.", userId);
return Mono.error(new RuntimeException("There's no credential available."));
return Mono.just(List.of());
})).doOnSuccess(list -> log.info("Retrieved VCs in JSON for userId: {}", userId)).onErrorResume(Mono::error);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

public interface AuthorizationResponseService {
Mono<String> buildAndPostAuthorizationResponseWithVerifiablePresentation(String processId, VcSelectorResponse vcSelectorResponse, String verifiablePresentation, String authorizationToken) throws JsonProcessingException;
Mono<Void> sendAuthorizationResponseForDome(String vpToken, VcSelectorResponse vcSelectorResponse);
rubenmodamioin2 marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package es.in2.wallet.domain.service;

import es.in2.wallet.domain.model.AuthorizationRequest;
import es.in2.wallet.domain.model.VcSelectorRequest;
import reactor.core.publisher.Mono;

public interface DomeVpTokenService {
Mono<Void> getVpRequest(String processId, String authorizationToken, AuthorizationRequest authorizationRequest);
Mono<VcSelectorRequest> getVpRequest(String processId, String authorizationToken, AuthorizationRequest authorizationRequest);

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,13 @@
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;

import static es.in2.wallet.domain.util.ApplicationUtils.postRequest;
import static es.in2.wallet.domain.util.MessageUtils.CONTENT_TYPE;
import static es.in2.wallet.domain.util.MessageUtils.CONTENT_TYPE_URL_ENCODED_FORM;


@Slf4j
@Service
Expand All @@ -43,6 +44,19 @@ public Mono<String> buildAndPostAuthorizationResponseWithVerifiablePresentation(
.flatMap(presentationSubmissionString -> postAuthorizationResponse(processId, vcSelectorResponse, verifiablePresentation, presentationSubmissionString, authorizationToken));
}

@Override
public Mono<Void> sendAuthorizationResponseForDome(String vpToken, VcSelectorResponse vcSelectorResponse) {
rubenmodamioin2 marked this conversation as resolved.
Show resolved Hide resolved
String body = "vp_token=" + vpToken;
List<Map.Entry<String, String>> headers = new ArrayList<>();
headers.add(new AbstractMap.SimpleEntry<>(CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED_FORM));

String urlWithState = vcSelectorResponse.redirectUri() + "?state=" + vcSelectorResponse.state();

return postRequest(urlWithState, headers,body)
oriolcanades marked this conversation as resolved.
Show resolved Hide resolved
.onErrorResume(e -> Mono.error(new FailedCommunicationException("Error while sending Vp Token Response")))
.then();
}

private Mono<DescriptorMap> generateDescriptorMapping(String verifiablePresentationString) throws JsonProcessingException {
// Parse the Verifiable Presentation
return parseVerifiablePresentationFromString(verifiablePresentationString)
Expand Down Expand Up @@ -161,7 +175,7 @@ private Mono<String> postAuthorizationResponse(String processId, VcSelectorRespo
String verifiablePresentation, String presentationSubmissionString, String authorizationToken) {
// Headers
List<Map.Entry<String, String>> headers = List.of(
Map.entry(MessageUtils.CONTENT_TYPE, MessageUtils.CONTENT_TYPE_URL_ENCODED_FORM),
Map.entry(CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED_FORM),
Map.entry(MessageUtils.HEADER_AUTHORIZATION, MessageUtils.BEARER + authorizationToken));
// Build URL encoded form data request body
Map<String, String> formDataMap = Map.of(
Expand All @@ -173,7 +187,7 @@ private Mono<String> postAuthorizationResponse(String processId, VcSelectorRespo
.map(entry -> URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8) + "=" + URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
// Post request
return ApplicationUtils.postRequest(vcSelectorResponse.redirectUri(), headers, xWwwFormUrlencodedBody)
return postRequest(vcSelectorResponse.redirectUri(), headers, xWwwFormUrlencodedBody)
oriolcanades marked this conversation as resolved.
Show resolved Hide resolved
.doOnSuccess(response -> log.info("ProcessID: {} - Authorization Response: {}", processId, response))
.onErrorResume(e -> Mono.error(new FailedCommunicationException("Error while fetching Credential Issuer Metadata from the Issuer")));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package es.in2.wallet.domain.service.impl;

import es.in2.wallet.application.port.BrokerService;
import es.in2.wallet.domain.exception.FailedCommunicationException;
import es.in2.wallet.domain.model.AuthorizationRequest;
import es.in2.wallet.domain.model.VcSelectorResponse;
import es.in2.wallet.domain.model.VcSelectorRequest;
import es.in2.wallet.domain.service.DomeVpTokenService;
import es.in2.wallet.domain.service.PresentationService;
import es.in2.wallet.domain.service.UserDataService;
Expand All @@ -12,25 +11,16 @@
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static es.in2.wallet.domain.util.ApplicationUtils.getUserIdFromToken;
import static es.in2.wallet.domain.util.ApplicationUtils.postRequest;
import static es.in2.wallet.domain.util.MessageUtils.CONTENT_TYPE;
import static es.in2.wallet.domain.util.MessageUtils.CONTENT_TYPE_URL_ENCODED_FORM;

@Slf4j
@Service
@RequiredArgsConstructor
public class DomeVpTokenServiceImpl implements DomeVpTokenService {
private final UserDataService userDataService;
private final BrokerService brokerService;
private final PresentationService presentationService;

/**
* Initiates the process to exchange the authorization token and JWT for a VP Token Request,
Expand All @@ -40,7 +30,7 @@ public class DomeVpTokenServiceImpl implements DomeVpTokenService {
* @param authorizationToken The authorization token provided by the client.
*/
@Override
public Mono<Void> getVpRequest(String processId, String authorizationToken, AuthorizationRequest authorizationRequest) {
public Mono<VcSelectorRequest> getVpRequest(String processId, String authorizationToken, AuthorizationRequest authorizationRequest) {
return completeVpTokenExchange(processId, authorizationToken, authorizationRequest)
.doOnSuccess(tokenResponse -> log.info("ProcessID: {} - Token Response: {}", processId, tokenResponse));
}
Expand All @@ -49,46 +39,33 @@ public Mono<Void> getVpRequest(String processId, String authorizationToken, Auth
/**
* Completes the VP Token exchange process by building a VP token response and extracting query parameters from it.
*/
private Mono<Void> completeVpTokenExchange(String processId, String authorizationToken, AuthorizationRequest authorizationRequest) {
private Mono<VcSelectorRequest> completeVpTokenExchange(String processId, String authorizationToken, AuthorizationRequest authorizationRequest) {
return buildVpTokenResponse(processId,authorizationToken,authorizationRequest);
}

private Mono<Void> buildVpTokenResponse(String processId, String authorizationToken, AuthorizationRequest authorizationRequest) {
private Mono<VcSelectorRequest> buildVpTokenResponse(String processId, String authorizationToken, AuthorizationRequest authorizationRequest) {
List<String> vcTypeList = List.of("LegalPersonCredential");

return buildBase64VerifiablePresentationByVcTypeList(processId, authorizationToken, vcTypeList)
.flatMap(vp -> sendVpTokenResponse(vp,authorizationRequest));
return buildVCSelectorRequest(processId, authorizationToken, vcTypeList,authorizationRequest);
}
/**
* Sends the VP Token response to the redirect URI specified in the JWT, as an application/x-www-form-urlencoded payload.
* This includes the VP token, presentation submission, and state parameters.
*/
private Mono<Void> sendVpTokenResponse(String vpToken, AuthorizationRequest authorizationRequest) {
String body = "vp_token=" + vpToken;
List<Map.Entry<String, String>> headers = new ArrayList<>();
headers.add(new AbstractMap.SimpleEntry<>(CONTENT_TYPE, CONTENT_TYPE_URL_ENCODED_FORM));

String urlWithState = authorizationRequest.redirectUri() + "?state=" + authorizationRequest.state();

return postRequest(urlWithState, headers,body)
.onErrorResume(e -> Mono.error(new FailedCommunicationException("Error while sending Vp Token Response")))
.then();

}

/**
* Builds a signed JWT Verifiable Presentation by extracting user data and credentials based on the VC type list provided.
*/
private Mono<String> buildBase64VerifiablePresentationByVcTypeList(String processId, String authorizationToken, List<String> vcTypeList) {
private Mono<VcSelectorRequest> buildVCSelectorRequest(String processId, String authorizationToken, List<String> vcTypeList, AuthorizationRequest authorizationRequest) {
return getUserIdFromToken(authorizationToken)
.flatMap(userId -> brokerService.getEntityById(processId, userId))
.flatMap(optionalEntity -> optionalEntity
.map(entity ->
userDataService.getSelectableVCsByVcTypeList(vcTypeList, entity)
.flatMap(list -> {
log.debug(list.toString());
VcSelectorResponse vcSelectorResponse = VcSelectorResponse.builder().selectedVcList(list).build();
return presentationService.createEncodedVerifiablePresentationForDome(processId, authorizationToken, vcSelectorResponse);
VcSelectorRequest vcSelectorRequest = VcSelectorRequest.builder().selectableVcList(list)
.redirectUri(authorizationRequest.redirectUri())
.state(authorizationRequest.state())
.build();
return Mono.just(vcSelectorRequest);
})
)
.orElseGet(() ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ private Mono<String> createVerifiablePresentationForDome(String processId, Strin
.map(entity -> getVerifiableCredentials(entity,selectedVcList, VC_JSON)
.flatMap(this::createEncodedPresentation)
// Log success
.doOnSuccess(verifiablePresentation -> log.info("ProcessID: {} - Verifiable Presentation created successfully: {}", processId, verifiablePresentation))
.doOnSuccess(verifiablePresentation -> log.info("ProcessID: {} - Verifiable Presentation created successfully for dome: {}", processId, verifiablePresentation))
rubenmodamioin2 marked this conversation as resolved.
Show resolved Hide resolved
// Handle errors
.onErrorResume(e -> {
log.error("Error in creating Verifiable Presentation: ", e);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/es/in2/wallet/domain/util/MessageUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ private MessageUtils() {
public static final Pattern OPENID_CREDENTIAL_OFFER_PATTERN = Pattern.compile("openid-credential-offer://\\S*");
public static final Pattern EBSI_CREDENTIAL_OFFER_PATTERN = Pattern.compile("\\S*(conformance.ebsi)\\S*");
public static final Pattern DOME_LOGIN_REQUEST_PATTERN = Pattern.compile("\\S*(did:web:dome-marketplace.org)\\S*");
public static final Pattern DOME_REDIRECT_URI_PATTERN = Pattern.compile("\\S*(dome-marketplace.org)\\S*");
public static final Pattern OPENID_AUTHENTICATION_REQUEST_PATTERN = Pattern.compile("openid://\\S*");

public static final String ISO_8601_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ssX";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package es.in2.wallet.infrastructure.core.controller;

import es.in2.wallet.infrastructure.core.config.SwaggerConfig;
import es.in2.wallet.application.service.AttestationExchangeService;
import es.in2.wallet.application.service.DomeAttestationExchangeService;
import es.in2.wallet.application.service.TurnstileAttestationExchangeService;
import es.in2.wallet.domain.model.CredentialsBasicInfo;
import es.in2.wallet.domain.model.VcSelectorResponse;
import es.in2.wallet.infrastructure.core.config.SwaggerConfig;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,6 +19,7 @@
import java.util.UUID;

import static es.in2.wallet.domain.util.ApplicationUtils.getCleanBearerToken;
import static es.in2.wallet.domain.util.MessageUtils.DOME_REDIRECT_URI_PATTERN;

@RestController
@RequestMapping("/api/v1/vp")
Expand All @@ -27,6 +29,7 @@ public class VerifiablePresentationController {

private final TurnstileAttestationExchangeService turnstileAttestationExchangeService;
private final AttestationExchangeService attestationExchangeService;
private final DomeAttestationExchangeService domeAttestationExchangeService;
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@Operation(
Expand All @@ -39,8 +42,14 @@ public Mono<Void> createVerifiablePresentation(@RequestHeader(HttpHeaders.AUTHOR
String processId = UUID.randomUUID().toString();
MDC.put("processId", processId);
return getCleanBearerToken(authorizationHeader)
.flatMap(authorizationToken ->
attestationExchangeService.buildVerifiablePresentationWithSelectedVCs(processId, authorizationToken, vcSelectorResponse));
.flatMap(authorizationToken ->{
if (DOME_REDIRECT_URI_PATTERN.matcher(vcSelectorResponse.redirectUri()).matches()){
rubenmodamioin2 marked this conversation as resolved.
Show resolved Hide resolved
return domeAttestationExchangeService.buildAndSendVerifiablePresentationWithSelectedVCsForDome(processId,authorizationToken,vcSelectorResponse);
}
else {
return attestationExchangeService.buildVerifiablePresentationWithSelectedVCs(processId, authorizationToken, vcSelectorResponse);
}
}).doOnSuccess(aVoid -> log.info("Attestation exchange successful"));
}
@PostMapping("/cbor")
@ResponseStatus(HttpStatus.CREATED)
Expand Down
52 changes: 28 additions & 24 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,33 +76,37 @@ hashicorp:
host: localhost
port: 8201
scheme: http
token: "hvs.cvkSZ1qirXWnLvFyugyKbSsT"
token: "hvs.60TonjabQzUoOPF3HTqIPLnV"

azure:
app:
endpoint: ${APP_CONFIG_ENDPOINT}
label:
global: service-discovery
key-vault:
endpoint: "your-endpoint"
#azure:
# app:
# endpoint: ${APP_CONFIG_ENDPOINT}
# label:
# global: service-discovery
# key-vault:
# endpoint: "your-endpoint"

# These properties will be removed in the future
auth-server:
external-url:
scheme: "http"
domain: "localhost"
port: "8080"
path: "/cross-keycloak/realms/EAAProvider"
internal-url:
scheme: "http"
domain: "localhost"
port: "8080"
path: "/realms/EAAProvider"
token-url:
scheme: "http"
domain: "localhost"
port: "8080"
path: "/realms/EAAProvider/verifiable-credential/did:key:z6MkqmaCT2JqdUtLeKah7tEVfNXtDXtQyj4yxEgV11Y5CqUa/token"
#auth-server:
rubenmodamioin2 marked this conversation as resolved.
Show resolved Hide resolved
# external-url:
# scheme: "http"
# domain: "localhost"
# port: "8080"
# path: "/cross-keycloak/realms/EAAProvider"
# internal-url:
# scheme: "http"
# domain: "localhost"
# port: "8080"
# path: "/realms/EAAProvider"
# token-url:
# scheme: "http"
# domain: "localhost"
# port: "8080"
# path: "/realms/EAAProvider/verifiable-credential/did:key:z6MkqmaCT2JqdUtLeKah7tEVfNXtDXtQyj4yxEgV11Y5CqUa/token"

verifiable-presentation:
expiration-time: 3
expiration-unit: MINUTES #DAYS HOURS MINUTES SECONDS

verifiable-presentation:
expiration-time: 3
Expand Down
Loading
Loading