Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow connection invitation encoded in oob url param #1583

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 31 additions & 4 deletions packages/core/src/utils/__tests__/shortenedUrl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Response } from 'node-fetch'
import { Headers } from 'node-fetch'

import { ConnectionInvitationMessage } from '../../modules/connections'
import { OutOfBandInvitation } from '../../modules/oob'
import { InvitationType, OutOfBandInvitation } from '../../modules/oob'
import { convertToNewInvitation } from '../../modules/oob/helpers'
import { JsonTransformer } from '../JsonTransformer'
import { MessageValidator } from '../MessageValidator'
Expand Down Expand Up @@ -80,9 +80,10 @@ let connectionInvitationMock: ConnectionInvitationMessage
let connectionInvitationToNew: OutOfBandInvitation

beforeAll(async () => {
outOfBandInvitationMock = await JsonTransformer.fromJSON(mockOobInvite, OutOfBandInvitation)
outOfBandInvitationMock = JsonTransformer.fromJSON(mockOobInvite, OutOfBandInvitation)
outOfBandInvitationMock.invitationType = InvitationType.OutOfBand
MessageValidator.validateSync(outOfBandInvitationMock)
connectionInvitationMock = await JsonTransformer.fromJSON(mockConnectionInvite, ConnectionInvitationMessage)
connectionInvitationMock = JsonTransformer.fromJSON(mockConnectionInvite, ConnectionInvitationMessage)
MessageValidator.validateSync(connectionInvitationMock)
connectionInvitationToNew = convertToNewInvitation(connectionInvitationMock)
})
Expand Down Expand Up @@ -113,8 +114,34 @@ describe('shortened urls resolving to connection invitations', () => {
const short = await oobInvitationFromShortUrl(mockedResponseConnectionJson)
expect(short).toEqual(connectionInvitationToNew)
})
test('Resolve a mocked Response in the form of a connection invitation encoded in an url', async () => {
test('Resolve a mocked Response in the form of a connection invitation encoded in an url c_i query parameter', async () => {
const short = await oobInvitationFromShortUrl(mockedResponseConnectionUrl)
expect(short).toEqual(connectionInvitationToNew)
})
test('Resolve a mocked Response in the form of a connection invitation encoded in an url oob query parameter', async () => {
const mockedResponseConnectionInOobUrl = {
status: 200,
ok: true,
headers: dummyHeader,
url: 'https://oob.lissi.io/ssi?oob=eyJAdHlwZSI6ImRpZDpzb3Y6QnpDYnNOWWhNcmpIaXFaRFRVQVNIZztzcGVjL2Nvbm5lY3Rpb25zLzEuMC9pbnZpdGF0aW9uIiwiQGlkIjoiMGU0NmEzYWEtMzUyOC00OTIxLWJmYjItN2JjYjk0NjVjNjZjIiwibGFiZWwiOiJTdGFkdCB8IExpc3NpLURlbW8iLCJzZXJ2aWNlRW5kcG9pbnQiOiJodHRwczovL2RlbW8tYWdlbnQuaW5zdGl0dXRpb25hbC1hZ2VudC5saXNzaS5pZC9kaWRjb21tLyIsImltYWdlVXJsIjoiaHR0cHM6Ly9yb3V0aW5nLmxpc3NpLmlvL2FwaS9JbWFnZS9kZW1vTXVzdGVyaGF1c2VuIiwicmVjaXBpZW50S2V5cyI6WyJEZlcxbzM2ekxuczlVdGlDUGQyalIyS2pvcnRvZkNhcFNTWTdWR2N2WEF6aCJdfQ',
} as Response

mockedResponseConnectionInOobUrl.headers = dummyHeader

const expectedOobMessage = convertToNewInvitation(
JsonTransformer.fromJSON(
{
'@type': 'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/connections/1.0/invitation',
'@id': '0e46a3aa-3528-4921-bfb2-7bcb9465c66c',
label: 'Stadt | Lissi-Demo',
serviceEndpoint: 'https://demo-agent.institutional-agent.lissi.id/didcomm/',
imageUrl: 'https://routing.lissi.io/api/Image/demoMusterhausen',
recipientKeys: ['DfW1o36zLns9UtiCPd2jR2KjortofCapSSY7VGcvXAzh'],
},
ConnectionInvitationMessage
)
)
const short = await oobInvitationFromShortUrl(mockedResponseConnectionInOobUrl)
expect(short).toEqual(expectedOobMessage)
})
})
66 changes: 39 additions & 27 deletions packages/core/src/utils/parseInvitation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,36 @@ const fetchShortUrl = async (invitationUrl: string, dependencies: AgentDependenc
return response
}

