diff --git a/src/components/Credentials/CredentialImage.js b/src/components/Credentials/CredentialImage.js
index d0f2e4a1..d1e72a89 100644
--- a/src/components/Credentials/CredentialImage.js
+++ b/src/components/Credentials/CredentialImage.js
@@ -1,11 +1,9 @@
import { useState, useEffect, useContext } from "react";
import StatusRibbon from '../../components/Credentials/StatusRibbon';
import ContainerContext from '../../context/ContainerContext';
-import RenderSvgTemplate from "./RenderSvgTemplate";
const CredentialImage = ({ credential, className, onClick, showRibbon = true }) => {
const [parsedCredential, setParsedCredential] = useState(null);
- const [svgImage, setSvgImage] = useState(null);
const container = useContext(ContainerContext);
useEffect(() => {
@@ -20,25 +18,13 @@ const CredentialImage = ({ credential, className, onClick, showRibbon = true })
}, [credential, container]);
- const handleSvgGenerated = (svgUri) => {
- setSvgImage(svgUri);
- };
-
return (
<>
- {parsedCredential && parsedCredential.credentialImage && parsedCredential.credentialImage.credentialImageSvgTemplateURL ? (
- <>
-
- {parsedCredential && svgImage && (
-

- )}
- >
- ) : parsedCredential && parsedCredential.credentialImage && parsedCredential.credentialImage.credentialImageURL && (
+ {parsedCredential && parsedCredential.credentialImage && (

)}
-
{parsedCredential && showRibbon &&
-
+
}
>
);
diff --git a/src/components/Credentials/RenderCustomSvgTemplate.js b/src/components/Credentials/RenderCustomSvgTemplate.js
new file mode 100644
index 00000000..64dfa96d
--- /dev/null
+++ b/src/components/Credentials/RenderCustomSvgTemplate.js
@@ -0,0 +1,62 @@
+import axios from 'axios';
+import jsonpointer from 'jsonpointer';
+import { formatDate } from '../../functions/DateFormat';
+import customTemplate from '../../assets/images/custom_template.svg';
+
+async function getBase64Image(url) {
+ if (!url) return null;
+ try {
+ const response = await axios.get(url, { responseType: 'blob' });
+ const blob = response.data;
+
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onloadend = () => resolve(reader.result);
+ reader.onerror = reject;
+ reader.readAsDataURL(blob);
+ });
+ } catch (error) {
+ console.error("Failed to load image", url);
+ return null;
+ }
+}
+
+const renderCustomSvgTemplate = async ({ beautifiedForm, name, description, logoURL, logoAltText, backgroundColor, textColor, backgroundImageURL }) => {
+ try {
+ const response = await fetch(customTemplate);
+ if (!response.ok) throw new Error("Failed to fetch SVG template");
+
+ let svgContent = await response.text();
+
+ const backgroundImageBase64 = await getBase64Image(backgroundImageURL);
+ const logoBase64 = await getBase64Image(logoURL);
+
+ svgContent = svgContent
+ .replace(/{{backgroundColor}}/g, backgroundColor)
+ .replace(
+ /{{backgroundImageBase64}}/g,
+ backgroundImageBase64
+ ? `
`
+ : ''
+ )
+ .replace(
+ /{{logoBase64}}/g,
+ logoBase64
+ ? `
${logoAltText}`
+ : ''
+ )
+ .replace(/{{name}}/g, name)
+ .replace(/{{textColor}}/g, textColor)
+ .replace(/{{description}}/g, description);
+
+ const expiryDate = jsonpointer.get(beautifiedForm, "/expiry_date");
+ svgContent = svgContent.replace(/{{\/expiry_date}}/g, expiryDate ? `Expiry Date: ${formatDate(expiryDate, 'date')}` : '');
+
+ return `data:image/svg+xml;utf8,${encodeURIComponent(svgContent)}`;
+ } catch (error) {
+ console.error("Error rendering SVG template", error);
+ return null;
+ }
+};
+
+export default renderCustomSvgTemplate;
diff --git a/src/components/Credentials/RenderSvgTemplate.js b/src/components/Credentials/RenderSvgTemplate.js
index 7589a8fa..a42777cc 100644
--- a/src/components/Credentials/RenderSvgTemplate.js
+++ b/src/components/Credentials/RenderSvgTemplate.js
@@ -1,48 +1,34 @@
-import { useState, useEffect } from 'react';
+import axios from 'axios';
import jsonpointer from 'jsonpointer';
import { formatDate } from '../../functions/DateFormat';
-const RenderSvgTemplate = ({ credential, onSvgGenerated }) => {
- const [svgContent, setSvgContent] = useState(null);
-
- useEffect(() => {
- const fetchSvgContent = async () => {
- try {
- const response = await fetch(credential.credentialImage.credentialImageSvgTemplateURL);
- if (!response.ok) {
- throw new Error(`Failed to fetch SVG from ${credential.credentialImage.credentialImageSvgTemplateURL}`);
- }
-
- const svgText = await response.text();
- setSvgContent(svgText);
- } catch (error) {
- console.error(error);
- }
- };
-
- if (credential.credentialImage.credentialImageSvgTemplateURL) {
- fetchSvgContent();
+const renderSvgTemplate = async ({ beautifiedForm, credentialImageSvgTemplateURL }) => {
+ let svgContent = null;
+ try {
+ const response = await axios.get(credentialImageSvgTemplateURL);
+ if (response.status !== 200) {
+ throw new Error(`Failed to fetch SVG`);
}
- }, [credential.credentialImage.credentialImageSvgTemplateURL]);
-
- useEffect(() => {
- if (svgContent) {
- const regex = /{{([^}]+)}}/g;
-
- const replacedSvgText = svgContent.replace(regex, (_match, content) => {
- let res = jsonpointer.get(credential.beautifiedForm, content.trim());
- if (res !== undefined) {
- res = formatDate(res, 'date');
- return res;
- }
- return '-';
- });
- const dataUri = `data:image/svg+xml;utf8,${encodeURIComponent(replacedSvgText)}`;
- onSvgGenerated(dataUri);
- }
- }, [svgContent, credential.beautifiedForm, onSvgGenerated]);
+ svgContent = response.data;
+ } catch (error) {
+ return null; // Return null if fetching fails
+ }
+
+ if (svgContent) {
+ const regex = /{{([^}]+)}}/g;
+ const replacedSvgText = svgContent.replace(regex, (_match, content) => {
+ let res = jsonpointer.get(beautifiedForm, content.trim());
+ if (res !== undefined) {
+ res = formatDate(res, 'date');
+ return res;
+ }
+ return '-';
+ });
+ const dataUri = `data:image/svg+xml;utf8,${encodeURIComponent(replacedSvgText)}`;
+ return dataUri; // Return the data URI for the SVG
+ }
- return null;
+ return null; // Return null if no SVG content is available
};
-export default RenderSvgTemplate;
+export default renderSvgTemplate;
diff --git a/src/components/Credentials/StatusRibbon.js b/src/components/Credentials/StatusRibbon.js
index 4c6bb844..2a743b03 100644
--- a/src/components/Credentials/StatusRibbon.js
+++ b/src/components/Credentials/StatusRibbon.js
@@ -1,27 +1,11 @@
// StatusRibbon.js
-import React, { useEffect, useState, useContext } from 'react';
+import React from 'react';
import { useTranslation } from 'react-i18next';
-import ContainerContext from '../../context/ContainerContext';
-import {CheckExpired} from '../../functions/CheckExpired';
+import { CheckExpired } from '../../functions/CheckExpired';
-const StatusRibbon = ({ credential }) => {
+const StatusRibbon = ({ parsedCredential }) => {
const { t } = useTranslation();
- const [parsedCredential, setParsedCredential] = useState(null);
- const container = useContext(ContainerContext);
-
- useEffect(() => {
- if (container) {
- container.credentialParserRegistry.parse(credential).then((c) => {
- if ('error' in c) {
- return;
- }
- setParsedCredential(c.beautifiedForm);
- });
- }
-
- }, [credential, container]);
-
return (
<>
{parsedCredential && CheckExpired(parsedCredential.expiry_date) &&
diff --git a/src/components/Layout/Header.js b/src/components/Layout/Header.js
index 7dd9a2b3..a42f99ed 100644
--- a/src/components/Layout/Header.js
+++ b/src/components/Layout/Header.js
@@ -1,5 +1,4 @@
import React, { useState, useEffect } from 'react';
-import { AiOutlineMenu } from "react-icons/ai";
import { useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import ConnectionStatusIcon from './Navigation/ConnectionStatusIcon';
diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js
index ddeeac0e..7ef41a12 100644
--- a/src/components/Layout/Layout.js
+++ b/src/components/Layout/Layout.js
@@ -9,7 +9,7 @@ const Layout = ({ children }) => {
const toggleSidebar = () => setIsOpen(!isOpen);
return (
-
+
{/* Header */}
diff --git a/src/components/Layout/Navigation/BottomNav.js b/src/components/Layout/Navigation/BottomNav.js
index c63caf37..9b97c435 100644
--- a/src/components/Layout/Navigation/BottomNav.js
+++ b/src/components/Layout/Navigation/BottomNav.js
@@ -1,4 +1,4 @@
-import React, { useContext, useState } from 'react';
+import React, { useContext } from 'react';
import { FaWallet, FaUserCircle } from "react-icons/fa";
import { IoIosAddCircle, IoIosSend } from "react-icons/io";
import { useLocation, useNavigate } from 'react-router-dom';
diff --git a/src/components/Layout/Navigation/Sidebar.js b/src/components/Layout/Navigation/Sidebar.js
index 29440798..e63fbc43 100644
--- a/src/components/Layout/Navigation/Sidebar.js
+++ b/src/components/Layout/Navigation/Sidebar.js
@@ -30,7 +30,7 @@ const NavItem = ({
};
const Sidebar = ({ isOpen, toggle }) => {
- const { isOnline, updateAvailable } = useContext(StatusContext);
+ const { updateAvailable } = useContext(StatusContext);
const { api, logout } = useContext(SessionContext);
const { username, displayName } = api.getSession();
const location = useLocation();
@@ -56,8 +56,8 @@ const Sidebar = ({ isOpen, toggle }) => {
return (
diff --git a/src/components/Popups/RedirectPopup.js b/src/components/Popups/RedirectPopup.js
index 9f6b485a..9674d6f4 100644
--- a/src/components/Popups/RedirectPopup.js
+++ b/src/components/Popups/RedirectPopup.js
@@ -1,9 +1,7 @@
import React, { useState, useEffect } from 'react';
-import Modal from 'react-modal';
import { FaShare } from 'react-icons/fa';
import { useTranslation } from 'react-i18next';
import Button from '../Buttons/Button';
-import Spinner from '../Shared/Spinner';
import PopupLayout from './PopupLayout';
const RedirectPopup = ({ loading, availableCredentialConfigurations, onClose, handleContinue, popupTitle, popupMessage }) => {
@@ -19,7 +17,7 @@ const RedirectPopup = ({ loading, availableCredentialConfigurations, onClose, ha
if (availableCredentialConfigurations) {
setSelectedConfiguration(Object.keys(availableCredentialConfigurations)[0])
}
- }, [])
+ }, [availableCredentialConfigurations, setSelectedConfiguration])
const handleOptionChange = (event) => {
if (availableCredentialConfigurations) {
diff --git a/src/components/Popups/SelectCredentialsPopup.js b/src/components/Popups/SelectCredentialsPopup.js
index 2fc12ce8..ee043201 100644
--- a/src/components/Popups/SelectCredentialsPopup.js
+++ b/src/components/Popups/SelectCredentialsPopup.js
@@ -71,7 +71,6 @@ function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformant
const [selectedCredential, setSelectedCredential] = useState(null);
const container = useContext(ContainerContext);
const screenType = useScreenType();
- const [credentialDisplay, setCredentialDisplay] = useState({});
const [currentSlide, setCurrentSlide] = useState(1);
useEffect(() => {
@@ -115,6 +114,7 @@ function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformant
keys,
setSelectionMap,
setIsOpen,
+ container.credentialParserRegistry,
]);
useEffect(() => {
@@ -197,7 +197,7 @@ function SelectCredentialsPopup({ isOpen, setIsOpen, setSelectionMap, conformant
return (
-
+
{stepTitles && (
diff --git a/src/components/Shared/Slider.js b/src/components/Shared/Slider.js
index dcf5e19a..55c5c30a 100644
--- a/src/components/Shared/Slider.js
+++ b/src/components/Shared/Slider.js
@@ -9,8 +9,8 @@ import { EffectCards } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/effect-cards';
-const Slider = ({ items, renderSlideContent, onSlideChange }) => {
- const [currentSlide, setCurrentSlide] = useState(1);
+const Slider = ({ items, renderSlideContent, onSlideChange, initialSlide = 1 }) => {
+ const [currentSlide, setCurrentSlide] = useState(initialSlide);
const sliderRef = useRef(null);
const { t } = useTranslation();
@@ -33,6 +33,7 @@ const Slider = ({ items, renderSlideContent, onSlideChange }) => {
grabCursor={true}
modules={[EffectCards]}
slidesPerView={1}
+ initialSlide={currentSlide -1}
onSlideChange={(swiper) => {
setCurrentSlide(swiper.activeIndex + 1);
if (onSlideChange) onSlideChange(swiper.activeIndex);
diff --git a/src/components/Shared/Spinner.js b/src/components/Shared/Spinner.js
index 740c0ce2..99f7bc24 100644
--- a/src/components/Shared/Spinner.js
+++ b/src/components/Shared/Spinner.js
@@ -11,7 +11,7 @@ function Spinner() {
}, []);
return (
-
+
diff --git a/src/components/WelcomeTourGuide/WelcomeTourGuide.js b/src/components/WelcomeTourGuide/WelcomeTourGuide.js
index 2483c64a..7fe392d7 100644
--- a/src/components/WelcomeTourGuide/WelcomeTourGuide.js
+++ b/src/components/WelcomeTourGuide/WelcomeTourGuide.js
@@ -21,7 +21,7 @@ const TourGuide = ({ toggleMenu, isOpen }) => {
useEffect(() => {
const getStepSelectorSmallScreen = (stepName) => {
- if (screenType != 'desktop') {
+ if (screenType !== 'desktop') {
return stepName + '-small-screen';
} else {
return stepName;
@@ -76,7 +76,7 @@ const TourGuide = ({ toggleMenu, isOpen }) => {
return {
...step,
action: () => {
- if (screenType != 'desktop') {
+ if (screenType !== 'desktop') {
if (index >= 5 && index <= 6 && !isOpen) {
toggleMenu();
} else if ((index < 5 || index > 6) && isOpen) {
@@ -88,7 +88,7 @@ const TourGuide = ({ toggleMenu, isOpen }) => {
});
setSteps(updatedSteps);
- }, [t, toggleMenu, isOpen]);
+ }, [t, toggleMenu, isOpen, screenType]);
const startTour = () => {
setIsModalOpen(false);
diff --git a/src/context/ContainerContext.tsx b/src/context/ContainerContext.tsx
index bb98f830..59c5dfed 100644
--- a/src/context/ContainerContext.tsx
+++ b/src/context/ContainerContext.tsx
@@ -1,4 +1,4 @@
-import { useEffect, useState, useContext, useMemo, createContext } from "react";
+import { useEffect, useState, useContext, createContext } from "react";
import { DIContainer } from "../lib/DIContainer";
import { IHttpProxy } from "../lib/interfaces/IHttpProxy";
import { IOpenID4VCIClient } from "../lib/interfaces/IOpenID4VCIClient";
@@ -21,8 +21,9 @@ import { parseSdJwtCredential } from "../functions/parseSdJwtCredential";
import { CredentialConfigurationSupported } from "../lib/schemas/CredentialConfigurationSupportedSchema";
import { generateRandomIdentifier } from "../lib/utils/generateRandomIdentifier";
import { fromBase64 } from "../util";
-import defaulCredentialImage from "../assets/images/cred.png";
-import { UserData } from "../api/types";
+import defaultCredentialImage from "../assets/images/cred.png";
+import renderSvgTemplate from "../components/Credentials/RenderSvgTemplate";
+import renderCustomSvgTemplate from "../components/Credentials/RenderCustomSvgTemplate";
import { MDoc } from "@auth0/mdl";
import { deviceResponseParser, mdocPIDParser } from "../lib/utils/mdocPIDParser";
@@ -51,9 +52,15 @@ export const ContainerContextProvider = ({ children }) => {
const [container, setContainer] = useState
(null);
const [isInitialized, setIsInitialized] = useState(false); // New flag
+ useEffect(() => {
+ window.addEventListener('generatedProof', (e) => {
+ setIsInitialized(false);
+ });
+ }, []);
+
useEffect(() => {
const initialize = async () => {
- if (isInitialized || !isLoggedIn || !api || container !== null) return;
+ if (isInitialized || !isLoggedIn || !api) return;
console.log('Initializing container...');
setIsInitialized(true);
@@ -102,47 +109,59 @@ export const ContainerContextProvider = ({ children }) => {
credentialHeader.vctm.display[0][defaultLocale]?.rendering?.svg_templates[0]?.uri
: null;
- let credentialFriendlyName = credentialHeader?.vctm?.display && credentialHeader.vctm.display[0] && credentialHeader.vctm.display[0][defaultLocale] ?
- credentialHeader.vctm.display[0][defaultLocale]?.name
- : null;
+ let credentialFriendlyName = credentialHeader?.vctm?.display?.[0]?.[defaultLocale]?.name
+ || credentialConfigurationSupportedObj?.display?.[0]?.name
+ || "Credential";
- // get credential friendly name from openid credential issuer metadata
- if (!credentialFriendlyName && credentialConfigurationSupportedObj && credentialConfigurationSupportedObj?.display && credentialConfigurationSupportedObj?.display.length > 0) {
- credentialFriendlyName = credentialConfigurationSupportedObj?.display[0]?.name;
- }
+ let credentialDescription = credentialHeader?.vctm?.display?.[0]?.[defaultLocale]?.description
+ || credentialConfigurationSupportedObj?.display?.[0]?.description
+ || "Credential";
- if (!credentialFriendlyName) { // fallback value
- credentialFriendlyName = "Credential";
- }
+ const svgContent = await renderSvgTemplate({ beautifiedForm: result.beautifiedForm, credentialImageSvgTemplateURL: credentialImageSvgTemplateURL });
+
+ const simple = credentialHeader?.vctm?.display?.[0]?.[defaultLocale]?.rendering?.simple;
+ const issuerMetadata = credentialConfigurationSupportedObj?.display?.[0];
- if (credentialImageSvgTemplateURL) {
+ if (svgContent) {
return {
beautifiedForm: result.beautifiedForm,
credentialImage: {
- credentialImageSvgTemplateURL: credentialImageSvgTemplateURL
+ credentialImageURL: svgContent
},
credentialFriendlyName,
}
}
- else if (credentialHeader?.vctm || credentialConfigurationSupportedObj) {
- let credentialImageURL = credentialHeader?.vctm?.display && credentialHeader.vctm.display[0] && credentialHeader.vctm.display[0][defaultLocale] ?
- credentialHeader.vctm.display[0][defaultLocale]?.rendering?.simple?.logo?.uri
- : null;
-
- if (!credentialImageURL) { // provide fallback method through the OpenID credential issuer metadata
- credentialImageURL = credentialConfigurationSupportedObj?.display?.length > 0 ? credentialConfigurationSupportedObj.display[0]?.background_image?.uri : null;
- }
- if (!credentialImageURL) {
- credentialImageURL = credentialConfigurationSupportedObj?.display?.length > 0 ? credentialConfigurationSupportedObj.display[0]?.logo?.url : null;
- }
- if (!credentialImageURL) {
- credentialImageURL = defaulCredentialImage;
+ else if (simple) {
+ // Simple style
+ let logoURL = simple?.logo?.uri || null;
+ let logoAltText = simple?.logo?.alt_text || 'Credential logo';
+ let backgroundColor = simple?.background_color || '#808080';
+ let textColor = simple?.text_color || '#000000';
+
+ const svgCustomContent = await renderCustomSvgTemplate({ beautifiedForm: result.beautifiedForm, name: credentialFriendlyName, description: credentialDescription, logoURL, logoAltText, backgroundColor, textColor, backgroundImageURL: null });
+ return {
+ beautifiedForm: result.beautifiedForm,
+ credentialImage: {
+ credentialImageURL: svgCustomContent || defaultCredentialImage,
+ },
+ credentialFriendlyName,
}
-
+ }
+ else if (issuerMetadata) {
+ // Issuer Metadata style
+ let name = issuerMetadata?.name || 'Credential';
+ let description = issuerMetadata?.description || '';
+ let logoURL = issuerMetadata?.logo?.uri || null;
+ let logoAltText = issuerMetadata?.logo?.alt_text || 'Credential logo';
+ let backgroundColor = issuerMetadata?.background_color || '#808080';
+ let textColor = issuerMetadata?.text_color || '#000000';
+ let backgroundImageURL = issuerMetadata?.background_image?.uri || null;
+
+ const svgCustomContent = await renderCustomSvgTemplate({ beautifiedForm: result.beautifiedForm, name, description, logoURL, logoAltText, backgroundColor, textColor, backgroundImageURL });
return {
beautifiedForm: result.beautifiedForm,
credentialImage: {
- credentialImageURL: credentialImageURL,
+ credentialImageURL: svgCustomContent || defaultCredentialImage,
},
credentialFriendlyName,
}
@@ -151,7 +170,7 @@ export const ContainerContextProvider = ({ children }) => {
return {
beautifiedForm: result.beautifiedForm,
credentialImage: {
- credentialImageURL: defaulCredentialImage,
+ credentialImageURL: defaultCredentialImage,
},
credentialFriendlyName,
}
@@ -192,7 +211,7 @@ export const ContainerContextProvider = ({ children }) => {
cont.resolve('HttpProxy'),
cont.resolve('OpenID4VCIClientStateRepository'),
async (cNonce: string, audience: string, clientId: string): Promise<{ jws: string }> => {
- const [{ proof_jwt }, newPrivateData, keystoreCommit] = await keystore.generateOpenid4vciProof(cNonce, audience, clientId);
+ const [{ proof_jwts: [proof_jwt] }, newPrivateData, keystoreCommit] = await keystore.generateOpenid4vciProofs([{ nonce: cNonce, audience, issuer: clientId }]);
await api.updatePrivateData(newPrivateData);
await keystoreCommit();
return { jws: proof_jwt };
@@ -278,7 +297,7 @@ export const ContainerContextProvider = ({ children }) => {
};
initialize();
- }, [isLoggedIn, api, container, isInitialized]);
+ }, [isLoggedIn, api, container, isInitialized, keystore]);
return (
diff --git a/src/hooks/useCheckURL.ts b/src/hooks/useCheckURL.ts
index 2a0d44d7..c04923be 100644
--- a/src/hooks/useCheckURL.ts
+++ b/src/hooks/useCheckURL.ts
@@ -33,91 +33,92 @@ function useCheckURL(urlToCheck: string): {
const [typeMessagePopup, setTypeMessagePopup] = useState("");
const { t } = useTranslation();
- async function handle(urlToCheck: string) {
- const userHandleB64u = keystore.getUserHandleB64u();
- if (!userHandleB64u) {
- throw new Error("User handle could not be extracted from keystore");
+ useEffect(() => {
+ if (!isLoggedIn || !container || !urlToCheck || !keystore || !api || !t) {
+ return;
}
- const u = new URL(urlToCheck);
- if (u.protocol === 'openid-credential-offer' || u.searchParams.get('credential_offer') || u.searchParams.get('credential_offer_uri')) {
- for (const credentialIssuerIdentifier of Object.keys(container.openID4VCIClients)) {
- await container.openID4VCIClients[credentialIssuerIdentifier].handleCredentialOffer(u.toString(), userHandleB64u)
- .then(({ credentialIssuer, selectedCredentialConfigurationId, issuer_state }) => {
- return container.openID4VCIClients[credentialIssuerIdentifier].generateAuthorizationRequest(selectedCredentialConfigurationId, userHandleB64u, issuer_state);
- })
- .then(({ url, client_id, request_uri }) => {
- if (url) {
- window.location.href = url;
- }
- })
- .catch((err) => console.error(err));
+
+ async function handle(urlToCheck: string) {
+ const userHandleB64u = keystore.getUserHandleB64u();
+ if (!userHandleB64u) {
+ throw new Error("User handle could not be extracted from keystore");
}
- }
- else if (u.searchParams.get('code')) {
- for (const credentialIssuerIdentifier of Object.keys(container.openID4VCIClients)) {
- addLoader();
- await container.openID4VCIClients[credentialIssuerIdentifier].handleAuthorizationResponse(urlToCheck, userHandleB64u)
- .then(() => {
- removeLoader();
- })
- .catch(err => {
- console.log("Error during the handling of authorization response")
- window.history.replaceState({}, '', `${window.location.pathname}`);
- console.error(err)
- removeLoader();
- });
+ const u = new URL(urlToCheck);
+ if (u.protocol === 'openid-credential-offer' || u.searchParams.get('credential_offer') || u.searchParams.get('credential_offer_uri')) {
+ for (const credentialIssuerIdentifier of Object.keys(container.openID4VCIClients)) {
+ await container.openID4VCIClients[credentialIssuerIdentifier].handleCredentialOffer(u.toString(), userHandleB64u)
+ .then(({ credentialIssuer, selectedCredentialConfigurationId, issuer_state }) => {
+ return container.openID4VCIClients[credentialIssuerIdentifier].generateAuthorizationRequest(selectedCredentialConfigurationId, userHandleB64u, issuer_state);
+ })
+ .then(({ url, client_id, request_uri }) => {
+ if (url) {
+ window.location.href = url;
+ }
+ })
+ .catch((err) => console.error(err));
+ }
}
- }
- else {
- await container.openID4VPRelyingParty.handleAuthorizationRequest(urlToCheck).then((result) => {
- if ('err' in result) {
- if (result.err === HandleAuthorizationRequestError.INSUFFICIENT_CREDENTIALS) {
- setTextMessagePopup({ title: `${t('messagePopup.insufficientCredentials.title')}`, description: `${t('messagePopup.insufficientCredentials.description')}` });
- setTypeMessagePopup('error');
- setMessagePopup(true);
- }
- else if (result.err === HandleAuthorizationRequestError.ONLY_ONE_INPUT_DESCRIPTOR_IS_SUPPORTED) {
- setTextMessagePopup({ title: `${t('messagePopup.onlyOneInputDescriptor.title')}`, description: `${t('messagePopup.onlyOneInputDescriptor.description')}` });
- setTypeMessagePopup('error');
- setMessagePopup(true);
- }
- else if (result.err == HandleAuthorizationRequestError.NONTRUSTED_VERIFIER) {
- setTextMessagePopup({ title: `${t('messagePopup.nonTrustedVerifier.title')}`, description: `${t('messagePopup.nonTrustedVerifier.description')}` });
- setTypeMessagePopup('error');
- setMessagePopup(true);
- }
- return;
+ else if (u.searchParams.get('code')) {
+ for (const credentialIssuerIdentifier of Object.keys(container.openID4VCIClients)) {
+ addLoader();
+ await container.openID4VCIClients[credentialIssuerIdentifier].handleAuthorizationResponse(urlToCheck, userHandleB64u)
+ .then(() => {
+ removeLoader();
+ })
+ .catch(err => {
+ console.log("Error during the handling of authorization response")
+ window.history.replaceState({}, '', `${window.location.pathname}`);
+ console.error(err)
+ removeLoader();
+ });
}
- const { conformantCredentialsMap, verifierDomainName } = result;
- const jsonedMap = Object.fromEntries(conformantCredentialsMap);
- window.history.replaceState({}, '', `${window.location.pathname}`);
- setVerifierDomainName(verifierDomainName);
- setConformantCredentialsMap(jsonedMap);
- setShowSelectCredentialsPopup(true);
- }).catch(err => {
- console.log("Failed to handle authorization req");
- console.error(err)
- })
- }
+ }
+ else {
+ await container.openID4VPRelyingParty.handleAuthorizationRequest(urlToCheck).then((result) => {
+ if ('err' in result) {
+ if (result.err === HandleAuthorizationRequestError.INSUFFICIENT_CREDENTIALS) {
+ setTextMessagePopup({ title: `${t('messagePopup.insufficientCredentials.title')}`, description: `${t('messagePopup.insufficientCredentials.description')}` });
+ setTypeMessagePopup('error');
+ setMessagePopup(true);
+ }
+ else if (result.err === HandleAuthorizationRequestError.ONLY_ONE_INPUT_DESCRIPTOR_IS_SUPPORTED) {
+ setTextMessagePopup({ title: `${t('messagePopup.onlyOneInputDescriptor.title')}`, description: `${t('messagePopup.onlyOneInputDescriptor.description')}` });
+ setTypeMessagePopup('error');
+ setMessagePopup(true);
+ }
+ else if (result.err === HandleAuthorizationRequestError.NONTRUSTED_VERIFIER) {
+ setTextMessagePopup({ title: `${t('messagePopup.nonTrustedVerifier.title')}`, description: `${t('messagePopup.nonTrustedVerifier.description')}` });
+ setTypeMessagePopup('error');
+ setMessagePopup(true);
+ }
+ return;
+ }
+ const { conformantCredentialsMap, verifierDomainName } = result;
+ const jsonedMap = Object.fromEntries(conformantCredentialsMap);
+ window.history.replaceState({}, '', `${window.location.pathname}`);
+ setVerifierDomainName(verifierDomainName);
+ setConformantCredentialsMap(jsonedMap);
+ setShowSelectCredentialsPopup(true);
+ }).catch(err => {
+ console.log("Failed to handle authorization req");
+ console.error(err)
+ })
+ }
- const urlParams = new URLSearchParams(window.location.search);
- const state = urlParams.get('state');
- const error = urlParams.get('error');
- if (urlToCheck && isLoggedIn && state && error) {
- window.history.replaceState({}, '', `${window.location.pathname}`);
- const errorDescription = urlParams.get('error_description');
- setTextMessagePopup({ title: error, description: errorDescription });
- setTypeMessagePopup('error');
- setMessagePopup(true);
+ const urlParams = new URLSearchParams(window.location.search);
+ const state = urlParams.get('state');
+ const error = urlParams.get('error');
+ if (urlToCheck && isLoggedIn && state && error) {
+ window.history.replaceState({}, '', `${window.location.pathname}`);
+ const errorDescription = urlParams.get('error_description');
+ setTextMessagePopup({ title: error, description: errorDescription });
+ setTypeMessagePopup('error');
+ setMessagePopup(true);
+ }
}
- }
- useEffect(() => {
- if (!isLoggedIn || !container || !urlToCheck || !keystore || !api || !t) {
- return;
- }
handle(urlToCheck);
- }, [api, keystore, t, urlToCheck, isLoggedIn, container]);
+ }, [api, keystore, t, urlToCheck, isLoggedIn, container, addLoader, removeLoader]);
useEffect(() => {
if (selectionMap) {
diff --git a/src/hooks/useFetchPresentations.js b/src/hooks/useFetchPresentations.js
index 5cb42f29..1c675a0b 100644
--- a/src/hooks/useFetchPresentations.js
+++ b/src/hooks/useFetchPresentations.js
@@ -26,7 +26,7 @@ const useFetchPresentations = (api, credentialId = "", historyId = "") => {
}
if (historyId) {
- vpListFromApi = vpListFromApi.filter(item => item.id == historyId);
+ vpListFromApi = vpListFromApi.filter(item => item.id.toString() === historyId);
}
setHistory(Array.isArray(vpListFromApi) ? vpListFromApi : [vpListFromApi]);
diff --git a/src/lib/interfaces/ICredentialParser.ts b/src/lib/interfaces/ICredentialParser.ts
index 500b2514..04de3336 100644
--- a/src/lib/interfaces/ICredentialParser.ts
+++ b/src/lib/interfaces/ICredentialParser.ts
@@ -2,7 +2,7 @@ import { PresentationDefinitionType } from "../types/presentationDefinition.type
export interface ICredentialParserRegistry {
addParser(parser: ICredentialParser): void;
- parse(rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; } | { credentialImageSvgTemplateURL: string; }; beautifiedForm: any; } | { error: string }>;
+ parse(rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; }; beautifiedForm: any; } | { error: string }>;
}
export interface ICredentialParser {
@@ -12,5 +12,5 @@ export interface ICredentialParser {
* @param presentationDefinitionFilter if defined, then the befautified form will include only the attributes defined in the presentation definition. This can be used
* in the presentation flow when the user is prompted to select credentials to present
*/
- parse(rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; } | { credentialImageSvgTemplateURL: string; }; beautifiedForm: any; } | { error: string }>;
+ parse(rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; }; beautifiedForm: any; } | { error: string }>;
}
diff --git a/src/lib/interfaces/IOpenID4VPRelyingParty.ts b/src/lib/interfaces/IOpenID4VPRelyingParty.ts
index 744730ba..6aa80313 100644
--- a/src/lib/interfaces/IOpenID4VPRelyingParty.ts
+++ b/src/lib/interfaces/IOpenID4VPRelyingParty.ts
@@ -9,4 +9,5 @@ export enum HandleAuthorizationRequestError {
MISSING_PRESENTATION_DEFINITION_URI,
ONLY_ONE_INPUT_DESCRIPTOR_IS_SUPPORTED,
NONTRUSTED_VERIFIER,
+ INVALID_RESPONSE_MODE,
}
diff --git a/src/lib/schemas/CredentialConfigurationSupportedSchema.ts b/src/lib/schemas/CredentialConfigurationSupportedSchema.ts
index 9603f364..3d82118e 100644
--- a/src/lib/schemas/CredentialConfigurationSupportedSchema.ts
+++ b/src/lib/schemas/CredentialConfigurationSupportedSchema.ts
@@ -11,13 +11,16 @@ const commonSchema = z.object({
display: z.array(z.object({
name: z.string(),
description: z.string().optional(),
+ background_color: z.string().optional(),
+ text_color: z.string().optional(),
+ alt_text: z.string().optional(),
background_image: z.object({
uri: z.string()
}).optional(),
locale: z.string().optional(),
logo: z.object({
- url: z.string(),
- alt_text: z.string(),
+ uri: z.string(),
+ alt_text: z.string().optional(),
}).optional(),
})).optional(),
scope: z.string(),
diff --git a/src/lib/services/CredentialParserRegistry.ts b/src/lib/services/CredentialParserRegistry.ts
index 1af382a2..20498c61 100644
--- a/src/lib/services/CredentialParserRegistry.ts
+++ b/src/lib/services/CredentialParserRegistry.ts
@@ -10,7 +10,7 @@ export class CredentialParserRegistry implements ICredentialParserRegistry {
/**
* optimize parsing time by caching alread parsed objects because parse() can be called multiple times in a single view
*/
- private parsedObjectsCache = new Map();
+ private parsedObjectsCache = new Map();
addParser(parser: ICredentialParser): void {
this.parserList.push(parser);
diff --git a/src/lib/services/OpenID4VCIClient.ts b/src/lib/services/OpenID4VCIClient.ts
index 87373ba8..f175e441 100644
--- a/src/lib/services/OpenID4VCIClient.ts
+++ b/src/lib/services/OpenID4VCIClient.ts
@@ -45,7 +45,7 @@ export class OpenID4VCIClient implements IOpenID4VCIClient {
throw new Error("Only authorization_code grant is supported");
}
- if (offer.credential_issuer != this.config.credentialIssuerIdentifier) {
+ if (offer.credential_issuer !== this.config.credentialIssuerIdentifier) {
return;
}
@@ -143,7 +143,7 @@ export class OpenID4VCIClient implements IOpenID4VCIClient {
return;
}
const s = await this.openID4VCIClientStateRepository.getByStateAndUserHandle(state, userHandleB64U);
- if (!s || !s.credentialIssuerIdentifier || s.credentialIssuerIdentifier != this.config.credentialIssuerIdentifier) {
+ if (!s || !s.credentialIssuerIdentifier || s.credentialIssuerIdentifier !== this.config.credentialIssuerIdentifier) {
return;
}
await this.requestCredentials({
@@ -295,7 +295,7 @@ export class OpenID4VCIClient implements IOpenID4VCIClient {
console.log("== response = ", response)
try { // try to extract the response and update the OpenID4VCIClientStateRepository
const {
- data: { access_token, c_nonce, expires_in, c_nonce_expires_in, refresh_token, token_type },
+ data: { access_token, c_nonce, expires_in, c_nonce_expires_in, refresh_token },
} = response;
if (!access_token) {
@@ -329,7 +329,7 @@ export class OpenID4VCIClient implements IOpenID4VCIClient {
private async credentialRequest(response: any, flowState: OpenID4VCIClientState) {
const {
- data: { access_token, c_nonce, expires_in, c_nonce_expires_in },
+ data: { access_token, c_nonce },
} = response;
@@ -362,6 +362,9 @@ export class OpenID4VCIClient implements IOpenID4VCIClient {
const generateProofResult = await this.generateNonceProof(c_nonce, this.config.credentialIssuerIdentifier, this.config.clientId);
jws = generateProofResult.jws;
console.log("proof = ", jws)
+ if (jws) {
+ dispatchEvent(new CustomEvent("generatedProof"));
+ }
}
catch (err) {
console.error(err);
@@ -377,10 +380,10 @@ export class OpenID4VCIClient implements IOpenID4VCIClient {
"format": this.config.credentialIssuerMetadata.credential_configurations_supported[flowState.credentialConfigurationId].format,
} as any;
- if (credentialConfigurationSupported.format == VerifiableCredentialFormat.SD_JWT_VC && credentialConfigurationSupported.vct) {
+ if (credentialConfigurationSupported.format === VerifiableCredentialFormat.SD_JWT_VC && credentialConfigurationSupported.vct) {
credentialEndpointBody.vct = credentialConfigurationSupported.vct;
}
- else if (credentialConfigurationSupported.format == VerifiableCredentialFormat.MSO_MDOC && credentialConfigurationSupported.doctype) {
+ else if (credentialConfigurationSupported.format === VerifiableCredentialFormat.MSO_MDOC && credentialConfigurationSupported.doctype) {
credentialEndpointBody.doctype = credentialConfigurationSupported.doctype;
}
diff --git a/src/lib/services/OpenID4VCIClientStateRepository.ts b/src/lib/services/OpenID4VCIClientStateRepository.ts
index 992618df..9f714d77 100644
--- a/src/lib/services/OpenID4VCIClientStateRepository.ts
+++ b/src/lib/services/OpenID4VCIClientStateRepository.ts
@@ -15,9 +15,18 @@ export class OpenID4VCIClientStateRepository implements IOpenID4VCIClientStateRe
}
async getByStateAndUserHandle(state: string, userHandleB64U: string): Promise {
const array = JSON.parse(localStorage.getItem(this.key)) as Array;
- const res = array.filter((s) => s.state == state && s.userHandleB64U == userHandleB64U)[0];
- if (res && (!res.created || typeof res.created != 'number' || res.tokenResponse?.data?.refresh_token && Math.floor(Date.now() / 1000) - res.created > this.refreshTokenMaxAgeInSeconds)) {
- const updatedArray = array.filter((x) => x.state != res.state); // remove the state
+ const res = array.filter((s) => s.state === state && s.userHandleB64U === userHandleB64U)[0];
+ if (res &&
+ (
+ !res.created ||
+ typeof res.created !== 'number' ||
+ (
+ res.tokenResponse?.data?.refresh_token &&
+ Math.floor(Date.now() / 1000) - res.created > this.refreshTokenMaxAgeInSeconds
+ )
+ )
+ ) {
+ const updatedArray = array.filter((x) => x.state !== res.state); // remove the state
localStorage.setItem(this.key, JSON.stringify(updatedArray));
return null;
}
@@ -26,9 +35,18 @@ export class OpenID4VCIClientStateRepository implements IOpenID4VCIClientStateRe
async getByCredentialConfigurationIdAndUserHandle(credentialConfigurationId: string, userHandleB64U: string): Promise {
const array = JSON.parse(localStorage.getItem(this.key)) as Array;
- const res = array.filter((s) => s.credentialConfigurationId == credentialConfigurationId && s.userHandleB64U == userHandleB64U)[0];
- if (res && (!res.created || typeof res.created != 'number' || res.tokenResponse?.data?.refresh_token && Math.floor(Date.now() / 1000) - res.created > this.refreshTokenMaxAgeInSeconds)) {
- const updatedArray = array.filter((x) => x.state != res.state); // remove the state
+ const res = array.filter((s) => s.credentialConfigurationId === credentialConfigurationId && s.userHandleB64U === userHandleB64U)[0];
+ if (res &&
+ (
+ !res.created ||
+ typeof res.created != 'number' ||
+ (
+ res.tokenResponse?.data?.refresh_token &&
+ Math.floor(Date.now() / 1000) - res.created > this.refreshTokenMaxAgeInSeconds
+ )
+ )
+ ) {
+ const updatedArray = array.filter((x) => x.state !== res.state); // remove the state
localStorage.setItem(this.key, JSON.stringify(updatedArray));
return null;
}
@@ -39,7 +57,7 @@ export class OpenID4VCIClientStateRepository implements IOpenID4VCIClientStateRe
const existingState = await this.getByCredentialConfigurationIdAndUserHandle(s.credentialConfigurationId, s.userHandleB64U);
if (existingState) { // remove the existing state for this configuration id
const array = JSON.parse(localStorage.getItem(this.key)) as Array;
- const updatedArray = array.filter((x) => x.credentialConfigurationId != s.credentialConfigurationId);
+ const updatedArray = array.filter((x) => x.credentialConfigurationId !== s.credentialConfigurationId);
localStorage.setItem(this.key, JSON.stringify(updatedArray));
}
let data = localStorage.getItem(this.key);
@@ -67,7 +85,7 @@ export class OpenID4VCIClientStateRepository implements IOpenID4VCIClientStateRe
return;
}
const array = JSON.parse(localStorage.getItem(this.key)) as Array;
- const updatedArray = array.filter((x) => x.state != newState.state); // remove the state that is going to be changed
+ const updatedArray = array.filter((x) => x.state !== newState.state); // remove the state that is going to be changed
updatedArray.push(newState);
// commit changes
localStorage.setItem(this.key, JSON.stringify(updatedArray));
diff --git a/src/lib/services/OpenID4VPRelyingParty.ts b/src/lib/services/OpenID4VPRelyingParty.ts
index cf40e9e0..7bb7b6bb 100644
--- a/src/lib/services/OpenID4VPRelyingParty.ts
+++ b/src/lib/services/OpenID4VPRelyingParty.ts
@@ -5,7 +5,7 @@ import { HasherAlgorithm, HasherAndAlgorithm, SdJwt } from "@sd-jwt/core";
import { VerifiableCredentialFormat } from "../schemas/vc";
import { generateRandomIdentifier } from "../utils/generateRandomIdentifier";
import { base64url, CompactEncrypt, importJWK, importX509, jwtVerify } from "jose";
-import { OpenID4VPRelyingPartyState } from "../types/OpenID4VPRelyingPartyState";
+import { OpenID4VPRelyingPartyState, ResponseMode, ResponseModeSchema } from "../types/OpenID4VPRelyingPartyState";
import { OpenID4VPRelyingPartyStateRepository } from "./OpenID4VPRelyingPartyStateRepository";
import { IHttpProxy } from "../interfaces/IHttpProxy";
import { ICredentialParserRegistry } from "../interfaces/ICredentialParser";
@@ -40,7 +40,7 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
let presentation_definition = authorizationRequest.searchParams.get('presentation_definition') ? JSON.parse(authorizationRequest.searchParams.get('presentation_definition')) : null;
let presentation_definition_uri = authorizationRequest.searchParams.get('presentation_definition_uri');
let client_metadata = authorizationRequest.searchParams.get('client_metadata') ? JSON.parse(authorizationRequest.searchParams.get('client_metadata')) : null;
-
+ let response_mode = authorizationRequest.searchParams.get('response_mode') ? JSON.parse(authorizationRequest.searchParams.get('response_mode')) : null;
if (presentation_definition_uri) {
const presentationDefinitionFetch = await this.httpProxy.get(presentation_definition_uri, {});
presentation_definition = presentationDefinitionFetch.data;
@@ -67,6 +67,9 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
presentation_definition = p.presentation_definition;
response_uri = p.response_uri ?? p.redirect_uri;
client_metadata = p.client_metadata;
+ if (p.response_mode) {
+ response_mode = p.response_mode;
+ }
state = p.state;
nonce = p.nonce;
@@ -80,7 +83,7 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
}
const altNames = await extractSAN('-----BEGIN CERTIFICATE-----\n' + parsedHeader.x5c[0] + '\n-----END CERTIFICATE-----');
- if (OPENID4VP_SAN_DNS_CHECK && !altNames || altNames.length === 0) {
+ if (OPENID4VP_SAN_DNS_CHECK && (!altNames || altNames.length === 0)) {
console.log("No SAN found");
return { err: HandleAuthorizationRequestError.NONTRUSTED_VERIFIER }
}
@@ -111,13 +114,12 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
}
}
+
const lastUsedNonce = sessionStorage.getItem('last_used_nonce');
- if (lastUsedNonce && nonce == lastUsedNonce) {
+ if (lastUsedNonce && nonce === lastUsedNonce) {
throw new Error("last used nonce");
}
- const vcList = await this.getAllStoredVerifiableCredentials().then((res) => res.verifiableCredentials);
-
if (!presentation_definition) {
return { err: HandleAuthorizationRequestError.MISSING_PRESENTATION_DEFINITION };
}
@@ -125,13 +127,21 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
return { err: HandleAuthorizationRequestError.ONLY_ONE_INPUT_DESCRIPTOR_IS_SUPPORTED };
}
+ const { error } = ResponseModeSchema.safeParse(response_mode);
+ if (error) {
+ return { err: HandleAuthorizationRequestError.INVALID_RESPONSE_MODE };
+ }
+
+ const vcList = await this.getAllStoredVerifiableCredentials().then((res) => res.verifiableCredentials);
+
await this.openID4VPRelyingPartyStateRepository.store(new OpenID4VPRelyingPartyState(
presentation_definition,
nonce,
response_uri,
client_id,
state,
- client_metadata
+ client_metadata,
+ response_mode,
));
const mapping = new Map();
@@ -217,7 +227,7 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
const S = await this.openID4VPRelyingPartyStateRepository.retrieve();
console.log("send AuthorizationResponse: S = ", S)
console.log("send AuthorizationResponse: Sess = ", sessionStorage.getItem('last_used_nonce'));
- if (S?.nonce == "" || (sessionStorage.getItem('last_used_nonce') && S.nonce == sessionStorage.getItem('last_used_nonce'))) {
+ if (S?.nonce === "" || (sessionStorage.getItem('last_used_nonce') && S.nonce === sessionStorage.getItem('last_used_nonce'))) {
console.info("OID4VP: Non existent flow");
return {};
}
@@ -382,7 +392,7 @@ export class OpenID4VPRelyingParty implements IOpenID4VPRelyingParty {
const formData = new URLSearchParams();
- if (S.client_metadata.authorization_encrypted_response_alg && S.client_metadata.jwks.keys.length > 0) {
+ if (S.response_mode === ResponseMode.DIRECT_POST_JWT && S.client_metadata.authorization_encrypted_response_alg && S.client_metadata.jwks.keys.length > 0) {
const rp_eph_pub_jwk = S.client_metadata.jwks.keys[0];
const rp_eph_pub = await importJWK(rp_eph_pub_jwk, S.client_metadata.authorization_encrypted_response_alg);
const jwe = await new CompactEncrypt(new TextEncoder().encode(JSON.stringify({
diff --git a/src/lib/types/OpenID4VPRelyingPartyState.ts b/src/lib/types/OpenID4VPRelyingPartyState.ts
index c3f95389..98721b82 100644
--- a/src/lib/types/OpenID4VPRelyingPartyState.ts
+++ b/src/lib/types/OpenID4VPRelyingPartyState.ts
@@ -1,6 +1,13 @@
import { JWK } from "jose";
import { PresentationDefinitionType } from "./presentationDefinition.type";
+import * as z from 'zod';
+export enum ResponseMode {
+ DIRECT_POST = 'direct_post',
+ DIRECT_POST_JWT = 'direct_post.jwt',
+}
+
+export const ResponseModeSchema = z.nativeEnum(ResponseMode);
type ClientMetadata = {
jwks?: { keys: JWK[] },
@@ -19,7 +26,8 @@ export class OpenID4VPRelyingPartyState {
public response_uri: string,
public client_id: string,
public state: string,
- public client_metadata: ClientMetadata
+ public client_metadata: ClientMetadata,
+ public response_mode: ResponseMode,
) { }
public serialize(): string {
@@ -30,11 +38,12 @@ export class OpenID4VPRelyingPartyState {
client_id: this.client_id,
state: this.state,
client_metadata: this.client_metadata,
+ response_mode: this.response_mode,
});
}
public static deserialize(storedValue: string): OpenID4VPRelyingPartyState {
- const { presentation_definition, nonce, response_uri, client_id, state, client_metadata } = JSON.parse(storedValue) as OpenID4VPRelyingPartyState;
- return new OpenID4VPRelyingPartyState(presentation_definition, nonce, response_uri, client_id, state, client_metadata);
+ const { presentation_definition, nonce, response_uri, client_id, state, client_metadata, response_mode } = JSON.parse(storedValue) as OpenID4VPRelyingPartyState;
+ return new OpenID4VPRelyingPartyState(presentation_definition, nonce, response_uri, client_id, state, client_metadata, response_mode);
}
}
diff --git a/src/lib/utils/mdocPIDParser.ts b/src/lib/utils/mdocPIDParser.ts
index 949b69ee..94bc2634 100644
--- a/src/lib/utils/mdocPIDParser.ts
+++ b/src/lib/utils/mdocPIDParser.ts
@@ -7,7 +7,7 @@ import defaulCredentialImage from "../../assets/images/cred.png";
export const deviceResponseParser: ICredentialParser = {
- parse: async function (rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; } | { credentialImageSvgTemplateURL: string; }; beautifiedForm: any; } | { error: string; }> {
+ parse: async function (rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; }; beautifiedForm: any; } | { error: string }> {
if (typeof rawCredential != 'string') {
return { error: "Not for this parser" };
@@ -35,7 +35,7 @@ export const deviceResponseParser: ICredentialParser = {
}
export const mdocPIDParser: ICredentialParser = {
- parse: async function (rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; } | { credentialImageSvgTemplateURL: string; }; beautifiedForm: any; } | { error: string; }> {
+ parse: async function (rawCredential: object | string, presentationDefinitionFilter?: PresentationDefinitionType): Promise<{ credentialFriendlyName: string; credentialImage: { credentialImageURL: string; }; beautifiedForm: any; } | { error: string }> {
console.log("Raw cred = ", rawCredential)
if (typeof rawCredential != 'string') {
return { error: "Not for this parser" };
diff --git a/src/pages/Home/Home.js b/src/pages/Home/Home.js
index b836b12f..19d3e267 100644
--- a/src/pages/Home/Home.js
+++ b/src/pages/Home/Home.js
@@ -1,5 +1,5 @@
// External libraries
-import React, { useState, useContext, useEffect } from 'react';
+import React, { useContext, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -10,6 +10,7 @@ import CredentialsContext from '../../context/CredentialsContext';
// Hooks
import useFetchPresentations from '../../hooks/useFetchPresentations';
import useScreenType from '../../hooks/useScreenType';
+import { useSessionStorage } from '../../hooks/useStorage';
// Components
import { H1 } from '../../components/Shared/Heading';
@@ -22,7 +23,7 @@ const Home = () => {
const { vcEntityList, latestCredentials, getData } = useContext(CredentialsContext);
const { api } = useContext(SessionContext);
const history = useFetchPresentations(api);
- const [currentSlide, setCurrentSlide] = useState(1);
+ const [currentSlide, setCurrentSlide,] = api.useClearOnClearSession(useSessionStorage('currentSlide', 1));
const screenType = useScreenType();
const navigate = useNavigate();
@@ -73,6 +74,7 @@ const Home = () => {
setCurrentSlide(currentIndex + 1)}
/>
diff --git a/src/pages/NotFound/NotFound.js b/src/pages/NotFound/NotFound.js
index d94cf4e2..17993b08 100644
--- a/src/pages/NotFound/NotFound.js
+++ b/src/pages/NotFound/NotFound.js
@@ -15,7 +15,7 @@ const NotFound = () => {
return (
-
+
diff --git a/src/services/LocalStorageKeystore.ts b/src/services/LocalStorageKeystore.ts
index 6f5b6346..bddac1b3 100644
--- a/src/services/LocalStorageKeystore.ts
+++ b/src/services/LocalStorageKeystore.ts
@@ -68,8 +68,8 @@ export interface LocalStorageKeystore {
getUserHandleB64u(): string | null,
signJwtPresentation(nonce: string, audience: string, verifiableCredentials: any[]): Promise<{ vpjwt: string }>,
- generateOpenid4vciProof(nonce: string, audience: string, issuer: string): Promise<[
- { proof_jwt: string },
+ generateOpenid4vciProofs(requests: { nonce: string, audience: string, issuer: string }[]): Promise<[
+ { proof_jwts: string[] },
AsymmetricEncryptedContainer,
CommitCallback,
]>,
@@ -372,20 +372,27 @@ export function useLocalStorageKeystore(): LocalStorageKeystore {
await keystore.signJwtPresentation(await openPrivateData(), nonce, audience, verifiableCredentials)
),
- generateOpenid4vciProof: async (nonce: string, audience: string, issuer: string): Promise<[
- { proof_jwt: string },
+ generateOpenid4vciProofs: async (requests: { nonce: string, audience: string, issuer: string }[]): Promise<[
+ { proof_jwts: string[] },
AsymmetricEncryptedContainer,
CommitCallback,
]> => (
- await editPrivateData(async (container) =>
- await keystore.generateOpenid4vciProof(
- container,
- config.DID_KEY_VERSION,
- nonce,
- audience,
- issuer
- ),
- )
+ await editPrivateData(async (originalContainer) => {
+ let container = originalContainer;
+ let proof_jwts = [];
+ for (const { nonce, audience, issuer } of requests) {
+ const [{ proof_jwt }, newContainer] = await keystore.generateOpenid4vciProof(
+ container,
+ config.DID_KEY_VERSION,
+ nonce,
+ audience,
+ issuer
+ );
+ proof_jwts.push(proof_jwt);
+ container = newContainer;
+ }
+ return [{ proof_jwts }, container];
+ })
),
generateDeviceResponse: async (mdocCredential: MDoc, presentationDefinition: any, mdocGeneratedNonce: string, verifierGeneratedNonce: string, clientId: string, responseUri: string): Promise<{ deviceResponseMDoc: MDoc }> => (
diff --git a/src/services/SigningRequestHandlers.ts b/src/services/SigningRequestHandlers.ts
index 73de27ad..bd644b7c 100644
--- a/src/services/SigningRequestHandlers.ts
+++ b/src/services/SigningRequestHandlers.ts
@@ -25,7 +25,7 @@ export function SigningRequestHandlerService(): SigningRequestHandlers {
},
handleGenerateOpenid4vciProofSigningRequest: async (api: BackendApi, socket, keystore, { message_id, audience, nonce, issuer }) => {
- const [{ proof_jwt }, newPrivateData, keystoreCommit] = await keystore.generateOpenid4vciProof(nonce, audience, issuer)
+ const [{ proof_jwts: [proof_jwt] }, newPrivateData, keystoreCommit] = await keystore.generateOpenid4vciProofs([{ nonce, audience, issuer }])
await api.updatePrivateData(newPrivateData);
await keystoreCommit();
console.log("proof jwt = ", proof_jwt);