Skip to content

Commit

Permalink
feat: New QR code provider plugin. Can generate both SIOPv2 and DIDCo…
Browse files Browse the repository at this point in the history
…mmv2 OOB QRs. Support for text generation and React QR codes as SVG
  • Loading branch information
nklomp committed Feb 25, 2023
1 parent 5a87aa3 commit d40ba75
Show file tree
Hide file tree
Showing 15 changed files with 436 additions and 229 deletions.
30 changes: 15 additions & 15 deletions packages/waci-pex-qr-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ recipient to either:
1. authenticate itself to the requester
2. inviting the issuer to issue a credential

The data fields required to generate the QR code will depend on the type of request and the acceptable values. The
The object fields required to generate the QR code will depend on the type of request and the acceptable values. The
possible `accept` value may be:

1. `oidc4vp`
Expand All @@ -50,18 +50,18 @@ possible `accept` value may be:
#### Importing the plugin

```typescript
import { WaciQrCodeProvider } from '@sphereon/ssi-sdk-waci-pex-qr-react'
import { QrCodeProvider } from '@sphereon/ssi-sdk-waci-pex-qr-react'

// Include in the interface
// const agent = createAgent<... WaciQrCodeProvider>
// const agent = createAgent<... QrCodeProvider>
```

#### Adding plugin to the agent

```typescript
plugins: [
...
new WaciQrCodeProvider()
new QrCodeProvider()
],
```

Expand All @@ -81,19 +81,19 @@ import { QRContent, QRType } from '@sphereon/ssi-sdk-waci-pex-qr-react'
#### Inside the component we can declare or get the values to pass to QR Code plugin