/**
* Parses a JSON containing an invitation message and returns an OutOfBandInvitation instance
*
* @param invitationJson JSON object containing message
* @returns OutOfBandInvitation
*/
export const parseInvitationJson = (invitationJson: Record<string, unknown>): OutOfBandInvitation => {
const messageType = invitationJson['@type'] as string

if (!messageType) {
throw new AriesFrameworkError('Invitation is not a valid DIDComm message')
}

const parsedMessageType = parseMessageType(messageType)
if (supportsIncomingMessageType(parsedMessageType, OutOfBandInvitation.type)) {
const invitation = JsonTransformer.fromJSON(invitationJson, OutOfBandInvitation)
MessageValidator.validateSync(invitation)
invitation.invitationType = InvitationType.OutOfBand
return invitation
} else if (supportsIncomingMessageType(parsedMessageType, ConnectionInvitationMessage.type)) {
const invitation = JsonTransformer.fromJSON(invitationJson, ConnectionInvitationMessage)
MessageValidator.validateSync(invitation)
const outOfBandInvitation = convertToNewInvitation(invitation)
outOfBandInvitation.invitationType = InvitationType.Connection
return outOfBandInvitation
} else {
throw new AriesFrameworkError(`Invitation with '@type' ${parsedMessageType.messageTypeUri} not supported.`)
}
}

/**
* Parses URL containing encoded invitation and returns invitation message.
*
Expand All @@ -44,12 +74,12 @@ const fetchShortUrl = async (invitationUrl: string, dependencies: AgentDependenc
*/
export const parseInvitationUrl = (invitationUrl: string): OutOfBandInvitation => {
const parsedUrl = parseUrl(invitationUrl).query
if (parsedUrl['oob']) {
const outOfBandInvitation = OutOfBandInvitation.fromUrl(invitationUrl)
return outOfBandInvitation
} else if (parsedUrl['c_i'] || parsedUrl['d_m']) {
const invitation = ConnectionInvitationMessage.fromUrl(invitationUrl)
return convertToNewInvitation(invitation)

const encodedInvitation = parsedUrl['oob'] ?? parsedUrl['c_i'] ?? parsedUrl['d_m']

if (typeof encodedInvitation === 'string') {
const invitationJson = JsonEncoder.fromBase64(encodedInvitation) as Record<string, unknown>
return parseInvitationJson(invitationJson)
}
throw new AriesFrameworkError(
'InvitationUrl is invalid. It needs to contain one, and only one, of the following parameters: `oob`, `c_i` or `d_m`.'
Expand All @@ -61,18 +91,7 @@ export const oobInvitationFromShortUrl = async (response: Response): Promise<Out
if (response) {
if (response.headers.get('Content-Type')?.startsWith('application/json') && response.ok) {
const invitationJson = await response.json()
const parsedMessageType = parseMessageType(invitationJson['@type'])
if (supportsIncomingMessageType(parsedMessageType, OutOfBandInvitation.type)) {
const invitation = JsonTransformer.fromJSON(invitationJson, OutOfBandInvitation)
MessageValidator.validateSync(invitation)
return invitation
} else if (supportsIncomingMessageType(parsedMessageType, ConnectionInvitationMessage.type)) {
const invitation = JsonTransformer.fromJSON(invitationJson, ConnectionInvitationMessage)
MessageValidator.validateSync(invitation)
return convertToNewInvitation(invitation)
} else {
throw new AriesFrameworkError(`Invitation with '@type' ${parsedMessageType.messageTypeUri} not supported.`)
}
return parseInvitationJson(invitationJson)
} else if (response['url']) {
// The following if else is for here for trinsic shorten urls
// Because the redirect targets a deep link the automatic redirect does not occur
Expand Down Expand Up @@ -102,15 +121,8 @@ export const parseInvitationShortUrl = async (
dependencies: AgentDependencies
): Promise<OutOfBandInvitation> => {
const parsedUrl = parseUrl(invitationUrl).query
if (parsedUrl['oob']) {
const outOfBandInvitation = OutOfBandInvitation.fromUrl(invitationUrl)
outOfBandInvitation.invitationType = InvitationType.OutOfBand
return outOfBandInvitation
} else if (parsedUrl['c_i']) {
const invitation = ConnectionInvitationMessage.fromUrl(invitationUrl)
const outOfBandInvitation = convertToNewInvitation(invitation)
outOfBandInvitation.invitationType = InvitationType.Connection
return outOfBandInvitation
if (parsedUrl['oob'] || parsedUrl['c_i']) {
return parseInvitationUrl(invitationUrl)
}
// Legacy connectionless invitation
else if (parsedUrl['d_m']) {
Expand Down