Skip to content

Commit

Permalink
Support credential configuration IDs in offers.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlongley committed Aug 22, 2024
1 parent 026c455 commit 9660176
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 18 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
- Add support for issuer configuration URLs that do not match RFC 8414,
but instead match the OID4VCI spec, i.e., `<issuer>/.well-known/...` will
be accepted and not just `<issuer origin>/.well-known/.../<issuer path>`.
- Add support for parsing and using credential offers with `credentials`
or `credential_configuration_ids` that include credential configuration
IDs that are present in the issuer configuration.

## 3.5.0 - 2024-08-08

Expand Down
82 changes: 64 additions & 18 deletions lib/OID4Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,12 @@ export class OID4Client {
// create a client from a credential offer
static async fromCredentialOffer({offer, agent} = {}) {
// validate offer
const {credential_issuer, credentials, grants = {}} = offer;
const {
credential_issuer,
credentials,
credential_configuration_ids,
grants = {}
} = offer;
let parsedIssuer;
try {
parsedIssuer = new URL(credential_issuer);
Expand All @@ -216,8 +221,20 @@ export class OID4Client {
} catch(cause) {
throw new Error('"offer.credential_issuer" is not valid.', {cause});
}
if(!(Array.isArray(credentials) && credentials.length > 0 &&
credentials.every(c => c && typeof c === 'object'))) {
if(credentials === undefined &&
credential_configuration_ids === undefined) {
throw new Error(
'Either "offer.credential_configuration_ids" or ' +
'"offer.credentials" is required.');
}
if(credential_configuration_ids !== undefined &&
!Array.isArray(credential_configuration_ids)) {
throw new Error('"offer.credential_configuration_ids" is not valid.');
}
if(credentials !== undefined &&
!(Array.isArray(credentials) && credentials.length > 0 &&
credentials.every(c => c && (
typeof c === 'object' || typeof c === 'string')))) {
throw new Error('"offer.credentials" is not valid.');
}
const grant = grants[GRANT_TYPES.get('preAuthorizedCode')];
Expand Down Expand Up @@ -376,9 +393,48 @@ function _isPresentationRequired(error) {
return error.status === 400 && errorType === 'presentation_required';
}

function _createCredentialRequestFromId({id, issuerConfig}) {
const {credentials_supported: supported = []} = issuerConfig;
const meta = supported.find(d => d.id === id);
function _createCredentialRequestsFromOffer({issuerConfig, offer}) {
// get any supported credential configurations from issuer config
const supported = _createSupportedCredentialsMap({issuerConfig});

// build requests from credentials identified in `offer`
const credentials = offer.credential_configuration_ids ?? offer.credentials;
return credentials.map(c => {
if(typeof c === 'string') {
// use supported credential config
return _getSupportedCredentialById({id: c, supported});
}
return c;
});
}

function _createSupportedCredentialsMap({issuerConfig}) {
const {
credential_configurations_supported,
credentials_supported
} = issuerConfig;

let supported;
if(credential_configurations_supported &&
typeof credential_configurations_supported === 'object') {
supported = new Map(Object.entries(
issuerConfig.credential_configurations_supported));
} else if(Array.isArray(credentials_supported)) {
// handle legacy `credentials_supported` array
supported = new Map();
for(const entry of issuerConfig.credentials_supported) {
supported.set(entry.id, entry);
}
} else {
// no supported credentials from issuer config
supported = new Map();
}

return supported;
}

function _getSupportedCredentialById({id, supported}) {
const meta = supported.get(id);
if(!meta) {
throw new Error(`No supported credential "${id}" found.`);
}
Expand All @@ -388,21 +444,11 @@ function _createCredentialRequestFromId({id, issuerConfig}) {
`Invalid supported credential "${id}"; "format" not specified.`);
}
if(!(Array.isArray(credential_definition?.['@context']) &&
Array.isArray(credential_definition?.types))) {
(Array.isArray(credential_definition?.types) ||
Array.isArray(credential_definition?.type)))) {
throw new Error(
`Invalid supported credential "${id}"; "credential_definition" not ` +
'fully specified.');
}
return {format, credential_definition};
}

function _createCredentialRequestsFromOffer({issuerConfig, offer}) {
// build requests from `offer`
return offer.credentials.map(c => {
if(typeof c === 'string') {
// use issuer config metadata to dereference string
return _createCredentialRequestFromId({id: c, issuerConfig});
}
return c;
});
}

0 comments on commit 9660176

Please sign in to comment.