```typescript
import { OobPayload } from '@sphereon/ssi-sdk-waci-pex-qr-react'
import { WaciOobProps } from '@sphereon/ssi-sdk-waci-pex-qr-react'

function getOobQrCodeProps(): OobQRProps {
function getOobQrCodeProps(): QRRenderingProps {
return {
oobBaseUrl: 'https://example.com/?oob=',
type: QRType.DID_AUTH_SIOP_V2,
baseUrl: 'https://example.com/?oob=',
type: QRType.SIOPV2,
id: '599f3638-b563-4937-9487-dfe55099d900',
from: 'did:key:zrfdjkgfjgfdjk',
body: {
object: {
goalCode: GoalCode.STREAMLINED_VP,
accept: [AcceptMode.SIOPV2_WITH_OIDC4VP],
},
onGenerate: (oobQRProps: OobQRProps, payload: OobPayload) => {
onGenerate: (oobQRProps: QRRenderingProps, payload: WaciOobProps) => {
console.log(payload)
},
bgColor: 'white',
Expand All @@ -105,7 +105,7 @@ function getOobQrCodeProps(): OobQRProps {
}

delegateCreateOobQRCode = () => {
let qrCode = createOobQrCode(this.getOobQrCodeProps())
let qrCode = createQrCode(this.getOobQrCodeProps())
return qrCode.then((qrCodeResolved) => {
return qrCodeResolved
})
Expand All @@ -119,7 +119,7 @@ On generate gives the following (example) output
"type": "openid",
"id": "599f3638-b563-4937-9487-dfe55099d900",
"from": "did:key:zrfdjkgfjgfdjk",
"body": {
"object": {
"goal-code": "streamlined-vp",
"accept": ["siopv2+oidc4vp"]
}
Expand All @@ -129,9 +129,9 @@ On generate gives the following (example) output
If you want to create the payload manually and want to do serialization yourself you can use:

```typescript
const payload = OutOfBandMessage.createPayload(getOobQrCodeProps())
const encoded = OutOfBandMessage.urlEncode(payload)
const url = oobQRProps.oobBaseUrl + encoded
const payload = DidCommOutOfBandMessage.createPayload(getOobQrCodeProps())
const encoded = DidCommOutOfBandMessage.urlEncode(payload)
const url = oobQRProps.baseUrl + encoded
console.log(url) // https://example.com/?oob=eyJ0eXBlIjoic2lvcHYyIiwiaWQiOiI1OTlmMzYzOC1iNTYzLTQ5MzctOTQ4Ny1kZmU1NTA5OWQ5MDAiLCJmcm9tIjoiZGlkOmtleTp6cmZkamtnZmpnZmRqayIsImJvZHkiOnsiZ29hbC1jb2RlIjoic3RyZWFtbGluZWQtdnAiLCJhY2NlcHQiOlsic2lvcHYyK29pZGM0dnAiXX19
```

Expand Down
6 changes: 3 additions & 3 deletions packages/waci-pex-qr-react/__tests__/agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ agent:
$args:
- schemaValidation: false
plugins:
- $require: ./packages/waci-pex-qr-react/dist#WaciQrCodeProvider
$args:
- oobQRProps
- $require: ./packages/waci-pex-qr-react/dist#QrCodeProvider
# $args:
# - renderingProps
48 changes: 7 additions & 41 deletions packages/waci-pex-qr-react/__tests__/encoding.test.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,22 @@
import { AcceptMode, GoalCode, OobPayload, OobQRProps, QRType } from '../src'
import { OutOfBandMessage } from '../src/agent/qr-utils/outOfBandMessage'
import base64url from 'base64url'

const oobQRProps: OobQRProps = {
oobBaseUrl: 'https://example.com/?oob=',
type: QRType.DID_AUTH_SIOP_V2,
id: '599f3638-b563-4937-9487-dfe55099d900',
from: 'did:key:zrfdjkgfjgfdjk',
body: {
goalCode: GoalCode.STREAMLINED_VP,
accept: [AcceptMode.SIOPV2_WITH_OIDC4VP],
},
onGenerate: (oobQRProps: OobQRProps, payload: OobPayload) => {
console.log(payload)
},
bgColor: 'white',
fgColor: 'black',
level: 'L',
size: 128,
title: 'title2021120903',
}
import { DidCommOutOfBandMessage } from '../src/agent/utils'
import { oobInvitation } from './shared/fixtures'

describe('SSI QR Code', () => {
it('should create payload object', async () => {
const payload = OutOfBandMessage.createPayload(oobQRProps)
// const encoded = OutOfBandMessage.encode(payload)
// const url = oobQRProps.oobBaseUrl + encoded

expect(payload).toMatchObject({
body: { accept: ['siopv2+oidc4vp'], goalCode: 'streamlined-vp' },
from: 'did:key:zrfdjkgfjgfdjk',
id: '599f3638-b563-4937-9487-dfe55099d900',
type: 'siopv2',
})
})

it('should create json value', async () => {
const payload = OutOfBandMessage.createPayload(oobQRProps)
const json = OutOfBandMessage.toJson(payload)
const json = DidCommOutOfBandMessage.toJson(oobInvitation)
expect(json).toMatch(
'{"type":"siopv2","id":"599f3638-b563-4937-9487-dfe55099d900","from":"did:key:zrfdjkgfjgfdjk","body":{"goal-code":"streamlined-vp","accept":["siopv2+oidc4vp"]}}'
'{"type":"https://didcomm.org/out-of-band/2.0/invitation","id":"599f3638-b563-4937-9487-dfe55099d900","from":"did:key:zrfdjkgfjgfdjk","body":{"goal_code":"streamlined-vp","accept":["didcomm/v2"]}}'
)
})

it('should url encode and decode', async () => {
const payload = OutOfBandMessage.createPayload(oobQRProps)
const urlEncoded = OutOfBandMessage.urlEncode(payload)
const urlEncoded = DidCommOutOfBandMessage.urlEncode(oobInvitation)
expect(urlEncoded).toMatch(
'eyJ0eXBlIjoic2lvcHYyIiwiaWQiOiI1OTlmMzYzOC1iNTYzLTQ5MzctOTQ4Ny1kZmU1NTA5OWQ5MDAiLCJmcm9tIjoiZGlkOmtleTp6cmZkamtnZmpnZmRqayIsImJvZHkiOnsiZ29hbC1jb2RlIjoic3RyZWFtbGluZWQtdnAiLCJhY2NlcHQiOlsic2lvcHYyK29pZGM0dnAiXX19'
'eyJ0eXBlIjoiaHR0cHM6Ly9kaWRjb21tLm9yZy9vdXQtb2YtYmFuZC8yLjAvaW52aXRhdGlvbiIsImlkIjoiNTk5ZjM2MzgtYjU2My00OTM3LTk0ODctZGZlNTUwOTlkOTAwIiwiZnJvbSI6ImRpZDprZXk6enJmZGprZ2ZqZ2ZkamsiLCJib2R5Ijp7ImdvYWxfY29kZSI6InN0cmVhbWxpbmVkLXZwIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiXX19'
)
expect(base64url.decode(urlEncoded)).toMatch(
'{"type":"siopv2","id":"599f3638-b563-4937-9487-dfe55099d900","from":"did:key:zrfdjkgfjgfdjk","body":{"goal-code":"streamlined-vp","accept":["siopv2+oidc4vp"]}}'
'{"type":"https://didcomm.org/out-of-band/2.0/invitation","id":"599f3638-b563-4937-9487-dfe55099d900","from":"did:key:zrfdjkgfjgfdjk","body":{"goal_code":"streamlined-vp","accept":["didcomm/v2"]}}'
)
})
})
85 changes: 85 additions & 0 deletions packages/waci-pex-qr-react/__tests__/shared/fixtures.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
CreateElementArgs,
CreateValueArgs,
DIDCommV2OOBInvitation,
DIDCommV2OOBInvitationData,
QRData,
QRRenderingProps,
QRType,
SIOPv2DataWithScheme,
ValueResult,
} from '../../src'
import { render } from '@testing-library/react'
import * as React from 'react'

export const renderingProps: QRRenderingProps = {
bgColor: 'white',
fgColor: 'black',
level: 'L',
size: 128,
title: 'title2021120903',
}

export const oobInvitation: DIDCommV2OOBInvitation = {
type: QRType.DIDCOMM_V2_OOB_INVITATION,
id: '599f3638-b563-4937-9487-dfe55099d900',
from: 'did:key:zrfdjkgfjgfdjk',
body: {
goal_code: 'streamlined-vp',
accept: ['didcomm/v2'],
},
}

export const oobInvitationData: DIDCommV2OOBInvitationData = {
oobInvitation,
baseURI: 'https://example.com/?_oob=',
}

export const oobInvitationCreateValue: CreateValueArgs<QRType.DIDCOMM_V2_OOB_INVITATION, DIDCommV2OOBInvitationData> = {
data: {
object: oobInvitationData,
type: QRType.DIDCOMM_V2_OOB_INVITATION,
id: '1234',
},
onGenerate: (result: ValueResult<QRType.DIDCOMM_V2_OOB_INVITATION, DIDCommV2OOBInvitationData>) => {
console.log(JSON.stringify(result, null, 2))
},
}

export const oobInvitationCreateElement: CreateElementArgs<QRType.DIDCOMM_V2_OOB_INVITATION, DIDCommV2OOBInvitationData> = {
data: {
object: oobInvitationData,
type: QRType.DIDCOMM_V2_OOB_INVITATION,
id: '1234',
},
renderingProps,
onGenerate: (result: ValueResult<QRType.DIDCOMM_V2_OOB_INVITATION, DIDCommV2OOBInvitationData>) => {
render(<div data-testid="test-div">{result.data.object.oobInvitation.from}</div>)
console.log(result.value)
},
}

const siopv2Object: SIOPv2DataWithScheme = {
scheme: 'openid-vc',
requestUri: 'https://test.com?id=23',
}
const siopv2Data: QRData<QRType.SIOPV2, SIOPv2DataWithScheme> = {
object: siopv2Object,
type: QRType.SIOPV2,
id: '456',
}
export const siopv2CreateValue: CreateValueArgs<QRType.SIOPV2, SIOPv2DataWithScheme> = {
data: siopv2Data,
onGenerate: (result: ValueResult<QRType.SIOPV2, SIOPv2DataWithScheme>) => {
console.log(JSON.stringify(result, null, 2))
},
}

export const siopv2CreateElement: CreateElementArgs<QRType.SIOPV2, SIOPv2DataWithScheme> = {
data: siopv2Data,
renderingProps,
onGenerate: (result: ValueResult<QRType.SIOPV2, SIOPv2DataWithScheme>) => {
render(<div data-testid="test-div-siopv2">{result.data.object.requestUri}</div>)
console.log(result.value)
},
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,11 @@
import { TAgent } from '@veramo/core'
import { AcceptMode, GoalCode, OobPayload, OobQRProps, QRType, WaciTypes } from '../../src'
import { IQRCodeGenerator } from '../../src'
import { render, screen } from '@testing-library/react'
// @ts-ignore
import React from 'react'
import { oobInvitationCreateElement, oobInvitationCreateValue, siopv2CreateElement, siopv2CreateValue } from './fixtures'

type ConfiguredAgent = TAgent<WaciTypes>

const oobQRProps: OobQRProps = {
oobBaseUrl: 'https://example.com/?oob=',
type: QRType.DID_AUTH_SIOP_V2,
id: '599f3638-b563-4937-9487-dfe55099d900',
from: 'did:key:zrfdjkgfjgfdjk',
body: {
goalCode: GoalCode.STREAMLINED_VP,
accept: [AcceptMode.SIOPV2_WITH_OIDC4VP],
},
onGenerate: (oobQRProps: OobQRProps, payload: OobPayload) => {
render(<div data-testid="test-div">{oobQRProps.from}</div>)
console.log(payload)
},
bgColor: 'white',
fgColor: 'black',
level: 'L',
size: 128,
title: 'title2021120903',
}
type ConfiguredAgent = TAgent<IQRCodeGenerator>

export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Promise<boolean>; tearDown: () => Promise<boolean> }) => {
describe('SSI QR Code', () => {
Expand All @@ -39,25 +20,46 @@ export default (testContext: { getAgent: () => ConfiguredAgent; setup: () => Pro
await testContext.tearDown()
})

it('should create qr code', async () => {
await agent.createOobQrCode(oobQRProps).then((ssiQrCode) => {
expect(ssiQrCode).not.toBeNull()
it('should create DIDComm V2 OOB Invitation qr code value', async () => {
await agent.didCommOobInvitationValue(oobInvitationCreateValue).then((ssiQrCode) => {
expect(ssiQrCode).toEqual(
'https://example.com/?_oob=eyJ0eXBlIjoiaHR0cHM6Ly9kaWRjb21tLm9yZy9vdXQtb2YtYmFuZC8yLjAvaW52aXRhdGlvbiIsImlkIjoiNTk5ZjM2MzgtYjU2My00OTM3LTk0ODctZGZlNTUwOTlkOTAwIiwiZnJvbSI6ImRpZDprZXk6enJmZGprZ2ZqZ2ZkamsiLCJib2R5Ijp7ImdvYWxfY29kZSI6InN0cmVhbWxpbmVkLXZwIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiXX19'
)
})
})

it('should create qr code with props', async () => {
await agent.createOobQrCode(oobQRProps).then(async (ssiQrCode) => {
it('should create DIDComm V2 OOB Invitation qr code with renderingProps', async () => {
await agent.didCommOobInvitationElement(oobInvitationCreateElement).then(async (ssiQrCode) => {
render(ssiQrCode)

// The on generate created a div with test id 'test' and did:key value
const div = screen.queryByTestId('test-div')
expect(div!.childNodes[0]!.textContent).toEqual('did:key:zrfdjkgfjgfdjk')

expect(ssiQrCode.props.value).toEqual(
'https://example.com/?oob=eyJ0eXBlIjoic2lvcHYyIiwiaWQiOiI1OTlmMzYzOC1iNTYzLTQ5MzctOTQ4Ny1kZmU1NTA5OWQ5MDAiLCJmcm9tIjoiZGlkOmtleTp6cmZkamtnZmpnZmRqayIsImJvZHkiOnsiZ29hbC1jb2RlIjoic3RyZWFtbGluZWQtdnAiLCJhY2NlcHQiOlsic2lvcHYyK29pZGM0dnAiXX19'
'https://example.com/?_oob=eyJ0eXBlIjoiaHR0cHM6Ly9kaWRjb21tLm9yZy9vdXQtb2YtYmFuZC8yLjAvaW52aXRhdGlvbiIsImlkIjoiNTk5ZjM2MzgtYjU2My00OTM3LTk0ODctZGZlNTUwOTlkOTAwIiwiZnJvbSI6ImRpZDprZXk6enJmZGprZ2ZqZ2ZkamsiLCJib2R5Ijp7ImdvYWxfY29kZSI6InN0cmVhbWxpbmVkLXZwIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiXX19'
)
screen.debug()
})
})

it('should create SIOPv2 qr code value', async () => {
await agent.siopv2Value(siopv2CreateValue).then((ssiQrCode) => {
expect(ssiQrCode).toEqual('openid-vc://?request_uri=https://test.com?id=23')
})
})

it('should create SIOPv2 qr code with renderingProps', async () => {
await agent.siopv2Element(siopv2CreateElement).then(async (ssiQrCode) => {
render(ssiQrCode)

// The on generate created a div with test id 'test' and did:key value
const div = screen.queryByTestId('test-div-siopv2')
expect(div!.childNodes[0]!.textContent).toEqual('https://test.com?id=23')

expect(ssiQrCode.props.value).toEqual('openid-vc://?request_uri=https://test.com?id=23')
screen.debug()
})
})
})
}
27 changes: 15 additions & 12 deletions packages/waci-pex-qr-react/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@sphereon/ssi-sdk-waci-pex-qr-react",
"name": "@sphereon/ssi-sdk-qr-react",
"version": "0.8.0",
"description": "WACI PEx QR Code provider (react)",
"description": "QR Code provider (react)",
"source": "src/index.ts",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand All @@ -20,18 +20,19 @@
"react-qr-code": "^2.0.11"
},
"devDependencies": {
"@types/jest": "^29.2.4",
"@types/react": "^18.0.26",
"@types/jest": "^29.4.0",
"@types/react": "^18.0.28",
"@types/uuid": "^8.3.4",
"@veramo/cli": "4.2.0",
"react-dom": "^18.2.0",
"jsdom": "^20.0.3",
"global-jsdom": "^8.6.0",
"jest-environment-jsdom": "^29.3.1",
"@testing-library/react": "^13.4.0",
"jsdom": "^21.1.0",
"global-jsdom": "^8.7.0",
"@inrupt/jest-jsdom-polyfills": "^1.5.5",
"jest-environment-jsdom": "^29.4.3",
"@testing-library/react": "^14.0.0",
"@testing-library/jest-dom": "^5.16.5",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"jest": "^29.4.3",
"ts-jest": "^29.0.5",
"typescript": "^4.6.4"
},
"files": [
Expand All @@ -49,9 +50,11 @@
"keywords": [
"Sphereon",
"SSI",
"DIF",
"React",
"WACI PEx",
"WACI",
"OpenID",
"OpenID4VP",
"OpenID4VCI",
"QR Code"
]
}
Loading

0 comments on commit d40ba75

Please sign in to comment.