diff --git a/packages/common/lib/types/Generic.types.ts b/packages/common/lib/types/Generic.types.ts index a965b299..cf707d56 100644 --- a/packages/common/lib/types/Generic.types.ts +++ b/packages/common/lib/types/Generic.types.ts @@ -64,16 +64,11 @@ export interface SupportedCredentialIssuerMetadataJwtVcJson extends CredentialSu order?: string[]; //An array of claims.display.name values that lists them in the order they should be displayed by the Wallet. } -export interface CredentialOfferCredential { +export interface CredentialOfferFormat { format: CredentialFormatEnum; types: string[]; } -export interface CredentialOfferCredentialJwtVcJson extends CredentialOfferCredential { - format: CredentialFormatEnum.jwt_vc_json; - types: string[]; -} - export interface IssuerCredentialDefinition { '@context': ICredentialContextType[]; types: string[]; diff --git a/packages/common/lib/types/v1_0_09.types.ts b/packages/common/lib/types/v1_0_09.types.ts index 9eee5a85..6107be41 100644 --- a/packages/common/lib/types/v1_0_09.types.ts +++ b/packages/common/lib/types/v1_0_09.types.ts @@ -1,12 +1,12 @@ import { CommonAuthorizationRequest } from './Authorization.types'; import { CredentialOfferPayload } from './CredentialIssuance.types'; -import { CredentialOfferCredentialJwtVcJson } from './Generic.types'; +import { CredentialOfferFormat } from './Generic.types'; // https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-09.html#name-issuance-initiation-request export interface CredentialOfferV1_0_09 { issuer: string; //(url) REQUIRED The issuer URL of the Credential issuer, the Wallet is requested to obtain one or more Credentials from. credential_type: string[] | string; //(url) REQUIRED A JSON string denoting the type of the Credential the Wallet shall request - credentials: CredentialOfferCredentialJwtVcJson[]; + credentials: CredentialOfferFormat[]; 'pre-authorized_code'?: string; //CONDITIONAL the code representing the issuer's authorization for the Wallet to obtain Credentials of a certain type. This code MUST be short-lived and single-use. MUST be present in a pre-authorized code flow. user_pin_required?: boolean | string; //OPTIONAL Boolean value specifying whether the issuer expects presentation of a user PIN along with the Token Request in a pre-authorized code flow. Default is false. op_state?: string; //(JWT) OPTIONAL String value created by the Credential Issuer and opaque to the Wallet that is used to bind the subsequent authentication request with the Credential Issuer to a context set up during previous steps diff --git a/packages/common/lib/types/v1_0_11.types.ts b/packages/common/lib/types/v1_0_11.types.ts index a423127e..9978ebd8 100644 --- a/packages/common/lib/types/v1_0_11.types.ts +++ b/packages/common/lib/types/v1_0_11.types.ts @@ -1,6 +1,6 @@ import { AuthorizationDetailsJwtVcJson, CommonAuthorizationRequest } from './Authorization.types'; import { CredentialOfferPayload } from './CredentialIssuance.types'; -import { CredentialOfferCredential, Grant, IssuerCredentialDefinition } from './Generic.types'; +import { CredentialOfferFormat, Grant, IssuerCredentialDefinition } from './Generic.types'; export interface CredentialOfferV1_0_11 { credential_offer?: CommonCredentialOfferPayloadV1_0_11; @@ -19,7 +19,7 @@ export interface CommonCredentialOfferPayloadV1_0_11 { * credentials_supported Credential Issuer metadata parameter. * When processing, the Wallet MUST resolve this string value to the respective object. */ - credentials?: (CredentialOfferCredential | string)[]; + credentials?: (CredentialOfferFormat | string)[]; /** * OPTIONAL. A JSON object indicating to the Wallet the Grant Types the Credential Issuer's AS is prepared * to process for this credential offer. Every grant is represented by a key and an object. @@ -43,7 +43,7 @@ export interface CredentialOfferJwtVcJsonLdAndLdpVcV1_0_11 extends CommonCredent } export interface CredentialOfferJwtVcJsonV1_0_11 extends CommonCredentialOfferPayloadV1_0_11 { - credentials: (CredentialOfferCredential | string)[]; // look at CommonCredentialOfferPayloadV1_0_11.credentials + credentials: (CredentialOfferFormat | string)[]; // look at CommonCredentialOfferPayloadV1_0_11.credentials } export type CredentialOfferPayloadV1_0_11 = CredentialOfferJwtVcJsonLdAndLdpVcV1_0_11 | CredentialOfferJwtVcJsonV1_0_11; diff --git a/packages/issuer-rest/lib/api.ts b/packages/issuer-rest/lib/api.ts index a36999b8..08609785 100644 --- a/packages/issuer-rest/lib/api.ts +++ b/packages/issuer-rest/lib/api.ts @@ -126,7 +126,11 @@ export class RestAPI { const preAuthorizedCode = request.params.pre_authorized_code const id = uuidv4() this.tokenToId.set(preAuthorizedCode, id) - return response.send(createCredentialOfferDeeplink(preAuthorizedCode, this._vcIssuer._issuerMetadata)) + return response.send( + createCredentialOfferDeeplink(this._vcIssuer._issuerMetadata, { + preAuthorizedCode: preAuthorizedCode, + }) + ) }) } } diff --git a/packages/issuer/lib/__tests__/CredentialOfferUtils.spec.ts b/packages/issuer/lib/__tests__/CredentialOfferUtils.spec.ts new file mode 100644 index 00000000..dfcb2c46 --- /dev/null +++ b/packages/issuer/lib/__tests__/CredentialOfferUtils.spec.ts @@ -0,0 +1,27 @@ +import { CredentialFormatEnum } from '@sphereon/openid4vci-common' + +import { createCredentialOfferDeeplink } from '../index' + +describe('CredentialOfferUtils should', () => { + it('create a deeplink from credentialOffer object', () => { + // below is the example from spec (https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-11.html#name-sending-credential-offer-by) and is wrong, the issuer_state should be in the grants and not a top-level property + // openid-credential-offer://credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com%22,%22credentials%22:%5B%7B%22format%22:%22jwt_vc_json%22,%22types%22:%5B%22VerifiableCredential%22,%22UniversityDegreeCredential%22%5D%7D%5D,%22issuer_state%22:%22eyJhbGciOiJSU0Et...FYUaBy%22%7D + const credentialOffer = { + credential_issuer: 'https://credential-issuer.example.com', + credentials: [ + { + format: CredentialFormatEnum.jwt_vc_json, + types: ['VerifiableCredential', 'UniversityDegreeCredential'], + }, + ], + grants: { + authorization_code: { + issuer_state: 'eyJhbGciOiJSU0Et...FYUaBy', + }, + }, + } + expect(createCredentialOfferDeeplink(undefined, { credentialOffer })).toEqual( + 'openid-credential-offer://?credential_offer=credential_issuer=https%3A%2F%2Fcredential-issuer.example.com&credentials=%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%2C%22UniversityDegreeCredential%22%5D%7D%5D&grants=%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22eyJhbGciOiJSU0Et...FYUaBy%22%7D%7D' + ) + }) +}) diff --git a/packages/issuer/lib/__test__/VcIssuerBuilder.spec.ts b/packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts similarity index 100% rename from packages/issuer/lib/__test__/VcIssuerBuilder.spec.ts rename to packages/issuer/lib/__tests__/VcIssuerBuilder.spec.ts diff --git a/packages/issuer/lib/functions/CredentialOffer.ts b/packages/issuer/lib/functions/CredentialOffer.ts deleted file mode 100644 index 6536403f..00000000 --- a/packages/issuer/lib/functions/CredentialOffer.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { CredentialFormatEnum, CredentialSupported, encodeJsonAsURI, IssuerMetadata, TokenErrorResponse } from '@sphereon/openid4vci-common' -import { v4 as uuidv4 } from 'uuid' - -export function createCredentialOfferDeeplink(preAuthorizedCode: string, issuerMetadata: IssuerMetadata, opts?: { state?: string }): string { - // openid-credential-offer://credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com - // %22,%22credentials%22:%5B%7B%22format%22:%22jwt_vc_json%22,%22types%22:%5B%22VerifiableCr - // edential%22,%22UniversityDegreeCredential%22%5D%7D%5D,%22issuer_state%22:%22eyJhbGciOiJSU0Et... - // FYUaBy%22%7D - if (!preAuthorizedCode) { - throw new Error(TokenErrorResponse.invalid_request) - } - - const types: string[] = [] - issuerMetadata.credentials_supported.map((cs) => { - if (cs.format != CredentialFormatEnum.mso_mdoc) types.push(...(cs['types' as keyof CredentialSupported] as string[])) - }) - return `openid-credential-offer://?credential_offer=${encodeJsonAsURI({ - credential_issuer: issuerMetadata.credential_issuer, - credentials: { - format: issuerMetadata.credentials_supported.map((cs) => cs.format), - types: types, - //fixme: @nklomp I've placed this here for now, but later we need to have the concept of sessions and in there we have to keep track of the id - issuer_state: opts && opts.state ? opts.state : uuidv4(), - }, - grants: { - authorization_code: preAuthorizedCode, - }, - })}` -} diff --git a/packages/issuer/lib/functions/CredentialOfferUtils.ts b/packages/issuer/lib/functions/CredentialOfferUtils.ts new file mode 100644 index 00000000..2d8a4ddb --- /dev/null +++ b/packages/issuer/lib/functions/CredentialOfferUtils.ts @@ -0,0 +1,37 @@ +import { CredentialOfferPayload, CredentialOfferPayloadV1_0_11, encodeJsonAsURI, IssuerMetadata } from '@sphereon/openid4vci-common' +import { v4 as uuidv4 } from 'uuid' + +export function createCredentialOfferDeeplink( + issuerMetadata?: IssuerMetadata, + opts?: { state?: string; credentialOffer?: CredentialOfferPayload; preAuthorizedCode?: string; userPinRequired?: boolean } +): string { + // openid-credential-offer://credential_offer=%7B%22credential_issuer%22:%22https://credential-issuer.example.com + // %22,%22credentials%22:%5B%7B%22format%22:%22jwt_vc_json%22,%22types%22:%5B%22VerifiableCr + // edential%22,%22UniversityDegreeCredential%22%5D%7D%5D,%22issuer_state%22:%22eyJhbGciOiJSU0Et... + // FYUaBy%22%7D + if (!issuerMetadata && !opts?.credentialOffer) { + throw new Error('You have to provide issuerMetadata or credentialOffer object for creating a deeplink') + } + if (opts?.credentialOffer) { + return `openid-credential-offer://?credential_offer=${encodeJsonAsURI(opts.credentialOffer)}` + } + const credentialOfferPayload = { + credential_issuer: issuerMetadata?.credential_issuer, + credentials: issuerMetadata?.credentials_supported, + grants: { + authorization_code: { + issuer_state: opts && opts.state ? opts.state : uuidv4(), + }, + }, + } as CredentialOfferPayloadV1_0_11 + if (opts?.preAuthorizedCode) { + if (!credentialOfferPayload.grants) { + credentialOfferPayload.grants = {} + } + credentialOfferPayload.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'] = { + 'pre-authorized_code': opts.preAuthorizedCode, + user_pin_required: opts.userPinRequired ? opts.userPinRequired : false, + } + } + return `openid-credential-offer://?credential_offer=${encodeJsonAsURI(credentialOfferPayload)}` +} diff --git a/packages/issuer/lib/functions/index.ts b/packages/issuer/lib/functions/index.ts index 096e8571..dac7ebe2 100644 --- a/packages/issuer/lib/functions/index.ts +++ b/packages/issuer/lib/functions/index.ts @@ -1 +1 @@ -export * from './CredentialOffer' +export * from './CredentialOfferUtils'