Skip to content

Commit

Permalink
feat: Translate v8 credentials_supported to v11
Browse files Browse the repository at this point in the history
  • Loading branch information
nklomp committed May 28, 2023
1 parent c6d95a3 commit b06fa22
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 61 deletions.
61 changes: 42 additions & 19 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import {
CredentialResponse,
CredentialSupported,
EndpointMetadata,
IssuerCredentialSubject,
OID4VCICredentialFormat,
OpenId4VCIVersion,
OpenIDResponse,
ProofOfPossessionCallbacks,
PushedAuthorizationResponse,
ResponseType,
} from '@sphereon/oid4vci-common';
import { CredentialSupportedTypeV1_0_08, CredentialSupportedV1_0_08 } from '@sphereon/oid4vci-common/dist/types/v1_0_08.types';
import { getSupportedCredentials } from '@sphereon/oid4vci-common/dist/functions/IssuerMetadataUtils';
import { CredentialSupportedTypeV1_0_08 } from '@sphereon/oid4vci-common/dist/types/v1_0_08.types';
import { CredentialFormat } from '@sphereon/ssi-types';
import Debug from 'debug';

Expand Down Expand Up @@ -167,9 +167,15 @@ export class OpenID4VCIClient {
// Authorization servers supporting PAR SHOULD include the URL of their pushed authorization request endpoint in their authorization server metadata document
// Note that the presence of pushed_authorization_request_endpoint is sufficient for a client to determine that it may use the PAR flow.
// What happens if it doesn't ???
if (!this._endpointMetadata?.issuerMetadata || !('pushed_authorization_request_endpoint' in this._endpointMetadata.issuerMetadata)) {
// let parEndpoint: string
if (
!this._endpointMetadata?.issuerMetadata ||
!('pushed_authorization_request_endpoint' in this._endpointMetadata.issuerMetadata) ||
typeof this._endpointMetadata.issuerMetadata.pushed_authorization_request_endpoint !== 'string'
) {
throw Error('Server metadata does not contain pushed authorization request endpoint');
}
const parEndpoint: string = this._endpointMetadata.issuerMetadata.pushed_authorization_request_endpoint;

// add 'openid' scope if not present
if (scope && !scope.includes('openid')) {
Expand All @@ -186,8 +192,7 @@ export class OpenID4VCIClient {
redirect_uri: redirectUri,
scope: scope,
};
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return await formPost(this._endpointMetadata.issuerMetadata.pushed_authorization_request_endpoint!, JSON.stringify(queryObj));
return await formPost(parEndpoint, JSON.stringify(queryObj));
}

public handleAuthorizationDetails(authorizationDetails?: AuthDetails | AuthDetails[]): AuthDetails | AuthDetails[] | undefined {
Expand Down Expand Up @@ -355,17 +360,35 @@ export class OpenID4VCIClient {
}

getCredentialsSupported(restrictToInitiationTypes: boolean, supportedType?: string): CredentialSupported[] {
//FIXME: delegate to getCredentialsSupported from IssuerMetadataUtils
return getSupportedCredentials({
issuerMetadata: this.endpointMetadata.issuerMetadata,
version: this.version(),
supportedType,
credentialTypes: restrictToInitiationTypes ? this.getCredentialTypes() : undefined,
});
/*//FIXME: delegate to getCredentialsSupported from IssuerMetadataUtils
let credentialsSupported = this.endpointMetadata?.issuerMetadata?.credentials_supported
if (this.version() === OpenId4VCIVersion.VER_1_0_08 || typeof credentialsSupported === 'object') {
const issuerMetadata = this.endpointMetadata.issuerMetadata as IssuerMetadataV1_0_08
const v8CredentialsSupported = issuerMetadata.credentials_supported
credentialsSupported = []
credentialsSupported = Object.entries(v8CredentialsSupported).map((key, value) => )
}
const credentialsSupported = this.endpointMetadata?.issuerMetadata?.credentials_supported;
if (!credentialsSupported) {
return [];
return []
} else if (!restrictToInitiationTypes) {
return credentialsSupported;
return credentialsSupported
}
/**
/!**
* the following (not array part is a legacy code from version 1_0-08 which jff implementors used)
*/
*!/
if (!Array.isArray(credentialsSupported)) {
const credentialsSupportedV8: CredentialSupportedV1_0_08 = credentialsSupported as CredentialSupportedV1_0_08;
const initiationTypes = supportedType ? [supportedType] : this.getCredentialTypes();
Expand All @@ -378,22 +401,22 @@ export class OpenID4VCIClient {
// todo: fix this later. we're returning CredentialSupportedV1_0_08 as a list of CredentialSupported (for v09 onward)
return supported as unknown as CredentialSupported[];
}
const initiationTypes = supportedType ? [supportedType] : this.getCredentialTypes();
const credentialSupportedOverlap: CredentialSupported[] = [];
const initiationTypes = supportedType ? [supportedType] : this.getCredentialTypes()
const credentialSupportedOverlap: CredentialSupported[] = []
for (const supported of credentialsSupported) {
const supportedTypeOverlap: string[] = [];
const supportedTypeOverlap: string[] = []
for (const type of supported.types) {
initiationTypes.includes(type);
supportedTypeOverlap.push(type);
initiationTypes.includes(type)
supportedTypeOverlap.push(type)
}
if (supportedTypeOverlap.length > 0) {
credentialSupportedOverlap.push({
...supported,
types: supportedTypeOverlap,
});
types: supportedTypeOverlap
})
}
}
return credentialSupportedOverlap as CredentialSupported[];
return credentialSupportedOverlap as CredentialSupported[]*/
}

getCredentialMetadata(type: string): CredentialSupported[] {
Expand Down
84 changes: 64 additions & 20 deletions packages/common/lib/functions/IssuerMetadataUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
import { CredentialIssuerMetadata, CredentialOfferFormat, CredentialSupported, CredentialSupportedV1_0_08, IssuerCredentialSubject } from '../types';
import {
CredentialIssuerMetadata,
CredentialOfferFormat,
CredentialSupported,
CredentialSupportedTypeV1_0_08,
CredentialSupportedV1_0_08,
IssuerMetadataV1_0_08,
OpenId4VCIVersion,
} from '../types';

export function getSupportedCredentials(opts?: {
issuerMetadata?: CredentialIssuerMetadata | IssuerMetadataV1_0_08;
version: OpenId4VCIVersion;
credentialTypes?: (CredentialOfferFormat | string)[];
supportedType?: CredentialOfferFormat | string;
}): CredentialSupported[] {
const { issuerMetadata } = opts ?? {};
let credentialsSupported: CredentialSupported[];
if (!issuerMetadata) {
return [];
}
const { version, credentialTypes, supportedType } = opts ?? { version: OpenId4VCIVersion.VER_1_0_11 };
if (version === OpenId4VCIVersion.VER_1_0_08 || !Array.isArray(issuerMetadata.credentials_supported)) {
credentialsSupported = credentialsSupportedV8ToV11((issuerMetadata.credentials_supported as IssuerMetadataV1_0_08).credentials_supported);
/* const credentialsSupportedV8: CredentialSupportedV1_0_08 = credentialsSupported as CredentialSupportedV1_0_08
// const initiationTypes = credentialTypes.map(type => typeof type === 'string' ? [type] : type.types)
const supported: IssuerCredentialSubject = {}
for (const [key, value] of Object.entries(credentialsSupportedV8)) {
if (initiationTypes.find((type) => (typeof type === 'string' ? type === key : type.types.includes(key)))) {
supported[key] = value
}
}
// todo: fix this later. we're returning CredentialSupportedV1_0_08 as a list of CredentialSupported (for v09 onward)
return supported as unknown as CredentialSupported[]*/
} else {
credentialsSupported = (issuerMetadata as CredentialIssuerMetadata).credentials_supported;
}

export function getCredentialsSupported(
issuerMetadata: CredentialIssuerMetadata,
credentialTypes?: (CredentialOfferFormat | string)[],
supportedType?: CredentialOfferFormat | string
): CredentialSupported[] {
const credentialsSupported = issuerMetadata?.credentials_supported;
if (!credentialsSupported) {
return [];
} else if (!credentialTypes || credentialTypes.length === 0) {
Expand All @@ -15,18 +45,6 @@ export function getCredentialsSupported(
* the following (not array part is a legacy code from version 1_0-08 which JFF plugfest 2 implementors used)
*/
const initiationTypes = supportedType ? [supportedType] : credentialTypes;
if (!Array.isArray(credentialsSupported)) {
const credentialsSupportedV8: CredentialSupportedV1_0_08 = credentialsSupported as CredentialSupportedV1_0_08;
// const initiationTypes = credentialTypes.map(type => typeof type === 'string' ? [type] : type.types)
const supported: IssuerCredentialSubject = {};
for (const [key, value] of Object.entries(credentialsSupportedV8)) {
if (initiationTypes.find((type) => (typeof type === 'string' ? type === key : type.types.includes(key)))) {
supported[key] = value;
}
}
// todo: fix this later. we're returning CredentialSupportedV1_0_08 as a list of CredentialSupported (for v09 onward)
return supported as unknown as CredentialSupported[];
}

const credentialSupportedOverlap: CredentialSupported[] = [];
for (const offerType of initiationTypes) {
Expand All @@ -42,5 +60,31 @@ export function getCredentialsSupported(
}
}
}
return credentialSupportedOverlap as CredentialSupported[];
return credentialSupportedOverlap;
}

export function credentialsSupportedV8ToV11(supportedV8: CredentialSupportedTypeV1_0_08): CredentialSupported[] {
return Object.entries(supportedV8).flatMap((entry) => {
const type = entry[0];
const supportedV8 = entry[1];
return credentialSupportedV8ToV11(type, supportedV8);
});
}

export function credentialSupportedV8ToV11(key: string, supportedV8: CredentialSupportedV1_0_08): CredentialSupported[] {
return Object.entries(supportedV8.formats).map((entry) => {
const format = entry[0];
const credentialSupportBrief = entry[1];
if (typeof format !== 'string') {
throw Error(`Unknown format received ${JSON.stringify(format)}`);
}
let credentialSupport: Partial<CredentialSupported> = {};
credentialSupport = {
format,
display: supportedV8.display,
...credentialSupportBrief,
credentialSubject: supportedV8.claims,
};
return credentialSupport as CredentialSupported;
});
}
16 changes: 11 additions & 5 deletions packages/common/lib/types/Generic.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ICredentialContextType, IVerifiableCredential, W3CVerifiableCredential

import { ProofOfPossession } from './CredentialIssuance.types';
import { Oauth2ASWithOID4VCIMetadata } from './OpenID4VCIServerMetadata';
import { IssuerMetadataV1_0_08 } from './v1_0_08.types';
import { CredentialRequestV1_0_11 } from './v1_0_11.types';

/**
Expand Down Expand Up @@ -99,19 +100,22 @@ export interface IssuerCredentialDefinition {
credentialSubject: IssuerCredentialSubject;
}

/*
export interface CredentialOfferCredentialDefinition {
'@context': ICredentialContextType[];
types: string[];
credentialSubject?: IssuerCredentialSubject;
order?: string[]; // An array of claims.display.name values that lists them in the order they should be displayed by the Wallet.
}
*/

export enum GrantType {
AUTHORIZATION_CODE = 'authorization_code',
PRE_AUTHORIZED_CODE = 'urn:ietf:params:oauth:grant-type:pre-authorized_code',
PASSWORD = 'password',
}

/*
export interface CommonAccessTokenRequest {
client_id?: string;
code?: string;
Expand All @@ -122,6 +126,7 @@ export interface CommonAccessTokenRequest {
scope?: string;
user_pin?: string;
}
*/

export enum TokenErrorResponse {
invalid_request = 'invalid_request',
Expand All @@ -130,6 +135,7 @@ export enum TokenErrorResponse {
invalid_scope = 'invalid_scope',
}

/*
export interface CommonAccessTokenResponse {
access_token: string;
scope?: string;
Expand All @@ -140,6 +146,7 @@ export interface CommonAccessTokenResponse {
authorization_pending?: boolean;
interval?: number; // in seconds
}
*/

export interface ErrorResponse extends Response {
error: string;
Expand Down Expand Up @@ -179,6 +186,9 @@ export interface CredentialResponseJwtVcJsonLdAndLdpVc extends CommonCredentialR
credential: IVerifiableCredential;
}

export interface CredentialResponseJwtVcJson {
credential: string;
}
// export type CredentialSubjectDisplay = NameAndLocale[];

export type IssuerCredentialSubjectDisplay = CredentialSubjectDisplay & { [key: string]: CredentialSubjectDisplay };
Expand All @@ -193,10 +203,6 @@ export interface IssuerCredentialSubject {
[key: string]: IssuerCredentialSubjectDisplay;
}

export interface CredentialResponseJwtVcJson {
credential: string;
}

export interface Grant {
authorization_code?: GrantAuthorizationCode;
'urn:ietf:params:oauth:grant-type:pre-authorized_code'?: GrantUrnIetf;
Expand Down Expand Up @@ -235,5 +241,5 @@ export interface EndpointMetadata {
token_endpoint: string;
credential_endpoint: string;
authorization_endpoint?: string;
issuerMetadata?: CredentialIssuerMetadata | Oauth2ASWithOID4VCIMetadata;
issuerMetadata?: CredentialIssuerMetadata | Oauth2ASWithOID4VCIMetadata | IssuerMetadataV1_0_08;
}
23 changes: 6 additions & 17 deletions packages/common/lib/types/OpenID4VCIServerMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
import { CredentialFormat } from '@sphereon/ssi-types';

import { CredentialIssuerMetadata, IssuerCredentialSubject, MetadataDisplay, NameAndLocale } from './Generic.types';
import { CredentialIssuerMetadata } from './Generic.types';
import { OAuth2ASMetadata } from './OAuth2ASMetadata';

export type Oauth2ASWithOID4VCIMetadata = OAuth2ASMetadata & CredentialIssuerMetadata;

export interface CredentialIssuer {
display?: NameAndLocale[]; //OPTIONAL. An array of objects, where each object contains display properties of a Credential issuer for a certain language. Below is a non-exhaustive list of valid parameters that MAY be included:
}

// https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-metadata-object
export interface CredentialMetadata {
display?: MetadataDisplay[]; //OPTIONAL. An array of objects, where each object contains display properties of a certain Credential for a certain language. Below is a non-exhaustive list of parameters that MAY be included. Note that the display name of the Credential is obtained from display.name and individual claim names from claims.display.name values.
formats: Formats; //REQUIRED. A JSON object containing a list of key value pairs, where the key is a string identifying the format of the Credential. Below is a non-exhaustive list of valid key values defined by this specification:
claims?: IssuerCredentialSubject; //REQUIRED. A JSON object containing a list of key value pairs, where the key identifies the claim offered in the Credential. The value is a JSON object detailing the specifics about the support for the claim
}

export type OpenID4VCICredentialFormatTypes = CredentialFormat | 'mdl_iso' | 'ac_vc' | string;
// export type OpenID4VCICredentialFormatTypes = CredentialFormat | 'mdl_iso' | 'ac_vc' | string;

export interface CredentialFormatSupport {
/*export interface CredentialFormatSupport {
types: string[]; //REQUIRED. Array of strings representing a format specific type of a Credential. This value corresponds to type in W3C [VC_DATA] and a doctype in ISO/IEC 18013-5 (mobile Driving License).
cryptographic_binding_methods_supported?: string[]; //OPTIONAL. Array of case sensitive strings that identify how the Credential is bound to the identifier of the End-User who possesses the Credential as defined in Section 9.1. A non-exhaustive list of valid values defined by this specification are did, jwk, and mso.
cryptographic_suites_supported?: string[]; //OPTIONAL. Array of case sensitive strings that identify the cryptographic suites that are supported for the cryptographic_binding_methods_supported. Cryptosuites for Credentials in jwt_vc format should use algorithm names defined in IANA JOSE Algorithms Registry. Cryptosuites for Credentials in ldp_vc format should use signature suites names defined in Linked Data Cryptographic Suite Registry.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[x: string]: any; //We use any, so you can access properties if you know the structure
}
}*/
/*
export type Formats = {
[format in OpenID4VCICredentialFormatTypes]: CredentialFormatSupport;
};
*/

0 comments on commit b06fa22

Please sign in to comment.