From 5af7c3da29c0dca2b5f62b885640e4bc0afe32f9 Mon Sep 17 00:00:00 2001 From: Jesse van Muijden Date: Wed, 4 Jun 2025 06:01:29 -0400 Subject: [PATCH 1/3] feat(display): hardcoded display of AcademicEnrollmentCredential name and issuer --- src/components/Credentials/CredentialInfo.js | 4 +++- src/context/ContainerContext.tsx | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Credentials/CredentialInfo.js b/src/components/Credentials/CredentialInfo.js index df0361a2b..be69682b4 100644 --- a/src/components/Credentials/CredentialInfo.js +++ b/src/components/Credentials/CredentialInfo.js @@ -60,6 +60,7 @@ const CredentialInfo = ({ credential, mainClassName = "text-sm lg:text-base w-fu const { isOnline } = useContext(StatusContext); const [parsedCredential, setParsedCredential] = useState(null); const [credentialFormat, setCredentialFormat] = useState(''); + const [issuerName, setIssuerName] = useState(''); const [credentialSubjectRows, setCredentialSubjectRows] = useState([]); const container = useContext(ContainerContext); const { api } = useContext(SessionContext); @@ -84,6 +85,7 @@ const CredentialInfo = ({ credential, mainClassName = "text-sm lg:text-base w-fu } setParsedCredential(c.beautifiedForm); + setIssuerName(c.beautifiedForm.issuer?.name || ''); let iss = c.beautifiedForm.iss; @@ -212,7 +214,7 @@ const CredentialInfo = ({ credential, mainClassName = "text-sm lg:text-base w-fu {!credentialSubjectRows.some(row => row.name === 'institution') && renderRow('institution', 'Institution', parsedCredential?.vc?.credentialSubject?.institution, screenType)} {!credentialSubjectRows.some(row => row.name === 'valid_from') && renderRow('valid_from', 'Valid from', parsedCredential?.vc?.credentialSubject?.valid_from, screenType)} {!credentialSubjectRows.some(row => row.name === 'valid_until') && renderRow('valid_until', 'Valid until', parsedCredential?.vc?.credentialSubject?.valid_until, screenType)} - {!credentialSubjectRows.some(row => row.name === 'issuer') && renderRow('issuer', 'Issuer', parsedCredential?.vc?.credentialSubject?.issuer, screenType)} + {!credentialSubjectRows.some(row => row.name === 'issuer') && renderRow('issuer', 'Issuer', parsedCredential?.vc?.credentialSubject?.issuer || issuerName, screenType)} ) } diff --git a/src/context/ContainerContext.tsx b/src/context/ContainerContext.tsx index 7e9b2e8be..a183e486c 100644 --- a/src/context/ContainerContext.tsx +++ b/src/context/ContainerContext.tsx @@ -269,6 +269,7 @@ export const ContainerContextProvider = ({ children }) => { if (isOpenBadgeCredential) return beautifiedForm.credentialSubject.achievement.name; if (t.includes('SupportCredential') || t.includes('ExamEnrollmentCredential')) return beautifiedForm.credentialSubject.title; + if (t.includes('AcademicEnrollmentCredential')) return beautifiedForm.credentialSubject.name; if (storedCredentialConfigurationId === 'EduID') return 'eduID' return result.beautifiedForm.name || credentialConfiguration?.display?.[0]?.name || 'Credential'; From 773050aa4bff5144cd24e4af2e69c6c75ca78872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A8r=20Kessels?= Date: Fri, 30 May 2025 13:09:31 +0200 Subject: [PATCH 2/3] fix: Make display[].locale optional in issuer-metadata We have at least one issuer that doesn't have the locale set. The spec makes it optional: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#section-11.2.3-2.10.2.2 > locale: OPTIONAL. String value that identifies the language of this > object represented as a language tag taken from values defined in BCP47 > [RFC5646]. There MUST be only one object for each language identifier. --- src/lib/schemas/OpenidCredentialIssuerMetadataSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/schemas/OpenidCredentialIssuerMetadataSchema.ts b/src/lib/schemas/OpenidCredentialIssuerMetadataSchema.ts index aa0647494..25f2c5fb0 100644 --- a/src/lib/schemas/OpenidCredentialIssuerMetadataSchema.ts +++ b/src/lib/schemas/OpenidCredentialIssuerMetadataSchema.ts @@ -7,7 +7,7 @@ export const OpenidCredentialIssuerMetadataSchema = z.object({ authorization_servers: z.array(z.string()).optional(), display: z.array(z.object({ name: z.string(), - locale: z.string(), + locale: z.string().optional() })).optional(), batch_credential_issuance: z.object({ batch_size: z.number(), From 2b4d7551b46beceaa8e7e024d89bc8f4a0e815ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A8r=20Kessels?= Date: Wed, 4 Jun 2025 10:14:58 +0200 Subject: [PATCH 3/3] fix: Ensure the url from the offer is decoded before calling it The url should be URL-encoded as per the spec. Usually, this does not matter, as url-encoding most URLS is involute - won't change anything But it may, if the url in the offer contains e.g. arguments, anchors, a port etc. --- src/lib/services/credential-offer.service.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/services/credential-offer.service.ts b/src/lib/services/credential-offer.service.ts index ddeb1a43f..68750fca9 100644 --- a/src/lib/services/credential-offer.service.ts +++ b/src/lib/services/credential-offer.service.ts @@ -1,16 +1,21 @@ -import { CredentialOfferSchema } from '../schemas/CredentialOfferSchema'; +import { CredentialOfferSchema } from "../schemas/CredentialOfferSchema"; -const PARAM_CREDENTIAL_OFFER = 'credential_offer'; -const PARAM_CREDENTIAL_OFFER_URI = 'credential_offer_uri'; +const PARAM_CREDENTIAL_OFFER = "credential_offer"; +const PARAM_CREDENTIAL_OFFER_URI = "credential_offer_uri"; // @todo: return type export const credentialOfferFromUrl = async (url: string) => { const parsedUrl = new URL(url); if (parsedUrl.searchParams.get(PARAM_CREDENTIAL_OFFER)) { - return CredentialOfferSchema.parse(JSON.parse(parsedUrl.searchParams.get(PARAM_CREDENTIAL_OFFER))); + return CredentialOfferSchema.parse( + JSON.parse(parsedUrl.searchParams.get(PARAM_CREDENTIAL_OFFER)), + ); } try { - const response = await fetch(parsedUrl.searchParams.get(PARAM_CREDENTIAL_OFFER_URI), {}); + const offerURI = + parsedUrl.searchParams.get(PARAM_CREDENTIAL_OFFER_URI) || ""; + const decodedOfferURI = decodeURIComponent(offerURI); + const response = await fetch(decodedOfferURI, {}); return await response.json(); } catch (err) { console.error(err);