Skip to content

Commit

Permalink
Merge pull request #189 from wwWallet/selective-disclosure-jwt
Browse files Browse the repository at this point in the history
Selective Disclosure JWT
  • Loading branch information
kkmanos authored Mar 19, 2024
2 parents a5c4173 + 859c7c9 commit 7920416
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 33 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"@cef-ebsi/key-did-resolver": "^1.1.0",
"@sd-jwt/core": "^0.2.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
Expand Down
3 changes: 2 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ function App() {
conformantCredentialsMap,
showPinInputPopup,
setShowPinInputPopup,
verifierDomainName
} = useCheckURL(url);

useEffect(() => {
Expand Down Expand Up @@ -93,7 +94,7 @@ function App() {
<Route path="*" element={<NotFound />} />
</Routes>
{showSelectCredentialsPopup &&
<SelectCredentialsPopup showPopup={showSelectCredentialsPopup} setShowPopup={setShowSelectCredentialsPopup} setSelectionMap={setSelectionMap} conformantCredentialsMap={conformantCredentialsMap} />
<SelectCredentialsPopup showPopup={showSelectCredentialsPopup} setShowPopup={setShowSelectCredentialsPopup} setSelectionMap={setSelectionMap} conformantCredentialsMap={conformantCredentialsMap} verifierDomainName={verifierDomainName} />
}
{showPinInputPopup &&
<PinInputPopup showPopup={showPinInputPopup} setShowPopup={setShowPinInputPopup} />
Expand Down
76 changes: 57 additions & 19 deletions src/components/Credentials/ApiFetchCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,39 @@

import { BackendApi } from '../../api';
import parseJwt from '../../functions/ParseJwt';
import {
HasherAlgorithm,
HasherAndAlgorithm,
SdJwt,
} from '@sd-jwt/core'

enum CredentialFormat {
VC_SD_JWT = "vc+sd-jwt",
JWT_VC_JSON = "jwt_vc_json"
}

const encoder = new TextEncoder();

// Encoding the string into a Uint8Array
const hasherAndAlgorithm: HasherAndAlgorithm = {
hasher: (input: string) => {
return crypto.subtle.digest('SHA-256', encoder.encode(input)).then((v) => new Uint8Array(v));
},
algorithm: HasherAlgorithm.Sha256
}

const parseCredentialDependingOnFormat = async (credential: string, format: string): Promise<any> => {
switch (format) {
case CredentialFormat.JWT_VC_JSON:
return parseJwt(credential);
case CredentialFormat.VC_SD_JWT:
return SdJwt.fromCompact<Record<string, unknown>, any>(credential)
.withHasher(hasherAndAlgorithm)
.getPrettyClaims();
default:
throw new Error("Format is not recognised");
}
}

export async function fetchCredentialData(api: BackendApi, id = null) {
try {
Expand All @@ -10,30 +43,35 @@ export async function fetchCredentialData(api: BackendApi, id = null) {
if (id) {
const targetImage = response.data.vc_list.find((img) => img.id.toString() === id);
const newImages = targetImage
? [targetImage].map((item) => ({
id: item.id,
credentialIdentifier: item.credentialIdentifier,
src: item.logoURL,
alt: item.issuerFriendlyName,
data: parseJwt(item.credential)["vc"]['credentialSubject'],
type: parseJwt(item.credential)['vc']["type"]["2"],
expdate: parseJwt(item.credential)['vc']["expirationDate"],
json: JSON.stringify(parseJwt(item.credential)["vc"], null, 2)

? await Promise.all([targetImage].map(async (item) => {
const credentialPayload = await parseCredentialDependingOnFormat(item.credential, item.format);
return ({
id: item.id,
credentialIdentifier: item.credentialIdentifier,
src: item.logoURL,
alt: item.issuerFriendlyName,
data: credentialPayload["vc"]['credentialSubject'],
type: credentialPayload['vc']["type"]["2"],
expdate: credentialPayload['vc']["expirationDate"],
json: JSON.stringify(credentialPayload["vc"], null, 2)
});
}))
: [];

return newImages[0];
} else {
const newImages = response.data.vc_list.map((item) => ({
id: item.id,
credentialIdentifier: item.credentialIdentifier,
src: item.logoURL,
alt: item.issuerFriendlyName,
data: parseJwt(item.credential)["vc"]['credentialSubject'],
type: parseJwt(item.credential)['vc']["type"]["2"],
expdate: parseJwt(item.credential)['vc']["expirationDate"],
json: JSON.stringify(parseJwt(item.credential)["vc"], null, 2)
const newImages = await Promise.all(response.data.vc_list.map(async (item) => {
const credentialPayload = await parseCredentialDependingOnFormat(item.credential, item.format);
return ({
id: item.id,
credentialIdentifier: item.credentialIdentifier,
src: item.logoURL,
alt: item.issuerFriendlyName,
data: credentialPayload["vc"]['credentialSubject'],
type: credentialPayload['vc']["type"]["2"],
expdate: credentialPayload['vc']["expirationDate"],
json: JSON.stringify(credentialPayload["vc"], null, 2)
})
}));

return newImages;
Expand Down
77 changes: 66 additions & 11 deletions src/components/Popups/SelectCredentials.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import { useApi } from '../../api';


function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conformantCredentialsMap }) {
function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conformantCredentialsMap, verifierDomainName }) {
const api = useApi();
const [images, setImages] = useState([]);
const navigate = useNavigate();
Expand All @@ -15,6 +15,11 @@ function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conforman
const keys = Object.keys(conformantCredentialsMap);
const [currentIndex, setCurrentIndex] = useState(0);
const [currentSelectionMap, setCurrentSelectionMap] = useState({});
const [requestedFields, setRequestedFields] = useState([]);
const [showRequestedFields, setShowRequestedFields] = useState(false);
const [renderContent, setRenderContent] = useState(showRequestedFields);
const [applyTransition, setApplyTransition] = useState(false);


useEffect(() => {
const getData = async () => {
Expand All @@ -27,12 +32,13 @@ function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conforman
try {
const response = await api.get('/storage/vc');
const simplifiedCredentials = response.data.vc_list
.filter(vc => conformantCredentialsMap[keys[currentIndex]].includes(vc.credentialIdentifier))
.map(vc => ({
id: vc.credentialIdentifier,
imageURL: vc.logoURL,
}));

.filter(vc => conformantCredentialsMap[keys[currentIndex]].credentials.includes(vc.credentialIdentifier))
.map(vc => ({
id: vc.credentialIdentifier,
imageURL: vc.logoURL,
}));
console.log("FIelds = ", conformantCredentialsMap[keys[currentIndex]].requestedFields)
setRequestedFields(conformantCredentialsMap[keys[currentIndex]].requestedFields);
setImages(simplifiedCredentials);
} catch (error) {
console.error('Failed to fetch data', error);
Expand All @@ -42,8 +48,19 @@ function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conforman
getData();
}, [api, currentIndex]);

useEffect(() => {
if (showRequestedFields) {
setRenderContent(true);
} else if (applyTransition) {
setTimeout(() => setRenderContent(false), 500);
} else {
setRenderContent(false);
}
}, [showRequestedFields, applyTransition]);


const goToNextSelection = () => {
setCurrentIndex((i) => i+1);
setCurrentIndex((i) => i + 1);
}

const handleClick = (id) => {
Expand All @@ -52,9 +69,12 @@ function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conforman
currentMap[descriptorId] = id;
return currentMap;
});
setApplyTransition(false);
setShowRequestedFields(false);
goToNextSelection();
};


const handleCancel = () => {
setShowPopup(false);
navigate('/'); // Navigate to home page or any other route
Expand All @@ -67,7 +87,7 @@ function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conforman
return (
<div className="fixed inset-0 flex items-center justify-center z-50">
<div className="absolute inset-0 bg-black opacity-50"></div>
<div className="bg-white p-4 rounded-lg shadow-lg w-full max-h-[80vh] lg:w-[33.33%] sm:w-[66.67%] z-10 relative m-4 ">
<div className="bg-white p-4 rounded-lg shadow-lg w-full lg:max-w-[33.33%] sm:max-w-[66.67%] max-h-[90vh] z-10 relative m-4 overflow-y-auto">
<h2 className="text-lg font-bold mb-2 text-custom-blue">
<FaShare size={20} className="inline mr-1 mb-1" />
{t('selectCredentialPopup.title')}
Expand All @@ -76,15 +96,50 @@ function SelectCredentials({ showPopup, setShowPopup, setSelectionMap, conforman
<p className="italic pd-2 text-gray-700">
{t('selectCredentialPopup.description')}
</p>
<div className='mt-2 flex flex-wrap justify-center flex overflow-y-auto max-h-[50vh]'>
{requestedFields && (


<div className="lg:p-0 p-2 mt-4 w-full">
<div className="mb-2 flex items-center">
<button
onClick={() => { setApplyTransition(true); setShowRequestedFields(!showRequestedFields) }}
className="px-2 py-2 text-white cursor-pointer flex items-center bg-custom-blue hover:bg-custom-blue-hover font-medium rounded-lg text-sm px-4 py-2 text-center dark:bg-custom-blue-hover dark:hover:bg-custom-blue-hover"
>
{showRequestedFields ? 'Hide Credentials Details' : 'Show Requested Fields'}
</button>
</div>

<hr className="my-2 border-t border-gray-300 py-2" />

<div
className={`overflow-hidden transition-height ${showRequestedFields ? 'max-h-96' : 'max-h-0'}`}
style={{ transition: 'max-height 0.5s ease-in-out' }}

>
{renderContent && (
<>
<p className='mb-2 text-sm italic text-gray-700'>The following fields were requested from the verifier{verifierDomainName}</p>
<textarea
readOnly
value={requestedFields.join('\n')}
className="w-full border rounded p-2 rounded-xl"
rows={Math.min(3, Math.max(1, requestedFields.length))}
></textarea>
</>
)}
</div>
</div>
)}

<div className='mt-2 flex flex-wrap justify-center flex overflow-y-auto max-h-[40vh]'>
{images.map(image => (
<div className="m-5">
<img
key={image.id}
src={image.imageURL}
alt={image.id}
onClick={() => handleClick(image.id)}
className="w-60 rounded-xl cursor-pointer"
className="w-48 rounded-xl cursor-pointer"
/>
</div>
))}
Expand Down
6 changes: 5 additions & 1 deletion src/components/useCheckURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ function useCheckURL(urlToCheck: string): {
conformantCredentialsMap: any,
showPinInputPopup: boolean,
setShowPinInputPopup: Dispatch<SetStateAction<boolean>>,
verifierDomainName: string
} {
const api = useApi();
const isLoggedIn: boolean = api.isLoggedIn();
const [showSelectCredentialsPopup, setShowSelectCredentialsPopup] = useState<boolean>(false);
const [showPinInputPopup, setShowPinInputPopup] = useState<boolean>(false);
const [selectionMap, setSelectionMap] = useState<string | null>(null);
const [conformantCredentialsMap, setConformantCredentialsMap] = useState(null);
const [verifierDomainName, setVerifierDomainName] = useState("");

const keystore = useLocalStorageKeystore();

useEffect(() => {
Expand Down Expand Up @@ -44,6 +47,7 @@ function useCheckURL(urlToCheck: string): {
return true;
} else if (conformantCredentialsMap) {
console.log('need action');
setVerifierDomainName(verifierDomainName);
setConformantCredentialsMap(conformantCredentialsMap);
setShowSelectCredentialsPopup(true);
console.log("called setShowSelectCredentialsPopup")
Expand Down Expand Up @@ -85,7 +89,7 @@ function useCheckURL(urlToCheck: string): {
}
}, [api, keystore, selectionMap]);

return {showSelectCredentialsPopup, setShowSelectCredentialsPopup, setSelectionMap, conformantCredentialsMap, showPinInputPopup, setShowPinInputPopup };
return {showSelectCredentialsPopup, setShowSelectCredentialsPopup, setSelectionMap, conformantCredentialsMap, showPinInputPopup, setShowPinInputPopup, verifierDomainName };
}

export default useCheckURL;
59 changes: 58 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2302,6 +2302,45 @@
resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz"
integrity sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw==

"@sd-jwt/core@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/core/-/core-0.2.1.tgz#75b0b273758e6be050e042a75bd6a0c4a2a7258e"
integrity sha512-8auyt3mfzgAK+IP9mNc3kSONdo5x2Y8ypNj5gHKP7N81nVeyI+DHethoPQv84JVcqYYcNwHwyrc2Z5k7rg2lFQ==
dependencies:
"@sd-jwt/decode" "0.2.1"
"@sd-jwt/present" "0.2.1"
"@sd-jwt/types" "0.2.1"
"@sd-jwt/utils" "0.2.1"

"@sd-jwt/[email protected]":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/decode/-/decode-0.2.1.tgz#e0fb32dd2a95440ad69237e66ea2cd4770ec7e09"
integrity sha512-rs55WB3llrMObxN8jeMl06km/h0WivO9jSWNubO9JUIdlfrVhssU38xoXakvQeSDjAJkUUhfZcvmC2vNo1X6Wg==
dependencies:
"@sd-jwt/types" "0.2.1"
"@sd-jwt/utils" "0.2.1"

"@sd-jwt/[email protected]":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/present/-/present-0.2.1.tgz#ff9958626b271a60d539dd1e601763ff33c024e8"
integrity sha512-yWIAR2C/q1jNUwzAeUlUcf3WCTEcSSGo9pltHW5AXptELjyaWGSmC5p6o9ucDXHvBnicfPONhe5OdUCSpiCntw==
dependencies:
"@sd-jwt/types" "0.2.1"
"@sd-jwt/utils" "0.2.1"

"@sd-jwt/[email protected]":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/types/-/types-0.2.1.tgz#e1e6b47728dffa90ed244e15e2253bd01793cb96"
integrity sha512-nbNik/cq6UIMsN144FcgPZQzaqIsjEEj307j3ZSFORkQBR4Tsmcj54aswTuNh0Z0z/4aSbfw14vOKBZvRWyVLQ==

"@sd-jwt/[email protected]":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@sd-jwt/utils/-/utils-0.2.1.tgz#35ad83232eab2de911e765d93222acd871982a5e"
integrity sha512-9eRrge44dhE3fenawR/RZGxP5iuW9DtgdOVANu/JK5PEl80r0fDsMwm/gDjuv8OgLDCmQ6uSaVte1lYaTG71bQ==
dependencies:
"@sd-jwt/types" "0.2.1"
buffer "*"

"@sideway/address@^4.1.3":
version "4.1.4"
resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0"
Expand Down Expand Up @@ -3741,6 +3780,11 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==

base64-js@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==

base64url@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
Expand Down Expand Up @@ -3863,6 +3907,14 @@ buffer-from@^1.0.0:
resolved "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==

buffer@*:
version "6.0.3"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6"
integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==
dependencies:
base64-js "^1.3.1"
ieee754 "^1.2.1"

builtin-modules@^3.1.0:
version "3.3.0"
resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz"
Expand Down Expand Up @@ -6022,6 +6074,11 @@ identity-obj-proxy@^3.0.0:
dependencies:
harmony-reflect "^1.4.6"

ieee754@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==

ignore@^5.2.0:
version "5.2.4"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
Expand Down Expand Up @@ -7489,7 +7546,7 @@ multiformats@^11.0.1:
resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-11.0.2.tgz#b14735efc42cd8581e73895e66bebb9752151b60"
integrity sha512-b5mYMkOkARIuVZCpvijFj9a6m5wMVLC7cf/jIPd5D/ARDOfLC5+IFkbgDXQgcU2goIsTD/O9NY4DI/Mt4OGvlg==

multiformats@^12.0.1:
multiformats@^12.0.1, multiformats@^12.1.3:
version "12.1.3"
resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-12.1.3.tgz#cbf7a9861e11e74f8228b21376088cb43ba8754e"
integrity sha512-eajQ/ZH7qXZQR2AgtfpmSMizQzmyYVmCql7pdhldPuYQi4atACekbJaQplk6dWyIi10jCaFnd6pqvcEFXjbaJw==
Expand Down

0 comments on commit 7920416

Please sign in to comment.