Skip to content

Commit

Permalink
Support credential_offer_uri and improve auto-cred-def selection.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Aug 22, 2024
1 parent 2650dd4 commit 754afa6
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 6 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @digitalbazaar/oid4-client Changelog

## 3.7.0 - 2024-08-dd

### Added
- Add support for fetching credential offer from `credential_offer_uri` via
`getCredentialOffer()`.
- Improve automatic credential definition selection from a credential offer
based on the specified `format`.

## 3.6.0 - 2024-08-22

### Added
Expand Down
26 changes: 20 additions & 6 deletions lib/OID4Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export class OID4Client {
if(!offer) {
throw new TypeError('"credentialDefinition" must be an object.');
}
requests = _createCredentialRequestsFromOffer({issuerConfig, offer});
requests = _createCredentialRequestsFromOffer({
issuerConfig, offer, format
});
if(requests.length > 1) {
throw new Error(
'More than one credential is offered; ' +
Expand All @@ -48,7 +50,9 @@ export class OID4Client {
} = {}) {
const {issuerConfig, offer} = this;
if(requests === undefined && offer) {
requests = _createCredentialRequestsFromOffer({issuerConfig, offer});
requests = _createCredentialRequestsFromOffer({
issuerConfig, offer, format
});
} else if(!(Array.isArray(requests) && requests.length > 0)) {
throw new TypeError('"requests" must be an array of length >= 1.');
}
Expand Down Expand Up @@ -393,19 +397,29 @@ function _isPresentationRequired(error) {
return error.status === 400 && errorType === 'presentation_required';
}

function _createCredentialRequestsFromOffer({issuerConfig, offer}) {
function _createCredentialRequestsFromOffer({
issuerConfig, offer, format
}) {
// get any supported credential configurations from issuer config
const supported = _createSupportedCredentialsMap({issuerConfig});

// build requests from credentials identified in `offer`
// build requests from credentials identified in `offer` and remove any
// that do not match the given format
const credentials = offer.credential_configuration_ids ?? offer.credentials;
return credentials.map(c => {
const requests = credentials.map(c => {
if(typeof c === 'string') {
// use supported credential config
return _getSupportedCredentialById({id: c, supported});
}
return c;
});
}).filter(r => r.format === format);

if(requests.length === 0) {
throw new Error(
`No supported credential(s) with format "${format}" found.`);
}

return requests;
}

function _createSupportedCredentialsMap({issuerConfig}) {
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * as oid4vp from './oid4vp.js';
export {
discoverIssuer,
generateDIDProofJWT,
getCredentialOffer,
parseCredentialOfferUrl,
robustDiscoverIssuer,
signJWT
Expand Down
37 changes: 37 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,43 @@ export async function generateDIDProofJWT({
return signJWT({payload, protectedHeader, signer});
}

export async function getCredentialOffer({url, agent} = {}) {
const {protocol, searchParams} = new URL(url);
if(protocol !== 'openid-credential-offer:') {
throw new SyntaxError(
'"url" must express a URL with the ' +
'"openid-credential-offer" protocol.');
}
const offer = searchParams.get('credential_offer');
if(offer) {
return JSON.parse(offer);
}

// try to fetch offer from URL
const offerUrl = searchParams.get('credential_offer_uri');
if(!offerUrl) {
throw new SyntaxError(
'OID4VCI credential offer must have "credential_offer" or ' +
'"credential_offer_uri".');
}

if(!offerUrl.startsWith('https://')) {
const error = new Error(
`"credential_offer_uri" (${offerUrl}) must start with "https://".`);
error.name = 'NotSupportedError';
throw error;
}

const response = await fetchJSON({url: offerUrl, agent});
if(!response.data) {
const error = new Error(
`Credential offer fetched from "${offerUrl}" is not JSON.`);
error.name = 'DataError';
throw error;
}
return response.data;
}

export function parseCredentialOfferUrl({url} = {}) {
assert(url, 'url', 'string');

Expand Down

0 comments on commit 754afa6

Please sign in to comment.