diff --git a/packages/callback-example/CHANGELOG.md b/packages/callback-example/CHANGELOG.md index c81d3f52..15e77e2d 100644 --- a/packages/callback-example/CHANGELOG.md +++ b/packages/callback-example/CHANGELOG.md @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @sphereon/oid4vci-callback-example - - - - ## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30) **Note:** Version bump only for package @sphereon/oid4vci-callback-example diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index 1e57f7c3..40078978 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @sphereon/oid4vci-client - - - - ## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30) **Note:** Version bump only for package @sphereon/oid4vci-client diff --git a/packages/client/lib/__tests__/MattrE2E.spec.test.ts b/packages/client/lib/__tests__/MattrE2E.spec.test.ts index 9000831c..770e3a5b 100644 --- a/packages/client/lib/__tests__/MattrE2E.spec.test.ts +++ b/packages/client/lib/__tests__/MattrE2E.spec.test.ts @@ -20,7 +20,7 @@ const jwk: JWK = { // priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5 const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; -describe('OID4VCI-Client using Mattr issuer should', () => { +describe.skip('OID4VCI-Client using Mattr issuer should', () => { async function test(format: 'ldp_vc' | 'jwt_vc_json') { const offer = await getCredentialOffer(format); const client = await OpenID4VCIClient.fromURI({ diff --git a/packages/client/lib/__tests__/SphereonE2E.spec.test.ts b/packages/client/lib/__tests__/SphereonE2E.spec.test.ts index f9b483ca..175899d2 100644 --- a/packages/client/lib/__tests__/SphereonE2E.spec.test.ts +++ b/packages/client/lib/__tests__/SphereonE2E.spec.test.ts @@ -1,84 +1,83 @@ -import * as crypto from 'crypto' +import * as crypto from 'crypto'; -import { Alg, Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-common' -import { CredentialMapper } from '@sphereon/ssi-types' -import * as didts from '@transmute/did-key.js' -import { fetch } from 'cross-fetch' -import debug from 'debug' -import { importJWK, JWK, SignJWT } from 'jose' -import { v4 } from 'uuid' +import { Alg, Jwt, ProofOfPossessionCallbacks } from '@sphereon/oid4vci-common'; +import { CredentialMapper } from '@sphereon/ssi-types'; +import * as didts from '@transmute/did-key.js'; +import { fetch } from 'cross-fetch'; +import debug from 'debug'; +import { importJWK, JWK, SignJWT } from 'jose'; +import { v4 } from 'uuid'; +import { OpenID4VCIClient } from '..'; -import { OpenID4VCIClient } from '..' +export const UNIT_TEST_TIMEOUT = 30000; -export const UNIT_TEST_TIMEOUT = 30000 - -const ISSUER_URL = 'https://ssi.sphereon.com/pf3' +const ISSUER_URL = 'https://ssi.sphereon.com/pf3'; const jwk: JWK = { crv: 'Ed25519', d: 'kTRm0aONHYwNPA-w_DtjMHUIWjE3K70qgCIhWojZ0eU', x: 'NeA0d8sp86xRh3DczU4m5wPNIbl0HCSwOBcMN3sNmdk', - kty: 'OKP' -} + kty: 'OKP', +}; // pub hex: 35e03477cb29f3ac518770dccd4e26e703cd21b9741c24b038170c377b0d99d9 // priv hex: 913466d1a38d1d8c0d3c0fb0fc3b633075085a31372bbd2a8022215a88d9d1e5 const did = `did:key:z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; -const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv` +const kid = `${did}#z6Mki5ZwZKN1dBQprfJTikUvkDxrHijiiQngkWviMF5gw2Hv`; describe('OID4VCI-Client using Sphereon issuer should', () => { async function test(format: 'ldp_vc' | 'jwt_vc_json') { - debug.enable('*') - const offer = await getCredentialOffer(format) + debug.enable('*'); + const offer = await getCredentialOffer(format); const client = await OpenID4VCIClient.fromURI({ uri: offer.uri, kid, - alg: Alg.EdDSA - }) - expect(client.credentialOffer).toBeDefined() - expect(client.endpointMetadata).toBeDefined() - expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credentials`) - expect(client.getAccessTokenEndpoint()).toEqual(`${ISSUER_URL}/token`) - - const accessToken = await client.acquireAccessToken() + alg: Alg.EdDSA, + }); + expect(client.credentialOffer).toBeDefined(); + expect(client.endpointMetadata).toBeDefined(); + expect(client.getCredentialEndpoint()).toEqual(`${ISSUER_URL}/credentials`); + expect(client.getAccessTokenEndpoint()).toEqual(`${ISSUER_URL}/token`); + + const accessToken = await client.acquireAccessToken(); // console.log(accessToken); expect(accessToken).toMatchObject({ expires_in: 300, // scope: 'GuestCredential', - token_type: 'bearer' - }) + token_type: 'bearer', + }); const credentialResponse = await client.acquireCredentials({ credentialTypes: 'GuestCredential', format, proofCallbacks: { - signCallback: proofOfPossessionCallbackFunction - } - }) - expect(credentialResponse.credential).toBeDefined() - const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!) - expect(format.startsWith(wrappedVC.format)).toEqual(true) + signCallback: proofOfPossessionCallbackFunction, + }, + }); + expect(credentialResponse.credential).toBeDefined(); + const wrappedVC = CredentialMapper.toWrappedVerifiableCredential(credentialResponse.credential!); + expect(format.startsWith(wrappedVC.format)).toEqual(true); } xit( 'succeed in a full flow with the client using OpenID4VCI version 11 and ldp_vc', async () => { - await test('ldp_vc') + await test('ldp_vc'); }, - UNIT_TEST_TIMEOUT - ) + UNIT_TEST_TIMEOUT, + ); it( 'succeed in a full flow with the client using OpenID4VCI version 11 and jwt_vc_json', async () => { - await test('jwt_vc_json') + await test('jwt_vc_json'); }, - UNIT_TEST_TIMEOUT - ) -}) + UNIT_TEST_TIMEOUT, + ); +}); interface CreateCredentialOfferResponse { - uri: string, - userPinRequired: boolean + uri: string; + userPinRequired: boolean; } async function getCredentialOffer(format: 'ldp_vc' | 'jwt_vc_json'): Promise { @@ -86,64 +85,63 @@ async function getCredentialOffer(format: 'ldp_vc' | 'jwt_vc_json'): Promise { - const importedJwk = await importJWK(jwk, 'EdDSA') + const importedJwk = await importJWK(jwk, 'EdDSA'); return await new SignJWT({ ...args.payload }) .setProtectedHeader({ ...args.header, kid: kid! }) .setIssuer(kid!) .setIssuedAt() .setExpirationTime('2h') - .sign(importedJwk) + .sign(importedJwk); } - describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4VC-demo/issues/63, should', () => { it('work as expected provided a correct JWT is supplied', async () => { - debug.enable('*') - const { uri } = await getCredentialOffer('jwt_vc_json') - const client = await OpenID4VCIClient.fromURI({ uri: uri, clientId: 'test-clientID' }) - const metadata = await client.retrieveServerMetadata() - console.log(JSON.stringify(metadata)) + debug.enable('*'); + const { uri } = await getCredentialOffer('jwt_vc_json'); + const client = await OpenID4VCIClient.fromURI({ uri: uri, clientId: 'test-clientID' }); + const metadata = await client.retrieveServerMetadata(); + console.log(JSON.stringify(metadata)); //2. Adquire acces token from authorization server endpoint - const accessToken = await client.acquireAccessToken({}) - console.log(`Access token: ${JSON.stringify(accessToken)}`) + const accessToken = await client.acquireAccessToken({}); + console.log(`Access token: ${JSON.stringify(accessToken)}`); //3. Create DID needed for later proof of possession const { keys, didDocument } = await didts.jwk.generate({ type: 'secp256k1', // 'P-256', 'P-384', 'X25519', 'secp256k1' accept: 'application/did+json', secureRandom: () => { - return crypto.randomBytes(32) - } - }) - const edPrivateKey = await importJWK(keys[0].privateKeyJwk) + return crypto.randomBytes(32); + }, + }); + const edPrivateKey = await importJWK(keys[0].privateKeyJwk); async function signCallback(args: Jwt, kid?: string): Promise { if (!args.payload.aud) { - throw Error('aud required') + throw Error('aud required'); } else if (!kid) { - throw Error('kid required') + throw Error('kid required'); } return await new SignJWT({ ...args.payload }) .setProtectedHeader({ alg: args.header.alg, kid, typ: 'openid4vci-proof+jwt' }) @@ -151,12 +149,12 @@ describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4V .setIssuer(kid) .setAudience(args.payload.aud) .setExpirationTime('2h') - .sign(edPrivateKey) + .sign(edPrivateKey); } const callbacks: ProofOfPossessionCallbacks = { - signCallback: signCallback - } + signCallback: signCallback, + }; const credentialResponse = await client.acquireCredentials({ credentialTypes: 'GuestCredential', @@ -164,8 +162,8 @@ describe('ismapolis bug report #63, https://github.com/Sphereon-Opensource/OID4V format: 'jwt_vc_json', alg: Alg.ES256K, kid: didDocument.verificationMethod[0].id, - jti: v4() - }) - console.log(JSON.stringify(credentialResponse.credential)) - }) -}) + jti: v4(), + }); + console.log(JSON.stringify(credentialResponse.credential)); + }); +}); diff --git a/packages/common/CHANGELOG.md b/packages/common/CHANGELOG.md index d0421188..0cc7c10a 100644 --- a/packages/common/CHANGELOG.md +++ b/packages/common/CHANGELOG.md @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @sphereon/oid4vci-common - - - - ## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30) **Note:** Version bump only for package @sphereon/oid4vci-common diff --git a/packages/common/lib/types/Generic.types.ts b/packages/common/lib/types/Generic.types.ts index 4266cc09..f1fcfb37 100644 --- a/packages/common/lib/types/Generic.types.ts +++ b/packages/common/lib/types/Generic.types.ts @@ -101,6 +101,7 @@ export type CredentialDataSupplierInput = any; export type CreateCredentialOfferURIResult = { uri: string; + qrCodeDataUri?: string; session: CredentialOfferSession; userPin?: string; userPinLength?: number; diff --git a/packages/common/lib/types/QRCode.types.ts b/packages/common/lib/types/QRCode.types.ts new file mode 100644 index 00000000..99deb63b --- /dev/null +++ b/packages/common/lib/types/QRCode.types.ts @@ -0,0 +1,227 @@ +export interface ComponentOptions { + /** + * Component options for data/ECC. + */ + data?: { + /** + * Scale factor for data/ECC dots. + * @default 1 + */ + scale?: number; + }; + + /** + * Component options for timing patterns. + */ + timing?: { + /** + * Scale factor for timing patterns. + * @default 1 + */ + scale?: number; + + /** + * Protector for timing patterns. + * @default false + */ + protectors?: boolean; + }; + + /** + * Component options for alignment patterns. + */ + alignment?: { + /** + * Scale factor for alignment patterns. + * @default 1 + */ + scale?: number; + + /** + * Protector for alignment patterns. + * @default false + */ + protectors?: boolean; + }; + + /** + * Component options for alignment pattern on the bottom-right corner. + */ + cornerAlignment?: { + /** + * Scale factor for alignment pattern on the bottom-right corner. + * @default 1 + */ + scale?: number; + + /** + * Protector for alignment pattern on the bottom-right corner. + * @default true + */ + protectors?: boolean; + }; +}; + +export interface QRCodeOpts { + /** + * Size of the QR code in pixel. + * + * @defaultValue 400 + */ + size?: number; + + /** + * Size of margins around the QR code body in pixel. + * + * @defaultValue 20 + */ + margin?: number; + + /** + * Error correction level of the QR code. + * + * Accepts a value provided by _QRErrorCorrectLevel_. + * + * For more information, please refer to [https://www.qrcode.com/en/about/error_correction.html](https://www.qrcode.com/en/about/error_correction.html). + * + * @defaultValue 0 + */ + correctLevel?: number; + + /** + * **This is an advanced option.** + * + * Specify the mask pattern to be used in QR code encoding. + * + * Accepts a value provided by _QRMaskPattern_. + * + * To find out all eight mask patterns, please refer to [https://en.wikipedia.org/wiki/File:QR_Code_Mask_Patterns.svg](https://en.wikipedia.org/wiki/File:QR_Code_Mask_Patterns.svg) + * + * For more information, please refer to [https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders#Masking](https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders#Masking). + */ + maskPattern?: number; + + /** + * **This is an advanced option.** + * + * Specify the version to be used in QR code encoding. + * + * Accepts an integer in range [1, 40]. + * + * For more information, please refer to [https://www.qrcode.com/en/about/version.html](https://www.qrcode.com/en/about/version.html). + */ + version?: number; + + /** + * Options to control components in the QR code. + * + * @deafultValue undefined + */ + components?: ComponentOptions; + + /** + * Color of the blocks on the QR code. + * + * Accepts a CSS <color>. + * + * For more information about CSS <color>, please refer to [https://developer.mozilla.org/en-US/docs/Web/CSS/color_value](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). + * + * @defaultValue "#000000" + */ + colorDark?: string; + + /** + * Color of the empty areas on the QR code. + * + * Accepts a CSS <color>. + * + * For more information about CSS <color>, please refer to [https://developer.mozilla.org/en-US/docs/Web/CSS/color_value](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). + * + * @defaultValue "#ffffff" + */ + colorLight?: string; + + /** + * Automatically calculate the _colorLight_ value from the QR code's background. + * + * @defaultValue true + */ + autoColor?: boolean; + + /** + * Background image to be used in the QR code. + * + * Accepts a `data:` string in web browsers or a Buffer in Node.js. + * + * @defaultValue undefined + */ + backgroundImage?: string | Buffer; + + /** + * Color of the dimming mask above the background image. + * + * Accepts a CSS <color>. + * + * For more information about CSS <color>, please refer to [https://developer.mozilla.org/en-US/docs/Web/CSS/color_value](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value). + * + * @defaultValue "rgba(0, 0, 0, 0)" + */ + backgroundDimming?: string; + + /** + * GIF background image to be used in the QR code. + * + * @defaultValue undefined + */ + gifBackground?: ArrayBuffer; + + /** + * Use a white margin instead of a transparent one which reveals the background of the QR code on margins. + * + * @defaultValue true + */ + whiteMargin?: boolean; + + /** + * Logo image to be displayed at the center of the QR code. + * + * Accepts a `data:` string in web browsers or a Buffer in Node.js. + * + * When set to `undefined` or `null`, the logo is disabled. + * + * @defaultValue undefined + */ + logoImage?: string | Buffer; + + /** + * Ratio of the logo size to the QR code size. + * + * @defaultValue 0.2 + */ + logoScale?: number; + + /** + * Size of margins around the logo image in pixels. + * + * @defaultValue 6 + */ + logoMargin?: number; + + /** + * Corner radius of the logo image in pixels. + * + * @defaultValue 8 + */ + logoCornerRadius?: number; + + /** + * @deprecated + * + * Ratio of the real size to the full size of the blocks. + * + * This can be helpful when you want to make more parts of the background visible. + * + * @deafultValue 0.4 + */ + dotScale?: number; +}; diff --git a/packages/common/lib/types/index.ts b/packages/common/lib/types/index.ts index 8a992b5f..17e75b1b 100644 --- a/packages/common/lib/types/index.ts +++ b/packages/common/lib/types/index.ts @@ -9,3 +9,4 @@ export * from './OpenID4VCIErrors'; export * from './OpenID4VCIVersions.types'; export * from './StateManager.types'; export * from './Token.types'; +export * from './QRCode.types'; diff --git a/packages/common/lib/types/v1_0_11.types.ts b/packages/common/lib/types/v1_0_11.types.ts index 51fbb7f2..5fb3bc79 100644 --- a/packages/common/lib/types/v1_0_11.types.ts +++ b/packages/common/lib/types/v1_0_11.types.ts @@ -8,6 +8,7 @@ import { Grant, IssuerCredentialDefinition, } from './Generic.types'; +import { QRCodeOpts } from './QRCode.types'; export interface CredentialOfferV1_0_11 { credential_offer?: CredentialOfferPayloadV1_0_11; @@ -18,6 +19,7 @@ export interface CredentialOfferRESTRequest extends CredentialOfferV1_0_11 { baseUri?: string; scheme?: string; pinLength?: number; + qrCodeOpts?: QRCodeOpts; credentialDataSupplierInput?: CredentialDataSupplierInput; } diff --git a/packages/issuer-rest/CHANGELOG.md b/packages/issuer-rest/CHANGELOG.md index ed880d90..a5648652 100644 --- a/packages/issuer-rest/CHANGELOG.md +++ b/packages/issuer-rest/CHANGELOG.md @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @sphereon/oid4vci-issuer-server - - - - ## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30) ### Bug Fixes diff --git a/packages/issuer-rest/lib/OID4VCIServer.ts b/packages/issuer-rest/lib/OID4VCIServer.ts index 3cd16f0c..025fefc8 100644 --- a/packages/issuer-rest/lib/OID4VCIServer.ts +++ b/packages/issuer-rest/lib/OID4VCIServer.ts @@ -1,7 +1,13 @@ import * as console from 'console' import process from 'process' -import { AuthorizationRequest, CredentialSupported, IssuerCredentialSubjectDisplay, OID4VCICredentialFormat } from '@sphereon/oid4vci-common' +import { + AuthorizationRequest, + CredentialSupported, + IssuerCredentialSubjectDisplay, + OID4VCICredentialFormat, + QRCodeOpts, +} from '@sphereon/oid4vci-common' import { CredentialSupportedBuilderV1_11, ITokenEndpointOpts, VcIssuer, VcIssuerBuilder } from '@sphereon/oid4vci-issuer' import { ExpressSupport, HasEndpointOpts, ISingleEndpointOpts } from '@sphereon/ssi-express-support' import express, { Express } from 'express' @@ -70,6 +76,7 @@ export interface IGetCredentialOfferEndpointOpts extends ISingleEndpointOpts { export interface ICreateCredentialOfferEndpointOpts extends ISingleEndpointOpts { getOfferPath?: string + qrCodeOpts?: QRCodeOpts } export interface IGetIssueStatusEndpointOpts extends ISingleEndpointOpts { diff --git a/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts b/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts index 76d9d6cc..79336a0a 100644 --- a/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts +++ b/packages/issuer-rest/lib/__tests__/IssuerTokenServer.spec.ts @@ -165,7 +165,7 @@ describe('OID4VCIServer', () => { expect(res.statusCode).toEqual(200) const actual = JSON.parse(res.text) expect(actual).toEqual({ - access_token: expect.stringContaining('eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE2O'), + access_token: expect.stringContaining('eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpYXQi'), token_type: 'bearer', expires_in: 300000, c_nonce: expect.any(String), diff --git a/packages/issuer-rest/lib/oid4vci-api-functions.ts b/packages/issuer-rest/lib/oid4vci-api-functions.ts index e01fddb7..6d3afac1 100644 --- a/packages/issuer-rest/lib/oid4vci-api-functions.ts +++ b/packages/issuer-rest/lib/oid4vci-api-functions.ts @@ -208,7 +208,8 @@ export function createCredentialOfferEndpoint( if (!credentials || credentials.length === 0) { return sendErrorResponse(response, 400, { error: TokenErrorResponse.invalid_request, error_description: 'No credentials supplied' }) } - const result = await issuer.createCredentialOfferURI({ ...request.body, grants, credentials }) + const qrCodeOpts = request.body.qrCodeOpts ?? opts?.qrCodeOpts + const result = await issuer.createCredentialOfferURI({ ...request.body, qrCodeOpts, grants, credentials }) const resultResponse: ICreateCredentialOfferURIResponse = result if ('session' in resultResponse) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/issuer/CHANGELOG.md b/packages/issuer/CHANGELOG.md index eba0e891..950cda88 100644 --- a/packages/issuer/CHANGELOG.md +++ b/packages/issuer/CHANGELOG.md @@ -7,10 +7,6 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline **Note:** Version bump only for package @sphereon/oid4vci-issuer - - - - ## [0.7.3](https://github.com/Sphereon-Opensource/OID4VCI/compare/v0.7.2...v0.7.3) (2023-09-30) **Note:** Version bump only for package @sphereon/oid4vci-issuer diff --git a/packages/issuer/lib/VcIssuer.ts b/packages/issuer/lib/VcIssuer.ts index 054ceb82..a7173f4f 100644 --- a/packages/issuer/lib/VcIssuer.ts +++ b/packages/issuer/lib/VcIssuer.ts @@ -1,5 +1,3 @@ -import * as process from 'process' - import { Alg, ALG_ERROR, @@ -30,6 +28,7 @@ import { NO_ISS_IN_AUTHORIZATION_CODE_CONTEXT, OID4VCICredentialFormat, OpenId4VCIVersion, + QRCodeOpts, TokenErrorResponse, toUniformCredentialOfferRequest, TYP_ERROR, @@ -101,10 +100,10 @@ export class VcIssuer { baseUri?: string scheme?: string pinLength?: number + qrCodeOpts?: QRCodeOpts }): Promise { let preAuthorizedCode: string | undefined = undefined let issuerState: string | undefined = undefined - const { grants, credentials, credentialDefinition } = opts if (!grants?.authorization_code && !grants?.['urn:ietf:params:oauth:grant-type:pre-authorized_code']) { @@ -139,6 +138,7 @@ export class VcIssuer { } const baseUri = opts?.baseUri ?? this.defaultCredentialOfferBaseUri + const credentialOfferObject = createCredentialOfferObject(this._issuerMetadata, { ...opts, credentialOffer: credentialOfferPayload, @@ -200,9 +200,20 @@ export class VcIssuer { if (issuerState) { await this.credentialOfferSessions.set(issuerState, session) } + + const uri = createCredentialOfferURIFromObject(credentialOffer, { ...opts, baseUri }) + let qrCodeDataUri: string | undefined + if (opts.qrCodeOpts) { + const { AwesomeQR } = await import('awesome-qr') + console.log(uri) + + const qrCode = new AwesomeQR({ ...opts.qrCodeOpts, text: uri }) + qrCodeDataUri = `data:image/png;base64,${(await qrCode.draw())!.toString('base64')}` + } return { session, - uri: createCredentialOfferURIFromObject(credentialOffer, { ...opts, baseUri }), + uri, + qrCodeDataUri, userPinRequired: userPinRequired ?? false, ...(userPin !== undefined && { userPin, pinLength: userPin?.length ?? 0 }), } diff --git a/packages/issuer/lib/__tests__/VcIssuer.spec.ts b/packages/issuer/lib/__tests__/VcIssuer.spec.ts index dd5d9e97..fa6332ce 100644 --- a/packages/issuer/lib/__tests__/VcIssuer.spec.ts +++ b/packages/issuer/lib/__tests__/VcIssuer.spec.ts @@ -133,21 +133,27 @@ describe('VcIssuer', () => { }) it.skip('should create credential offer', async () => { - const uri = await vcIssuer - .createCredentialOfferURI({ - grants: { - authorization_code: { - issuer_state: issuerState, - }, - 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { - 'pre-authorized_code': preAuthorizedCode, - user_pin_required: true, - }, + const { uri, ...rest } = await vcIssuer.createCredentialOfferURI({ + grants: { + authorization_code: { + issuer_state: issuerState, }, - scheme: 'http', - baseUri: 'issuer-example.com', - }) - .then((response) => response.uri) + 'urn:ietf:params:oauth:grant-type:pre-authorized_code': { + 'pre-authorized_code': preAuthorizedCode, + user_pin_required: true, + }, + }, + scheme: 'http', + baseUri: 'issuer-example.com', + qrCodeOpts: { + size: 400, + colorDark: '#000000', + colorLight: '#ffffff', + correctLevel: 2, + }, + }) + + console.log(JSON.stringify(rest, null, 2)) expect(uri).toEqual( 'http://issuer-example.com?credential_offer=%7B%22grants%22%3A%7B%22authorization_code%22%3A%7B%22issuer_state%22%3A%22previously-created-state%22%7D%2C%22urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Apre-authorized_code%22%3A%7B%22pre-authorized_code%22%3A%22test_code%22%2C%22user_pin_required%22%3Atrue%7D%7D%2C%22credential_issuer%22%3A%22https%3A%2F%2Fissuer.research.identiproof.io%22%2C%22credentials%22%3A%5B%7B%22format%22%3A%22jwt_vc_json%22%2C%22types%22%3A%5B%22VerifiableCredential%22%5D%2C%22credentialSubject%22%3A%7B%22given_name%22%3A%7B%22name%22%3A%22given%20name%22%2C%22locale%22%3A%22en-US%22%7D%7D%2C%22cryptographic_suites_supported%22%3A%5B%22ES256K%22%5D%2C%22cryptographic_binding_methods_supported%22%3A%5B%22did%22%5D%2C%22id%22%3A%22UniversityDegree_JWT%22%2C%22display%22%3A%5B%7B%22name%22%3A%22University%20Credential%22%2C%22locale%22%3A%22en-US%22%2C%22logo%22%3A%7B%22url%22%3A%22https%3A%2F%2Fexampleuniversity.com%2Fpublic%2Flogo.png%22%2C%22alt_text%22%3A%22a%20square%20logo%20of%20a%20university%22%7D%2C%22background_color%22%3A%22%2312107c%22%2C%22text_color%22%3A%22%23FFFFFF%22%7D%5D%7D%5D%7D', ) diff --git a/packages/issuer/package.json b/packages/issuer/package.json index d1a40338..5a68335c 100644 --- a/packages/issuer/package.json +++ b/packages/issuer/package.json @@ -14,6 +14,14 @@ "@sphereon/ssi-types": "0.17.2", "uuid": "^9.0.0" }, + "peerDependencies": { + "awesome-qr": "^2.1.5-rc.0" + }, + "peerDependenciesMeta": { + "awesome-qr": { + "optional": true + } + }, "devDependencies": { "@sphereon/oid4vci-client": "workspace:*", "@types/jest": "^29.5.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8224169d..cbe61181 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,6 +224,9 @@ importers: '@sphereon/ssi-types': specifier: 0.17.2 version: 0.17.2 + awesome-qr: + specifier: ^2.1.5-rc.0 + version: 2.1.5-rc.0 uuid: specifier: ^9.0.0 version: 9.0.1 @@ -2729,8 +2732,6 @@ packages: transitivePeerDependencies: - encoding - supports-color - dev: true - optional: true /@mattrglobal/bbs-signatures@1.3.1: resolution: {integrity: sha512-syZGkapPpktD2el4lPTCQRw/LSia6/NwBS83hzCKu4dTlaJRO636qo5NCiiQb+iBYWyZQQEzN0jdRik8N9EUGA==} @@ -4126,7 +4127,6 @@ packages: /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - dev: true /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -4290,7 +4290,6 @@ packages: /aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - dev: true /are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} @@ -4299,8 +4298,6 @@ packages: dependencies: delegates: 1.0.0 readable-stream: 3.6.2 - dev: true - optional: true /are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} @@ -4481,6 +4478,17 @@ packages: resolution: {integrity: sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==} dev: false + /awesome-qr@2.1.5-rc.0: + resolution: {integrity: sha512-nRxvKuJxoxdOIStb79bElh52YPI+Cbu/UewgLjVSpXJvxwIZQjcvZgrCjXPe3jSl2i6mz3foxA6xgzLf8NQy+Q==} + dependencies: + buffer: 6.0.3 + canvas: 2.11.2 + js-binary-schema-parser: 2.0.3 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /axios@1.5.1: resolution: {integrity: sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==} dependencies: @@ -4956,7 +4964,7 @@ packages: minipass-pipeline: 1.2.4 p-map: 4.0.0 ssri: 10.0.5 - tar: 6.1.11 + tar: 6.2.0 unique-filename: 3.0.0 dev: true @@ -5025,6 +5033,19 @@ packages: /canonicalize@1.0.8: resolution: {integrity: sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==} + /canvas@2.11.2: + resolution: {integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==} + engines: {node: '>=6'} + requiresBuild: true + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + nan: 2.18.0 + simple-get: 3.1.1 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /casbin@5.27.1: resolution: {integrity: sha512-nAbUyMfeVasZDZ39/OX+Y8AzpiAGvhYI58l/3BKuDZSnqC+Rpja/DmcHPt0wcUgJxfMkG/yo0dCmMhnSXZq49A==} dependencies: @@ -5217,7 +5238,6 @@ packages: /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true - dev: true /colorette@1.4.0: resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} @@ -5356,7 +5376,6 @@ packages: /console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - dev: true /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} @@ -5690,6 +5709,13 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /decompress-response@4.2.1: + resolution: {integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==} + engines: {node: '>=8'} + dependencies: + mimic-response: 2.1.0 + dev: false + /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -5799,7 +5825,6 @@ packages: /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dev: true /denodeify@1.2.1: resolution: {integrity: sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==} @@ -5832,8 +5857,6 @@ packages: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} requiresBuild: true - dev: true - optional: true /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} @@ -7065,8 +7088,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wide-align: 1.1.5 - dev: true - optional: true /gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} @@ -7403,7 +7424,6 @@ packages: /has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - dev: true /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} @@ -8611,6 +8631,10 @@ packages: /jose@4.14.6: resolution: {integrity: sha512-EqJPEUlZD0/CSUMubKtMaYUOtWe91tZXTWMJZoKSbLk+KtdhNdcvppH8lA9XwVu2V4Ailvsj0GBZJ2ZwDjfesQ==} + /js-binary-schema-parser@2.0.3: + resolution: {integrity: sha512-xezGJmOb4lk/M1ZZLTR/jaBHQ4gG/lqQnJqdIv4721DMggsa1bDVlHXNeHYogaIEHD9vCRv0fcL4hMA+Coarkg==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -9127,8 +9151,6 @@ packages: requiresBuild: true dependencies: semver: 6.3.1 - dev: true - optional: true /make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} @@ -9666,6 +9688,11 @@ packages: engines: {node: '>=12'} dev: true + /mimic-response@2.1.0: + resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} + engines: {node: '>=8'} + dev: false + /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -9899,6 +9926,10 @@ packages: object-assign: 4.1.1 thenify-all: 1.6.0 + /nan@2.18.0: + resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==} + dev: false + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -10013,7 +10044,7 @@ packages: npmlog: 6.0.2 rimraf: 3.0.2 semver: 7.5.4 - tar: 6.1.11 + tar: 6.2.0 which: 2.0.2 transitivePeerDependencies: - supports-color @@ -10040,8 +10071,6 @@ packages: requiresBuild: true dependencies: abbrev: 1.1.1 - dev: true - optional: true /nopt@6.0.0: resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} @@ -10237,8 +10266,6 @@ packages: console-control-strings: 1.1.0 gauge: 3.0.2 set-blocking: 2.0.0 - dev: true - optional: true /npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} @@ -11811,6 +11838,18 @@ packages: - supports-color dev: true + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@3.1.1: + resolution: {integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==} + dependencies: + decompress-response: 4.2.1 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + /simple-plist@1.3.1: resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} dependencies: @@ -13125,7 +13164,6 @@ packages: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: string-width: 4.2.3 - dev: true /wonka@4.0.15: resolution: {integrity: sha512-U0IUQHKXXn6PFo9nqsHphVCE5m3IntqZNB9Jjn7EB1lrR7YTDY3YWgFvEvwniTzXSvOH/XMzAZaIfJF/LvHYXg==}